diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 9d0944eb5931d59b0704bbc96c532351a85e9e83..419521f727e78ff21ae9a17f9cbac9fe20def720 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -1,200 +1,30 @@ +/** @type { import("eslint").Linter.Config } */ module.exports = { root: true, extends: [ - "eslint:recommended", - "plugin:svelte/recommended", - "prettier", + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:svelte/recommended' ], + parser: '@typescript-eslint/parser', + plugins: ['@typescript-eslint'], parserOptions: { - sourceType: "module", + sourceType: 'module', ecmaVersion: 2020, - //extraFileExtensions: [".svelte"], + extraFileExtensions: ['.svelte'] }, env: { browser: true, es2017: true, - node: true, - }, - rules: { - "no-await-in-loop": ["warn"], - "no-constant-binary-expression": ["warn"], - "no-duplicate-imports": ["error"], - "no-promise-executor-return": ["error", { allowVoid: true }], - "no-template-curly-in-string": ["warn"], - "no-unmodified-loop-condition": ["warn"], - "no-unreachable-loop": ["warn"], - "camelcase": ["error"], - "consistent-return": ["error"], - "default-case-last": ["error"], - "default-param-last": ["error"], - "dot-notation": ["warn"], - "eqeqeq": ["warn"], - "no-alert": ["warn"], - "no-eval": ["error"], - "no-implied-eval": ["error"], - "no-new-func": ["error"], - "no-var": ["error"], - "prefer-const": ["warn", { destructuring: "all" }], - "prefer-template": ["warn"], - "yoda": ["warn", "never", { exceptRange: true }], - "array-bracket-newline": ["warn", "consistent"], - "array-bracket-spacing": ["warn", "never"], - "array-element-newline": ["warn", "consistent"], - "brace-style": ["error", "1tbs"], - "comma-dangle": ["warn", "always-multiline"], - "comma-spacing": ["warn", { before: false, after: true }], - "comma-style": ["warn", "last"], - "computed-property-spacing": ["warn", "never"], - "dot-location": ["warn", "property"], - "eol-last": ["warn", "always"], - "func-call-spacing": ["warn", "never"], - "function-call-argument-newline": ["warn", "consistent"], - "function-paren-newline": ["warn", "multiline"], - "indent": ["warn", "tab"], - "key-spacing": ["warn", { beforeColon: false, afterColon: true }], - "keyword-spacing": ["warn", { before: false, after: false, overrides: { - "import": { before: false, after: true }, - "from": { before: true, after: true }, - "return": { before: true, after: true }, - "const": { before: true, after: true }, - "let": { before: true, after: true }, - "var": { before: true, after: true }, - "of": { before: true, after: true }, - "in": { before: true, after: true }, - "export": { before: true, after: true }, - "continue": { before: true, after: false }, - "throw": { before: true, after: true }, - } }], - "no-extra-semi": ["error"], - "no-loss-of-precision": ["error"], - "linebreak-style": ["warn", "unix"], - "new-parens": ["warn", "always"], - "newline-per-chained-call": ["warn", { ignoreChainWithDepth: 3 }], - "no-tabs": ["warn", { allowIndentationTabs: true }], - "no-trailing-spaces": ["warn", { skipBlankLines: true }], - "no-whitespace-before-property": ["warn"], - "object-curly-newline": ["warn", { consistent: true }], - //"object-curly-spacing": ["warn", "always"], - "operator-linebreak": ["warn", "before", { overrides: { ":": "after", "=": "after" } }], - "quotes": ["warn", "double", { avoidEscape: true, allowTemplateLiterals: true }], - "require-await": ["warn"], - "rest-spread-spacing": ["warn", "never"], - "semi": ["warn", "always"], - "semi-style": ["warn", "last"], - "space-before-blocks": ["warn", "never"], - "space-before-function-paren": ["warn", { named: "never" }], - "space-in-parens": ["warn", "never"], - "space-unary-ops": ["warn", { words: true, nonwords: false }], - "switch-colon-spacing": ["warn", { before: false, after: true }], - "template-curly-spacing": ["warn", "never"], - "template-tag-spacing": ["warn", "never"], + node: true }, overrides: [ { - files: ["**/*.ts"], - extends: [ - "eslint:recommended", - "plugin:@typescript-eslint/recommended", - ], - parser: "@typescript-eslint/parser", - plugins: ["@typescript-eslint"], + files: ['*.svelte'], + parser: 'svelte-eslint-parser', parserOptions: { - project: ["./.svelte-kit/tsconfig.json"], - }, - rules: { - "@typescript-eslint/no-explicit-any": "off", - "no-await-in-loop": ["warn"], - "no-constant-binary-expression": ["warn"], - "no-duplicate-imports": ["error"], - "no-promise-executor-return": ["error", { allowVoid: true }], - "no-template-curly-in-string": ["warn"], - "no-unmodified-loop-condition": ["warn"], - "no-unreachable-loop": ["warn"], - "camelcase": ["error"], - "consistent-return": ["error"], - "default-case-last": ["error"], - "default-param-last": "off", - "@typescript-eslint/default-param-last": ["error"], - "dot-notation": "off", - "@typescript-eslint/dot-notation": ["warn"], - "eqeqeq": ["warn"], - "no-alert": ["warn"], - "no-eval": ["error"], - "no-implied-eval": "off", - "@typescript-eslint/no-implied-eval": ["error"], - "no-new-func": ["error"], - "no-var": ["error"], - "prefer-const": ["warn", { destructuring: "all" }], - "prefer-template": ["warn"], - "yoda": ["warn", "never", { exceptRange: true }], - "array-bracket-newline": ["warn", "consistent"], - "array-bracket-spacing": ["warn", "never"], - "array-element-newline": ["warn", "consistent"], - "brace-style": "off", - "@typescript-eslint/brace-style": ["error", "1tbs"], - "comma-dangle": "off", - "@typescript-eslint/comma-dangle": ["warn", "always-multiline"], - "comma-spacing": "off", - "@typescript-eslint/comma-spacing": ["warn", { before: false, after: true }], - "comma-style": ["warn", "last"], - "computed-property-spacing": ["warn", "never"], - "dot-location": ["warn", "property"], - "eol-last": ["warn", "always"], - "func-call-spacing": "off", - "@typescript-eslint/func-call-spacing": ["warn", "never"], - "function-call-argument-newline": ["warn", "consistent"], - "function-paren-newline": ["warn", "multiline"], - "indent": "off", - "@typescript-eslint/indent": ["warn", "tab"], - "key-spacing": "off", - "@typescript-eslint/key-spacing": ["warn", { beforeColon: false, afterColon: true }], - "keyword-spacing": "off", - "@typescript-eslint/keyword-spacing": ["warn", { before: false, after: false, overrides: { - "import": { before: false, after: true }, - "from": { before: true, after: true }, - "type": { before: true, after: true }, - "return": { before: true, after: true }, - "as": { before: true, after: true }, - "const": { before: true, after: true }, - "let": { before: true, after: true }, - "var": { before: true, after: true }, - "of": { before: true, after: true }, - "in": { before: true, after: true }, - "export": { before: true, after: true }, - "continue": { before: true, after: false }, - "throw": { before: true, after: true }, - } }], - "no-extra-semi": "off", - "@typescript-eslint/no-extra-semi": ["error"], - "no-loss-of-precision": "off", - "@typescript-eslint/no-loss-of-precision": ["warn"], - "linebreak-style": ["warn", "unix"], - "new-parens": ["warn", "always"], - "newline-per-chained-call": ["warn", { ignoreChainWithDepth: 3 }], - "no-tabs": ["warn", { allowIndentationTabs: true }], - "no-trailing-spaces": ["warn", { skipBlankLines: true }], - "no-whitespace-before-property": ["warn"], - "object-curly-newline": ["warn", { consistent: true }], - //"object-curly-spacing": "off", - //"@typescript-eslint/object-curly-spacing": ["warn", "always"], - "operator-linebreak": ["warn", "before", { overrides: { ":": "after", "=": "after" } }], - "quotes": "off", - "@typescript-eslint/quotes": ["warn", "double", { avoidEscape: true, allowTemplateLiterals: true }], - "require-await": "off", - "@typescript-eslint/require-await": ["warn"], - "rest-spread-spacing": ["warn", "never"], - "semi": "off", - "@typescript-eslint/semi": ["warn", "always"], - "semi-style": ["warn", "last"], - "space-before-blocks": "off", - "space-before-function-paren": "off", - "@typescript-eslint/space-before-function-paren": ["warn", { named: "never" }], - "space-in-parens": ["warn", "never"], - "space-unary-ops": ["warn", { words: true, nonwords: false }], - "switch-colon-spacing": ["warn", { before: false, after: true }], - "template-curly-spacing": ["warn", "never"], - "template-tag-spacing": ["warn", "never"], - }, - }, - ], + parser: '@typescript-eslint/parser' + } + } + ] }; diff --git a/.example.env b/.example.env new file mode 100644 index 0000000000000000000000000000000000000000..b26bc52f43d4d99cc179b8a38892dba750f2adf6 --- /dev/null +++ b/.example.env @@ -0,0 +1,16 @@ +POSTGRES_USER=postgres +POSTGRES_PASSWORD=postgres +POSTGRES_DB=postgres +POSTGRES_HOST=localhost +POSTGRES_PORT=5432 + +AUTH_SECRET= # some random key, e.g. openssl rand -base64 32 +KEYCLOAK_SCOPES=openid profile email +KEYCLOAK_ISSUER=https://keycloak.example.com/realms/esag +KEYCLOAK_ID= # Add your client id here +KEYCLOAK_SECRET= # Add your secret here +REQUIRED_GROUP=esag + +MAIL_HOST=mail.example.com +MAIL_PORT=25 +MAIL_FROM=esag@example.com diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000000000000000000000000000000000..f33a02cd16e48a11403f8a847d5f2fd282e1559a --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for more information: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates +# https://containers.dev/guide/dependabot + +version: 2 +updates: + - package-ecosystem: "devcontainers" + directory: "/" + schedule: + interval: weekly diff --git a/.gitignore b/.gitignore index 510be0a1f0f827bdca8b97f5c2db4ea259bb0987..31bc78031052e6b3dd0acecbc5fe5a0c877665de 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,9 @@ node_modules !.env.example vite.config.js.timestamp-* vite.config.ts.timestamp-* - -# generated images based on the flyer PDFs -#static/flyer/*.jpg -#static/flyer/data.json +.devcontainer +static/flyer +static/stundenplaene +static/images +#!static/stundenplaene/branding.png +src/lib/server/fonts diff --git a/.npmrc b/.npmrc index 0c05da457e450c0a6fafe36006e17fa39abc899b..b6f27f135954640c8cc5bfd7b8c9922ca6eb2aad 100644 --- a/.npmrc +++ b/.npmrc @@ -1,2 +1 @@ engine-strict=true -resolution-mode=highest diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index 38972655faff07d2cc0383044bbf9f43b22c2248..0000000000000000000000000000000000000000 --- a/.prettierignore +++ /dev/null @@ -1,13 +0,0 @@ -.DS_Store -node_modules -/build -/.svelte-kit -/package -.env -.env.* -!.env.example - -# Ignore files for PNPM, NPM and YARN -pnpm-lock.yaml -package-lock.json -yarn.lock diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index a77fddea90975988d17a7e8b2f61720939a947f5..0000000000000000000000000000000000000000 --- a/.prettierrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "useTabs": true, - "singleQuote": true, - "trailingComma": "none", - "printWidth": 100, - "plugins": ["prettier-plugin-svelte"], - "pluginSearchDirs": ["."], - "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] -} diff --git a/.typesafe-i18n.json b/.typesafe-i18n.json deleted file mode 100644 index 08dde285ed12661c9d67512a49ce65ea7399b117..0000000000000000000000000000000000000000 --- a/.typesafe-i18n.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "adapter": "svelte", - "$schema": "https://unpkg.com/typesafe-i18n@5.26.2/schema/typesafe-i18n.json", - "outputPath": "./src/lib/i18n/", - "baseLocale": "de" -} diff --git a/Containerfile b/Containerfile deleted file mode 100644 index 0189855dd6910a3a83e89c9853646f079b7c0861..0000000000000000000000000000000000000000 --- a/Containerfile +++ /dev/null @@ -1,11 +0,0 @@ -FROM alpine:latest - -RUN apk update && apk upgrade && apk add git nodejs-current npm pkgconf python3 pixman-dev cairo-dev pango-dev make g++ -WORKDIR /app -RUN git clone --branch master https://git.fsmpi.rwth-aachen.de/aaron/esa-temp-copy . -RUN npm i -RUN npm audit fix -RUN npm run build - -EXPOSE 3000 -CMD ["/bin/sh", "-c", "git pull && npm i && npm audit fix && npm run build && node /app/build/index.js"] diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..474988fe7a2eacbea6dcebdaaf59f6937902430c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,12 @@ +FROM node:21-alpine + +WORKDIR /app +COPY package*.json ./ +COPY .env ./ +RUN npm i --legacy-peer-deps + +COPY . . +RUN npm run build + +EXPOSE 3000 +CMD ["node", "build/index.js"] diff --git a/README.md b/README.md index 5c91169b0ca6508bb24301c957a9edea5abf2b01..5ce676612ebf910f459f7d5e3e4b2847d0c1bfbb 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # create-svelte -Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte). +Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/main/packages/create-svelte). ## Creating a project diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..1c4033c9143cb80a2bbc4329320f9056803ca98b --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,31 @@ +version: 3.8 + +services: + web: + build: . + ports: + - 3000:3000 + restart: always + env_file: + - .env + environment: + - NODE_ENV=production + - TZ=Europe/Berlin + depends_on: + db: + condition: service_healthy + + db: + image: postgres:16-alpine + restart: always + volumes: + - ./pgdata:/var/lib/postgresql/data + environment: + - POSTGRES_DB + - POSTGRES_USER + - POSTGRES_PASSWORD + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] + interval: 1s + timeout: 5s + retries: 10 diff --git a/package-lock.json b/package-lock.json index b2cab9685722ed86bd5f16b185c50e37862435f6..5406a788a1f23dd4b28588d165eeabc9b124830e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6106 +1,8035 @@ -{ - "name": "esa-website", - "version": "0.0.1", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "esa-website", - "version": "0.0.1", - "dependencies": { - "@sveltejs/adapter-node": "^1.3.1", - "@sveltejs/adapter-static": "^2.0.3", - "canvas2pdf": "github:joshua-gould/canvas2pdf", - "classnames": "^2.3.2", - "flowbite-svelte": "^0.44.4", - "intl": "^1.2.5", - "leaflet": "^1.9.4" - }, - "devDependencies": { - "@sveltejs/kit": "^1.20.4", - "@types/geojson": "^7946.0.11", - "@types/intl": "^1.2.0", - "@types/leaflet": "^1.9.6", - "@types/node": "^20.8.0", - "@typescript-eslint/eslint-plugin": "^6.7.3", - "@typescript-eslint/parser": "^6.7.3", - "autoprefixer": "^10.4.14", - "canvas": "^2.11.2", - "eslint": "^8.28.0", - "eslint-config-prettier": "^8.5.0", - "eslint-plugin-svelte": "^2.30.0", - "flowbite-svelte-icons": "^0.3.6", - "npm-run-all": "^4.1.5", - "pdfjs-dist": "^3.10.111", - "postcss": "^8.4.24", - "postcss-load-config": "^4.0.1", - "prettier": "^2.8.0", - "prettier-plugin-svelte": "^2.10.1", - "prisma": "^5.2.0", - "svelte": "^4.0.0", - "svelte-check": "^3.4.3", - "svelte-preprocess": "^5.0.3", - "tailwindcss": "^3.3.2", - "typesafe-i18n": "^5.24.3", - "typescript": "^5.0.0", - "vite": "^4.3.0", - "vite-plugin-watch-and-run": "^1.1.3" - } - }, - "node_modules/@aashutoshrathi/word-wrap": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", - "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@alloc/quick-lru": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", - "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", - "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", - "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", - "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", - "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", - "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", - "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", - "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", - "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", - "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", - "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", - "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", - "cpu": [ - "ia32" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", - "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", - "cpu": [ - "loong64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", - "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", - "cpu": [ - "mips64el" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", - "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", - "cpu": [ - "ppc64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", - "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", - "cpu": [ - "riscv64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", - "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", - "cpu": [ - "s390x" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", - "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", - "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", - "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", - "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", - "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", - "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", - "cpu": [ - "ia32" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", - "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.8.2", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.8.2.tgz", - "integrity": "sha512-0MGxAVt1m/ZK+LTJp/j0qF7Hz97D9O/FH9Ms3ltnyIdDD57cbb1ACIQTkbHvNXtWDv5TPq7w5Kq56+cNukbo7g==", - "dev": true, - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", - "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", - "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/js": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.50.0.tgz", - "integrity": "sha512-NCC3zz2+nvYd+Ckfh87rA47zfu2QsQpvc6k1yzTk+b9KzRj0wkGa8LSoGOXN6Zv4lRf/EIoZ80biDh9HOI+RNQ==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@floating-ui/core": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.5.0.tgz", - "integrity": "sha512-kK1h4m36DQ0UHGj5Ah4db7R0rHemTqqO0QLvUqi1/mUUp3LuAWbWxdxSIf/XsnH9VS6rRVPLJCncjRzUvyCLXg==", - "dependencies": { - "@floating-ui/utils": "^0.1.3" - } - }, - "node_modules/@floating-ui/dom": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.3.tgz", - "integrity": "sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA==", - "dependencies": { - "@floating-ui/core": "^1.4.2", - "@floating-ui/utils": "^0.1.3" - } - }, - "node_modules/@floating-ui/utils": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.4.tgz", - "integrity": "sha512-qprfWkn82Iw821mcKofJ5Pk9wgioHicxcQMxx+5zt5GSKoqdWvgG5AxVmpmUUjzTLPVSH5auBrhI93Deayn/DA==" - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.11", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", - "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", - "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", - "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.19", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", - "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@kitql/helper": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@kitql/helper/-/helper-0.6.1.tgz", - "integrity": "sha512-jHl1YHItwOI8Z0h4kvx5LaJjcjY4zbmgSZVajWaGanCmbBKYphVP3UoNHDhs5944Ar7fJ/L7MNSdIINBhurIOA==", - "dev": true, - "dependencies": { - "safe-stable-stringify": "^2.4.2" - } - }, - "node_modules/@mapbox/node-pre-gyp": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", - "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", - "dev": true, - "dependencies": { - "detect-libc": "^2.0.0", - "https-proxy-agent": "^5.0.0", - "make-dir": "^3.1.0", - "node-fetch": "^2.6.7", - "nopt": "^5.0.0", - "npmlog": "^5.0.1", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "tar": "^6.1.11" - }, - "bin": { - "node-pre-gyp": "bin/node-pre-gyp" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@polka/url": { - "version": "1.0.0-next.23", - "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.23.tgz", - "integrity": "sha512-C16M+IYz0rgRhWZdCmK+h58JMv8vijAA61gmz2rspCSwKwzBebpdcsiUmwrtJRdphuY30i6BSLEOP8ppbNLyLg==" - }, - "node_modules/@popperjs/core": { - "version": "2.11.8", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", - "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/popperjs" - } - }, - "node_modules/@prisma/engines": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.3.1.tgz", - "integrity": "sha512-6QkILNyfeeN67BNEPEtkgh3Xo2tm6D7V+UhrkBbRHqKw9CTaz/vvTP/ROwYSP/3JT2MtIutZm/EnhxUiuOPVDA==", - "dev": true, - "hasInstallScript": true - }, - "node_modules/@rollup/plugin-commonjs": { - "version": "25.0.4", - "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-25.0.4.tgz", - "integrity": "sha512-L92Vz9WUZXDnlQQl3EwbypJR4+DM2EbsO+/KOcEkP4Mc6Ct453EeDB2uH9lgRwj4w5yflgNpq9pHOiY8aoUXBQ==", - "dependencies": { - "@rollup/pluginutils": "^5.0.1", - "commondir": "^1.0.1", - "estree-walker": "^2.0.2", - "glob": "^8.0.3", - "is-reference": "1.2.1", - "magic-string": "^0.27.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^2.68.0||^3.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/plugin-json": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.0.0.tgz", - "integrity": "sha512-i/4C5Jrdr1XUarRhVu27EEwjt4GObltD7c+MkCIpO2QIbojw8MUs+CCTqOphQi3Qtg1FLmYt+l+6YeoIf51J7w==", - "dependencies": { - "@rollup/pluginutils": "^5.0.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/plugin-node-resolve": { - "version": "15.2.1", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.1.tgz", - "integrity": "sha512-nsbUg588+GDSu8/NS8T4UAshO6xeaOfINNuXeVHcKV02LJtoRaM1SiOacClw4kws1SFiNhdLGxlbMY9ga/zs/w==", - "dependencies": { - "@rollup/pluginutils": "^5.0.1", - "@types/resolve": "1.20.2", - "deepmerge": "^4.2.2", - "is-builtin-module": "^3.2.1", - "is-module": "^1.0.0", - "resolve": "^1.22.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^2.78.0||^3.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/pluginutils": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.4.tgz", - "integrity": "sha512-0KJnIoRI8A+a1dqOYLxH8vBf8bphDmty5QvIm2hqm7oFCFYKCAZWWd2hXgMibaPsNDhI0AtpYfQZJG47pt/k4g==", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@sveltejs/adapter-node": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@sveltejs/adapter-node/-/adapter-node-1.3.1.tgz", - "integrity": "sha512-A0VgRQDCDPzdLNoiAbcOxGw4zT1Mc+n1LwT1OmO350R7WxrEqdMUChPPOd1iMfIDWlP4ie6E2d/WQf5es2d4Zw==", - "dependencies": { - "@rollup/plugin-commonjs": "^25.0.0", - "@rollup/plugin-json": "^6.0.0", - "@rollup/plugin-node-resolve": "^15.0.1", - "rollup": "^3.7.0" - }, - "peerDependencies": { - "@sveltejs/kit": "^1.0.0" - } - }, - "node_modules/@sveltejs/adapter-static": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-2.0.3.tgz", - "integrity": "sha512-VUqTfXsxYGugCpMqQv1U0LIdbR3S5nBkMMDmpjGVJyM6Q2jHVMFtdWJCkeHMySc6mZxJ+0eZK3T7IgmUCDrcUQ==", - "peerDependencies": { - "@sveltejs/kit": "^1.5.0" - } - }, - "node_modules/@sveltejs/kit": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-1.25.1.tgz", - "integrity": "sha512-pD8XsvNJNgTNkFngNlM60my/X8dXWPKVzN5RghEQr0NjGZmuCjy49AfFu2cGbZjNf5pBcqd2RCNMW912P5fkhA==", - "hasInstallScript": true, - "dependencies": { - "@sveltejs/vite-plugin-svelte": "^2.4.1", - "@types/cookie": "^0.5.1", - "cookie": "^0.5.0", - "devalue": "^4.3.1", - "esm-env": "^1.0.0", - "kleur": "^4.1.5", - "magic-string": "^0.30.0", - "mime": "^3.0.0", - "sade": "^1.8.1", - "set-cookie-parser": "^2.6.0", - "sirv": "^2.0.2", - "tiny-glob": "^0.2.9", - "undici": "~5.25.0" - }, - "bin": { - "svelte-kit": "svelte-kit.js" - }, - "engines": { - "node": "^16.14 || >=18" - }, - "peerDependencies": { - "svelte": "^3.54.0 || ^4.0.0-next.0", - "vite": "^4.0.0" - } - }, - "node_modules/@sveltejs/kit/node_modules/magic-string": { - "version": "0.30.3", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.3.tgz", - "integrity": "sha512-B7xGbll2fG/VjP+SWg4sX3JynwIU0mjoTc6MPpKNuIvftk6u6vqhDnk1R80b8C2GBR6ywqy+1DcKBrevBg+bmw==", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@sveltejs/vite-plugin-svelte": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-2.4.6.tgz", - "integrity": "sha512-zO79p0+DZnXPnF0ltIigWDx/ux7Ni+HRaFOw720Qeivc1azFUrJxTl0OryXVibYNx1hCboGia1NRV3x8RNv4cA==", - "dependencies": { - "@sveltejs/vite-plugin-svelte-inspector": "^1.0.4", - "debug": "^4.3.4", - "deepmerge": "^4.3.1", - "kleur": "^4.1.5", - "magic-string": "^0.30.3", - "svelte-hmr": "^0.15.3", - "vitefu": "^0.2.4" - }, - "engines": { - "node": "^14.18.0 || >= 16" - }, - "peerDependencies": { - "svelte": "^3.54.0 || ^4.0.0", - "vite": "^4.0.0" - } - }, - "node_modules/@sveltejs/vite-plugin-svelte-inspector": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-1.0.4.tgz", - "integrity": "sha512-zjiuZ3yydBtwpF3bj0kQNV0YXe+iKE545QGZVTaylW3eAzFr+pJ/cwK8lZEaRp4JtaJXhD5DyWAV4AxLh6DgaQ==", - "dependencies": { - "debug": "^4.3.4" - }, - "engines": { - "node": "^14.18.0 || >= 16" - }, - "peerDependencies": { - "@sveltejs/vite-plugin-svelte": "^2.2.0", - "svelte": "^3.54.0 || ^4.0.0", - "vite": "^4.0.0" - } - }, - "node_modules/@sveltejs/vite-plugin-svelte/node_modules/magic-string": { - "version": "0.30.3", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.3.tgz", - "integrity": "sha512-B7xGbll2fG/VjP+SWg4sX3JynwIU0mjoTc6MPpKNuIvftk6u6vqhDnk1R80b8C2GBR6ywqy+1DcKBrevBg+bmw==", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@swc/helpers": { - "version": "0.3.17", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.3.17.tgz", - "integrity": "sha512-tb7Iu+oZ+zWJZ3HJqwx8oNwSDIU440hmVMDPhpACWQWnrZHK99Bxs70gT1L2dnr5Hg50ZRWEFkQCAnOVVV0z1Q==", - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@types/cookie": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.5.2.tgz", - "integrity": "sha512-DBpRoJGKJZn7RY92dPrgoMew8xCWc2P71beqsjyhEI/Ds9mOyVmBwtekyfhpwFIVt1WrxTonFifiOZ62V8CnNA==" - }, - "node_modules/@types/estree": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.2.tgz", - "integrity": "sha512-VeiPZ9MMwXjO32/Xu7+OwflfmeoRwkE/qzndw42gGtgJwZopBnzy2gD//NN1+go1mADzkDcqf/KnFRSjTJ8xJA==" - }, - "node_modules/@types/geojson": { - "version": "7946.0.11", - "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.11.tgz", - "integrity": "sha512-L7A0AINMXQpVwxHJ4jxD6/XjZ4NDufaRlUJHjNIFKYUFBH1SvOW+neaqb0VTRSLW5suSrSu19ObFEFnfNcr+qg==", - "dev": true - }, - "node_modules/@types/intl": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@types/intl/-/intl-1.2.0.tgz", - "integrity": "sha512-BP+KwmOvD9AR5aoxnbyyPr3fAtpjEI/bVImHsotmpuC43+z0NAmjJ9cQbX7vPCq8XcvCeAVc8E3KSQPYNaPsUQ==", - "dev": true - }, - "node_modules/@types/json-schema": { - "version": "7.0.13", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.13.tgz", - "integrity": "sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ==", - "dev": true - }, - "node_modules/@types/leaflet": { - "version": "1.9.6", - "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.6.tgz", - "integrity": "sha512-HakGTK5LBBWegNWsAmTlG55zN1zszYec7aG47/z6SzT90bW2vqjmbqk3YKAbrtveO+G7fSTKTYqVbIwAFnTrbg==", - "dev": true, - "dependencies": { - "@types/geojson": "*" - } - }, - "node_modules/@types/node": { - "version": "20.8.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.0.tgz", - "integrity": "sha512-LzcWltT83s1bthcvjBmiBvGJiiUe84NWRHkw+ZV6Fr41z2FbIzvc815dk2nQ3RAKMuN2fkenM/z3Xv2QzEpYxQ==", - "devOptional": true - }, - "node_modules/@types/pug": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.7.tgz", - "integrity": "sha512-I469DU0UXNC1aHepwirWhu9YKg5fkxohZD95Ey/5A7lovC+Siu+MCLffva87lnfThaOrw9Vb1DUN5t55oULAAw==", - "dev": true - }, - "node_modules/@types/resolve": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", - "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==" - }, - "node_modules/@types/semver": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.3.tgz", - "integrity": "sha512-OxepLK9EuNEIPxWNME+C6WwbRAOOI2o2BaQEGzz5Lu2e4Z5eDnEo+/aVEDMIXywoJitJ7xWd641wrGLZdtwRyw==", - "dev": true - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.7.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.7.3.tgz", - "integrity": "sha512-vntq452UHNltxsaaN+L9WyuMch8bMd9CqJ3zhzTPXXidwbf5mqqKCVXEuvRZUqLJSTLeWE65lQwyXsRGnXkCTA==", - "dev": true, - "dependencies": { - "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.7.3", - "@typescript-eslint/type-utils": "6.7.3", - "@typescript-eslint/utils": "6.7.3", - "@typescript-eslint/visitor-keys": "6.7.3", - "debug": "^4.3.4", - "graphemer": "^1.4.0", - "ignore": "^5.2.4", - "natural-compare": "^1.4.0", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", - "eslint": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "6.7.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.7.3.tgz", - "integrity": "sha512-TlutE+iep2o7R8Lf+yoer3zU6/0EAUc8QIBB3GYBc1KGz4c4TRm83xwXUZVPlZ6YCLss4r77jbu6j3sendJoiQ==", - "dev": true, - "dependencies": { - "@typescript-eslint/scope-manager": "6.7.3", - "@typescript-eslint/types": "6.7.3", - "@typescript-eslint/typescript-estree": "6.7.3", - "@typescript-eslint/visitor-keys": "6.7.3", - "debug": "^4.3.4" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "6.7.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.7.3.tgz", - "integrity": "sha512-wOlo0QnEou9cHO2TdkJmzF7DFGvAKEnB82PuPNHpT8ZKKaZu6Bm63ugOTn9fXNJtvuDPanBc78lGUGGytJoVzQ==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "6.7.3", - "@typescript-eslint/visitor-keys": "6.7.3" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "6.7.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.7.3.tgz", - "integrity": "sha512-Fc68K0aTDrKIBvLnKTZ5Pf3MXK495YErrbHb1R6aTpfK5OdSFj0rVN7ib6Tx6ePrZ2gsjLqr0s98NG7l96KSQw==", - "dev": true, - "dependencies": { - "@typescript-eslint/typescript-estree": "6.7.3", - "@typescript-eslint/utils": "6.7.3", - "debug": "^4.3.4", - "ts-api-utils": "^1.0.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/types": { - "version": "6.7.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.7.3.tgz", - "integrity": "sha512-4g+de6roB2NFcfkZb439tigpAMnvEIg3rIjWQ+EM7IBaYt/CdJt6em9BJ4h4UpdgaBWdmx2iWsafHTrqmgIPNw==", - "dev": true, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.7.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.3.tgz", - "integrity": "sha512-YLQ3tJoS4VxLFYHTw21oe1/vIZPRqAO91z6Uv0Ss2BKm/Ag7/RVQBcXTGcXhgJMdA4U+HrKuY5gWlJlvoaKZ5g==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "6.7.3", - "@typescript-eslint/visitor-keys": "6.7.3", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "6.7.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.7.3.tgz", - "integrity": "sha512-vzLkVder21GpWRrmSR9JxGZ5+ibIUSudXlW52qeKpzUEQhRSmyZiVDDj3crAth7+5tmN1ulvgKaCU2f/bPRCzg==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.7.3", - "@typescript-eslint/types": "6.7.3", - "@typescript-eslint/typescript-estree": "6.7.3", - "semver": "^7.5.4" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.7.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.3.tgz", - "integrity": "sha512-HEVXkU9IB+nk9o63CeICMHxFWbHWr3E1mpilIQBe9+7L/lH97rleFLVtYsfnWB+JVMaiFnEaxvknvmIzX+CqVg==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "6.7.3", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@yr/monotone-cubic-spline": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz", - "integrity": "sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA==" - }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true - }, - "node_modules/acorn": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "dev": true - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/apexcharts": { - "version": "3.42.0", - "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.42.0.tgz", - "integrity": "sha512-hYhzZqh2Efny9uiutkGU2M/EarJ4Nn8s6dxZ0C7E7N+SV4d1xjTioXi2NLn4UKVJabZkb3HnpXDoumXgtAymwg==", - "dependencies": { - "@yr/monotone-cubic-spline": "^1.0.3", - "svg.draggable.js": "^2.2.2", - "svg.easing.js": "^2.0.0", - "svg.filter.js": "^2.0.2", - "svg.pathmorphing.js": "^0.1.3", - "svg.resize.js": "^1.4.3", - "svg.select.js": "^3.0.1" - } - }, - "node_modules/aproba": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", - "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", - "dev": true - }, - "node_modules/are-we-there-yet": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", - "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", - "dev": true, - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/arg": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", - "dev": true - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/aria-query": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", - "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", - "dependencies": { - "dequal": "^2.0.3" - } - }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", - "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", - "dependencies": { - "call-bind": "^1.0.2", - "is-array-buffer": "^3.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz", - "integrity": "sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==", - "dev": true, - "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1", - "is-array-buffer": "^3.0.2", - "is-shared-array-buffer": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/autoprefixer": { - "version": "10.4.16", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.16.tgz", - "integrity": "sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "browserslist": "^4.21.10", - "caniuse-lite": "^1.0.30001538", - "fraction.js": "^4.3.6", - "normalize-range": "^0.1.2", - "picocolors": "^1.0.0", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/axobject-query": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz", - "integrity": "sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==", - "dependencies": { - "dequal": "^2.0.3" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/blob": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.4.tgz", - "integrity": "sha512-YRc9zvVz4wNaxcXmiSgb9LAg7YYwqQ2xd0Sj6osfA7k/PKmIGVlnOYs3wOFdkRC9/JpQu8sGt/zHgJV7xzerfg==" - }, - "node_modules/blob-stream": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/blob-stream/-/blob-stream-0.1.3.tgz", - "integrity": "sha512-xXwyhgVmPsFVFFvtM5P0syI17/oae+MIjLn5jGhuD86mmSJ61EWMWmbPrV/0+bdcH9jQ2CzIhmTQKNUJL7IPog==", - "dependencies": { - "blob": "0.0.4" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/brotli": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz", - "integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==", - "dependencies": { - "base64-js": "^1.1.2" - } - }, - "node_modules/browserslist": { - "version": "4.21.11", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.11.tgz", - "integrity": "sha512-xn1UXOKUz7DjdGlg9RrUr0GGiWzI97UQJnugHtH0OLDfJB7jMgoIkYvRIEO1l9EeEERVqeqLYOcFBW9ldjypbQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "caniuse-lite": "^1.0.30001538", - "electron-to-chromium": "^1.4.526", - "node-releases": "^2.0.13", - "update-browserslist-db": "^1.0.13" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/builtin-modules": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", - "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/busboy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", - "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", - "dependencies": { - "streamsearch": "^1.1.0" - }, - "engines": { - "node": ">=10.16.0" - } - }, - "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase-css": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", - "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001539", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001539.tgz", - "integrity": "sha512-hfS5tE8bnNiNvEOEkm8HElUHroYwlqMMENEzELymy77+tJ6m+gA2krtHl5hxJaj71OlpC2cHZbdSMX1/YEqEkA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ] - }, - "node_modules/canvas": { - "version": "2.11.2", - "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.11.2.tgz", - "integrity": "sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "@mapbox/node-pre-gyp": "^1.0.0", - "nan": "^2.17.0", - "simple-get": "^3.0.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/canvas2pdf": { - "version": "1.1.0", - "resolved": "git+ssh://git@github.com/joshua-gould/canvas2pdf.git#c35f00f6a823f7a7c7b875757114c75daa39ed8f", - "license": "MIT", - "dependencies": { - "blob-stream": "^0.1.3", - "pdfkit": "^0.13.0" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/classnames": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", - "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" - }, - "node_modules/clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/code-red": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/code-red/-/code-red-1.0.4.tgz", - "integrity": "sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15", - "@types/estree": "^1.0.1", - "acorn": "^8.10.0", - "estree-walker": "^3.0.3", - "periscopic": "^3.1.0" - } - }, - "node_modules/code-red/node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "dev": true, - "bin": { - "color-support": "bin.js" - } - }, - "node_modules/commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "node_modules/console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", - "dev": true - }, - "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/crypto-js": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz", - "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==" - }, - "node_modules/css-tree": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", - "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", - "dependencies": { - "mdn-data": "2.0.30", - "source-map-js": "^1.0.1" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" - } - }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true, - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decompress-response": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", - "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", - "dev": true, - "dependencies": { - "mimic-response": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/deep-equal": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.2.tgz", - "integrity": "sha512-xjVyBf0w5vH0I42jdAZzOKVldmPgSulmiyPRywoyq7HXC9qdgo17kxJE+rdnif5Tz6+pIrpJI8dCpMNLIGkUiA==", - "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "call-bind": "^1.0.2", - "es-get-iterator": "^1.1.3", - "get-intrinsic": "^1.2.1", - "is-arguments": "^1.1.1", - "is-array-buffer": "^3.0.2", - "is-date-object": "^1.0.5", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "isarray": "^2.0.5", - "object-is": "^1.1.5", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.0", - "side-channel": "^1.0.4", - "which-boxed-primitive": "^1.0.2", - "which-collection": "^1.0.1", - "which-typed-array": "^1.1.9" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/define-data-property": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.0.tgz", - "integrity": "sha512-UzGwzcjyv3OtAvolTj1GoyNYzfFR+iqbGjcnBEENZVCpM4/Ng1yhGNvS3lR/xDS74Tb2wGG9WzNSNIOS9UVb2g==", - "dependencies": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", - "dev": true - }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/detect-indent": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", - "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/detect-libc": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", - "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/devalue": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/devalue/-/devalue-4.3.2.tgz", - "integrity": "sha512-KqFl6pOgOW+Y6wJgu80rHpo2/3H07vr8ntR9rkkFIRETewbf5GaYYcakYfiKz89K+sLsuPkQIZaXDMjUObZwWg==" - }, - "node_modules/dfa": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz", - "integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==" - }, - "node_modules/didyoumean": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", - "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", - "dev": true - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/dir-glob/node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/dlv": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", - "dev": true - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.4.528", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.528.tgz", - "integrity": "sha512-UdREXMXzLkREF4jA8t89FQjA8WHI6ssP38PMY4/4KhXFQbtImnghh4GkCgrtiZwLKUKVD2iTVXvDVQjfomEQuA==", - "dev": true - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es-abstract": { - "version": "1.22.2", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.2.tgz", - "integrity": "sha512-YoxfFcDmhjOgWPWsV13+2RNjq1F6UQnfs+8TftwNqtzlmFzEXvlUwdrNrYeaizfjQzRMxkZ6ElWMOJIFKdVqwA==", - "dev": true, - "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "arraybuffer.prototype.slice": "^1.0.2", - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "es-set-tostringtag": "^2.0.1", - "es-to-primitive": "^1.2.1", - "function.prototype.name": "^1.1.6", - "get-intrinsic": "^1.2.1", - "get-symbol-description": "^1.0.0", - "globalthis": "^1.0.3", - "gopd": "^1.0.1", - "has": "^1.0.3", - "has-property-descriptors": "^1.0.0", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.5", - "is-array-buffer": "^3.0.2", - "is-callable": "^1.2.7", - "is-negative-zero": "^2.0.2", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "is-string": "^1.0.7", - "is-typed-array": "^1.1.12", - "is-weakref": "^1.0.2", - "object-inspect": "^1.12.3", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.1", - "safe-array-concat": "^1.0.1", - "safe-regex-test": "^1.0.0", - "string.prototype.trim": "^1.2.8", - "string.prototype.trimend": "^1.0.7", - "string.prototype.trimstart": "^1.0.7", - "typed-array-buffer": "^1.0.0", - "typed-array-byte-length": "^1.0.0", - "typed-array-byte-offset": "^1.0.0", - "typed-array-length": "^1.0.4", - "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.11" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es-get-iterator": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", - "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "has-symbols": "^1.0.3", - "is-arguments": "^1.1.1", - "is-map": "^2.0.2", - "is-set": "^2.0.2", - "is-string": "^1.0.7", - "isarray": "^2.0.5", - "stop-iteration-iterator": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", - "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.3", - "has": "^1.0.3", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es6-promise": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", - "integrity": "sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==", - "dev": true - }, - "node_modules/esbuild": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", - "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/android-arm": "0.18.20", - "@esbuild/android-arm64": "0.18.20", - "@esbuild/android-x64": "0.18.20", - "@esbuild/darwin-arm64": "0.18.20", - "@esbuild/darwin-x64": "0.18.20", - "@esbuild/freebsd-arm64": "0.18.20", - "@esbuild/freebsd-x64": "0.18.20", - "@esbuild/linux-arm": "0.18.20", - "@esbuild/linux-arm64": "0.18.20", - "@esbuild/linux-ia32": "0.18.20", - "@esbuild/linux-loong64": "0.18.20", - "@esbuild/linux-mips64el": "0.18.20", - "@esbuild/linux-ppc64": "0.18.20", - "@esbuild/linux-riscv64": "0.18.20", - "@esbuild/linux-s390x": "0.18.20", - "@esbuild/linux-x64": "0.18.20", - "@esbuild/netbsd-x64": "0.18.20", - "@esbuild/openbsd-x64": "0.18.20", - "@esbuild/sunos-x64": "0.18.20", - "@esbuild/win32-arm64": "0.18.20", - "@esbuild/win32-ia32": "0.18.20", - "@esbuild/win32-x64": "0.18.20" - } - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.50.0.tgz", - "integrity": "sha512-FOnOGSuFuFLv/Sa+FDVRZl4GGVAAFFi8LecRsI5a1tMO5HIE8nCm4ivAlzt4dT3ol/PaaGC0rJEEXQmHJBGoOg==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.50.0", - "@humanwhocodes/config-array": "^0.11.11", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-config-prettier": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz", - "integrity": "sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg==", - "dev": true, - "bin": { - "eslint-config-prettier": "bin/cli.js" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, - "node_modules/eslint-plugin-svelte": { - "version": "2.33.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-2.33.2.tgz", - "integrity": "sha512-knWmauax+E/jvQ9CmuX5dAhQKP9P4eGQZxWa5RMutEJVCcy0wFmiUvOeDND2jR4vUkbDlX4khKjaceY7QzbkYw==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@jridgewell/sourcemap-codec": "^1.4.14", - "debug": "^4.3.1", - "esutils": "^2.0.3", - "known-css-properties": "^0.28.0", - "postcss": "^8.4.5", - "postcss-load-config": "^3.1.4", - "postcss-safe-parser": "^6.0.0", - "postcss-selector-parser": "^6.0.11", - "semver": "^7.5.3", - "svelte-eslint-parser": ">=0.33.0 <1.0.0" - }, - "engines": { - "node": "^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ota-meshi" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0-0", - "svelte": "^3.37.0 || ^4.0.0" - }, - "peerDependenciesMeta": { - "svelte": { - "optional": true - } - } - }, - "node_modules/eslint-plugin-svelte/node_modules/postcss-load-config": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", - "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", - "dev": true, - "dependencies": { - "lilconfig": "^2.0.5", - "yaml": "^1.10.2" - }, - "engines": { - "node": ">= 10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - "peerDependencies": { - "postcss": ">=8.0.9", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "postcss": { - "optional": true - }, - "ts-node": { - "optional": true - } - } - }, - "node_modules/eslint-plugin-svelte/node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esm-env": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.0.0.tgz", - "integrity": "sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA==" - }, - "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-glob": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", - "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.0.tgz", - "integrity": "sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew==", - "dev": true, - "dependencies": { - "flatted": "^3.2.7", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.2.9", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", - "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", - "dev": true - }, - "node_modules/flowbite": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/flowbite/-/flowbite-1.8.1.tgz", - "integrity": "sha512-lXTcO8a6dRTPFpINyOLcATCN/pK1Of/jY4PryklPllAiqH64tSDUsOdQpar3TO59ZXWwugm2e92oaqwH6X90Xg==", - "dependencies": { - "@popperjs/core": "^2.9.3", - "mini-svg-data-uri": "^1.4.3" - } - }, - "node_modules/flowbite-svelte": { - "version": "0.44.17", - "resolved": "https://registry.npmjs.org/flowbite-svelte/-/flowbite-svelte-0.44.17.tgz", - "integrity": "sha512-Ig7K/obOQ/6uwR2zkg0NT4u5dvcqAXcM0ZkMWVGBkfCD5refnvGsS2q6lflefN+cFxXaDi9J/Rorgp6KwP9JPw==", - "dependencies": { - "@floating-ui/dom": "^1.5.3", - "apexcharts": "^3.42.0", - "flowbite": "^1.8.1", - "tailwind-merge": "^1.14.0" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - }, - "peerDependencies": { - "svelte": "^4.0.0" - } - }, - "node_modules/flowbite-svelte-icons": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/flowbite-svelte-icons/-/flowbite-svelte-icons-0.3.6.tgz", - "integrity": "sha512-4YEq++cbD36KF+zGgLqfkmQgfWGMAP7tjDbesuieROx6UgbMBTtj7f4n49iO+g1cMLelGsCkyZiwelCXDbIJ2w==", - "dev": true, - "peerDependencies": { - "svelte": "^3.54.0 || ^4.0.0", - "tailwind-merge": "^1.13.2", - "tailwindcss": "^3.3.2" - } - }, - "node_modules/fontkit": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/fontkit/-/fontkit-1.9.0.tgz", - "integrity": "sha512-HkW/8Lrk8jl18kzQHvAw9aTHe1cqsyx5sDnxncx652+CIfhawokEPkeM3BoIC+z/Xv7a0yMr0f3pRRwhGH455g==", - "dependencies": { - "@swc/helpers": "^0.3.13", - "brotli": "^1.3.2", - "clone": "^2.1.2", - "deep-equal": "^2.0.5", - "dfa": "^1.2.0", - "restructure": "^2.0.1", - "tiny-inflate": "^1.0.3", - "unicode-properties": "^1.3.1", - "unicode-trie": "^2.0.0" - } - }, - "node_modules/for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dependencies": { - "is-callable": "^1.1.3" - } - }, - "node_modules/fraction.js": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.6.tgz", - "integrity": "sha512-n2aZ9tNfYDwaHhvFTkhFErqOMIb8uyzSQ+vGJBjZyanAKZVbGUQ1sngfk9FdkBw7G26O7AgNjLcecLffD1c7eg==", - "dev": true, - "engines": { - "node": "*" - }, - "funding": { - "type": "patreon", - "url": "https://github.com/sponsors/rawify" - } - }, - "node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" - }, - "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/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "node_modules/function.prototype.name": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", - "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "functions-have-names": "^1.2.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gauge": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", - "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", - "dev": true, - "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.2", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.1", - "object-assign": "^4.1.1", - "signal-exit": "^3.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/get-intrinsic": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", - "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", - "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/globals": { - "version": "13.22.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.22.0.tgz", - "integrity": "sha512-H1Ddc/PbZHTDVJSnj8kWptIRSD6AM3pK+mKytuIVF4uoBV7rshFlhhvA58ceJ5wp3Er58w6zj7bykMpYXt3ETw==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globalthis": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", - "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", - "dev": true, - "dependencies": { - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/globalyzer": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.0.tgz", - "integrity": "sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==" - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globrex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", - "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==" - }, - "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dependencies": { - "get-intrinsic": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", - "dependencies": { - "get-intrinsic": "^1.1.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", - "dev": true - }, - "node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, - "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/internal-slot": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", - "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", - "dependencies": { - "get-intrinsic": "^1.2.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/intl": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/intl/-/intl-1.2.5.tgz", - "integrity": "sha512-rK0KcPHeBFBcqsErKSpvZnrOmWOj+EmDkyJ57e90YWaQNqbcivcqmKDlHEeNprDWOsKzPsh1BfSpPQdDvclHVw==" - }, - "node_modules/is-arguments": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", - "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-array-buffer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", - "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.0", - "is-typed-array": "^1.1.10" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true - }, - "node_modules/is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dependencies": { - "has-bigints": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-builtin-module": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", - "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", - "dependencies": { - "builtin-modules": "^3.3.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-core-module": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", - "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", - "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", - "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==" - }, - "node_modules/is-negative-zero": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-reference": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", - "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", - "dependencies": { - "@types/estree": "*" - } - }, - "node_modules/is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-set": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", - "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", - "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-typed-array": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", - "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", - "dependencies": { - "which-typed-array": "^1.1.11" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakmap": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", - "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakset": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", - "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/jiti": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.20.0.tgz", - "integrity": "sha512-3TV69ZbrvV6U5DfQimop50jE9Dl6J8O1ja1dvBbMba/sZ3YBEQqJ2VZRoQPVnhlzjNtU1vaXRZVrVjU4qtm8yA==", - "dev": true, - "bin": { - "jiti": "bin/jiti.js" - } - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true - }, - "node_modules/json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "node_modules/keyv": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", - "integrity": "sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==", - "dev": true, - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/kleur": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", - "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/known-css-properties": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.28.0.tgz", - "integrity": "sha512-9pSL5XB4J+ifHP0e0jmmC98OGC1nL8/JjS+fi6mnTlIf//yt/MfVLtKg7S6nCtj/8KTcWX7nRlY0XywoYY1ISQ==", - "dev": true - }, - "node_modules/leaflet": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", - "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==" - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lilconfig": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", - "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/linebreak": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/linebreak/-/linebreak-1.1.0.tgz", - "integrity": "sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==", - "dependencies": { - "base64-js": "0.0.8", - "unicode-trie": "^2.0.0" - } - }, - "node_modules/linebreak/node_modules/base64-js": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz", - "integrity": "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true - }, - "node_modules/load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/locate-character": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", - "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==" - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/magic-string": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", - "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.13" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/mdn-data": { - "version": "2.0.30", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", - "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==" - }, - "node_modules/memorystream": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", - "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", - "dev": true, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", - "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/mimic-response": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", - "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/min-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/mini-svg-data-uri": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz", - "integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==", - "bin": { - "mini-svg-data-uri": "cli.js" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/mri": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", - "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", - "engines": { - "node": ">=4" - } - }, - "node_modules/mrmime": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz", - "integrity": "sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==", - "engines": { - "node": ">=10" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dev": true, - "dependencies": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" - } - }, - "node_modules/nan": { - "version": "2.18.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.18.0.tgz", - "integrity": "sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==", - "dev": true - }, - "node_modules/nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", - "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/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "node_modules/nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true - }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dev": true, - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/node-releases": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", - "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", - "dev": true - }, - "node_modules/nopt": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", - "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", - "dev": true, - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/normalize-package-data/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-all": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", - "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "chalk": "^2.4.1", - "cross-spawn": "^6.0.5", - "memorystream": "^0.3.1", - "minimatch": "^3.0.4", - "pidtree": "^0.3.0", - "read-pkg": "^3.0.0", - "shell-quote": "^1.6.1", - "string.prototype.padend": "^3.0.0" - }, - "bin": { - "npm-run-all": "bin/npm-run-all/index.js", - "run-p": "bin/run-p/index.js", - "run-s": "bin/run-s/index.js" - }, - "engines": { - "node": ">= 4" - } - }, - "node_modules/npm-run-all/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm-run-all/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm-run-all/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/npm-run-all/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/npm-run-all/node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "engines": { - "node": ">=4.8" - } - }, - "node_modules/npm-run-all/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/npm-run-all/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm-run-all/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm-run-all/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/npm-run-all/node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "dev": true, - "dependencies": { - "shebang-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-all/node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-all/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm-run-all/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/npmlog": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", - "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", - "dev": true, - "dependencies": { - "are-we-there-yet": "^2.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^3.0.0", - "set-blocking": "^2.0.0" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-hash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-is": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", - "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", - "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/optionator": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", - "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", - "dev": true, - "dependencies": { - "@aashutoshrathi/word-wrap": "^1.2.3", - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pako": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", - "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==" - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", - "dev": true, - "dependencies": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" - }, - "node_modules/path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "dev": true, - "dependencies": { - "pify": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/path2d-polyfill": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path2d-polyfill/-/path2d-polyfill-2.0.1.tgz", - "integrity": "sha512-ad/3bsalbbWhmBo0D6FZ4RNMwsLsPpL6gnvhuSaU5Vm7b06Kr5ubSltQQ0T7YKsiJQO+g22zJ4dJKNTXIyOXtA==", - "dev": true, - "optional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/pdfjs-dist": { - "version": "3.11.174", - "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-3.11.174.tgz", - "integrity": "sha512-TdTZPf1trZ8/UFu5Cx/GXB7GZM30LT+wWUNfsi6Bq8ePLnb+woNKtDymI2mxZYBpMbonNFqKmiz684DIfnd8dA==", - "dev": true, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "canvas": "^2.11.2", - "path2d-polyfill": "^2.0.1" - } - }, - "node_modules/pdfkit": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/pdfkit/-/pdfkit-0.13.0.tgz", - "integrity": "sha512-AW79eHU5eLd2vgRDS9z3bSoi0FA+gYm+100LLosrQQMLUzOBGVOhG7ABcMFpJu7Bpg+MT74XYHi4k9EuU/9EZw==", - "dependencies": { - "crypto-js": "^4.0.0", - "fontkit": "^1.8.1", - "linebreak": "^1.0.2", - "png-js": "^1.0.0" - } - }, - "node_modules/periscopic": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz", - "integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^3.0.0", - "is-reference": "^3.0.0" - } - }, - "node_modules/periscopic/node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "node_modules/periscopic/node_modules/is-reference": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz", - "integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==", - "dependencies": { - "@types/estree": "*" - } - }, - "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/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pidtree": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz", - "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==", - "dev": true, - "bin": { - "pidtree": "bin/pidtree.js" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/pirates": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", - "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/png-js": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/png-js/-/png-js-1.0.0.tgz", - "integrity": "sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g==" - }, - "node_modules/postcss": { - "version": "8.4.30", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.30.tgz", - "integrity": "sha512-7ZEao1g4kd68l97aWG/etQKPKq07us0ieSZ2TnFDk11i0ZfDW2AwKHYU8qv4MZKqN2fdBfg+7q0ES06UA73C1g==", - "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.6", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-import": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", - "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", - "dev": true, - "dependencies": { - "postcss-value-parser": "^4.0.0", - "read-cache": "^1.0.0", - "resolve": "^1.1.7" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "postcss": "^8.0.0" - } - }, - "node_modules/postcss-js": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", - "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", - "dev": true, - "dependencies": { - "camelcase-css": "^2.0.1" - }, - "engines": { - "node": "^12 || ^14 || >= 16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - "peerDependencies": { - "postcss": "^8.4.21" - } - }, - "node_modules/postcss-load-config": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.1.tgz", - "integrity": "sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==", - "dev": true, - "dependencies": { - "lilconfig": "^2.0.5", - "yaml": "^2.1.1" - }, - "engines": { - "node": ">= 14" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - "peerDependencies": { - "postcss": ">=8.0.9", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "postcss": { - "optional": true - }, - "ts-node": { - "optional": true - } - } - }, - "node_modules/postcss-nested": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", - "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==", - "dev": true, - "dependencies": { - "postcss-selector-parser": "^6.0.11" - }, - "engines": { - "node": ">=12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - "peerDependencies": { - "postcss": "^8.2.14" - } - }, - "node_modules/postcss-safe-parser": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz", - "integrity": "sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==", - "dev": true, - "engines": { - "node": ">=12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - "peerDependencies": { - "postcss": "^8.3.3" - } - }, - "node_modules/postcss-scss": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-4.0.8.tgz", - "integrity": "sha512-Cr0X8Eu7xMhE96PJck6ses/uVVXDtE5ghUTKNUYgm8ozgP2TkgV3LWs3WgLV1xaSSLq8ZFiXaUrj0LVgG1fGEA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss-scss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "engines": { - "node": ">=12.0" - }, - "peerDependencies": { - "postcss": "^8.4.29" - } - }, - "node_modules/postcss-selector-parser": { - "version": "6.0.13", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", - "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", - "dev": true, - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", - "dev": true, - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/prettier-plugin-svelte": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-2.10.1.tgz", - "integrity": "sha512-Wlq7Z5v2ueCubWo0TZzKc9XHcm7TDxqcuzRuGd0gcENfzfT4JZ9yDlCbEgxWgiPmLHkBjfOtpAWkcT28MCDpUQ==", - "dev": true, - "peerDependencies": { - "prettier": "^1.16.4 || ^2.0.0", - "svelte": "^3.2.0 || ^4.0.0-next.0" - } - }, - "node_modules/prisma": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.3.1.tgz", - "integrity": "sha512-Wp2msQIlMPHe+5k5Od6xnsI/WNG7UJGgFUJgqv/ygc7kOECZapcSz/iU4NIEzISs3H1W9sFLjAPbg/gOqqtB7A==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "@prisma/engines": "5.3.1" - }, - "bin": { - "prisma": "build/index.js" - }, - "engines": { - "node": ">=16.13" - } - }, - "node_modules/punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/read-cache": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", - "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", - "dev": true, - "dependencies": { - "pify": "^2.3.0" - } - }, - "node_modules/read-cache/node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==", - "dev": true, - "dependencies": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/regexp.prototype.flags": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz", - "integrity": "sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "set-function-name": "^2.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve": { - "version": "1.22.6", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.6.tgz", - "integrity": "sha512-njhxM7mV12JfufShqGy3Rz8j11RPdLy4xi15UurGJeoHLfJpVXKdh3ueuOqbYUcDZnffr6X739JBo5LzyahEsw==", - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/restructure": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/restructure/-/restructure-2.0.1.tgz", - "integrity": "sha512-e0dOpjm5DseomnXx2M5lpdZ5zoHqF1+bqdMJUohoYVVQa7cBdnk7fdmeI6byNWP/kiME72EeTiSypTCVnpLiDg==" - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rollup": { - "version": "3.29.3", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.3.tgz", - "integrity": "sha512-T7du6Hum8jOkSWetjRgbwpM6Sy0nECYrYRSmZjayFcOddtKJWU4d17AC3HNUk7HRuqy4p+G7aEZclSHytqUmEg==", - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=14.18.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/sade": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", - "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", - "dependencies": { - "mri": "^1.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/safe-array-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.1.tgz", - "integrity": "sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1", - "has-symbols": "^1.0.3", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">=0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/safe-regex-test": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", - "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "is-regex": "^1.1.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safe-stable-stringify": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", - "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/sander": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/sander/-/sander-0.5.1.tgz", - "integrity": "sha512-3lVqBir7WuKDHGrKRDn/1Ye3kwpXaDOMsiRP1wd6wpZW56gJhsbp5RqQpA6JG/P+pkXizygnr1dKR8vzWaVsfA==", - "dev": true, - "dependencies": { - "es6-promise": "^3.1.2", - "graceful-fs": "^4.1.3", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.2" - } - }, - "node_modules/sander/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/sander/node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true - }, - "node_modules/set-cookie-parser": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz", - "integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==" - }, - "node_modules/set-function-name": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz", - "integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==", - "dependencies": { - "define-data-property": "^1.0.1", - "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/shell-quote": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", - "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "node_modules/simple-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", - "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/simple-get": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz", - "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", - "dev": true, - "dependencies": { - "decompress-response": "^4.2.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - } - }, - "node_modules/sirv": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.3.tgz", - "integrity": "sha512-O9jm9BsID1P+0HOi81VpXPoDxYP374pkOLzACAoyUQ/3OUVndNpsz6wMnY2z+yOxzbllCKZrM+9QrWsv4THnyA==", - "dependencies": { - "@polka/url": "^1.0.0-next.20", - "mrmime": "^1.0.0", - "totalist": "^3.0.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/sorcery": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/sorcery/-/sorcery-0.11.0.tgz", - "integrity": "sha512-J69LQ22xrQB1cIFJhPfgtLuI6BpWRiWu1Y3vSsIwK/eAScqJxd/+CJlUuHQRdX2C9NGFamq+KqNywGgaThwfHw==", - "dev": true, - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.14", - "buffer-crc32": "^0.2.5", - "minimist": "^1.2.0", - "sander": "^0.5.0" - }, - "bin": { - "sorcery": "bin/sorcery" - } - }, - "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/spdx-correct": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", - "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", - "dev": true, - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.15", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.15.tgz", - "integrity": "sha512-lpT8hSQp9jAKp9mhtBU4Xjon8LPGBvLIuBiSVhMEtmLecTh2mO0tlqrAMp47tBXzMr13NJMQ2lf7RpQGLJ3HsQ==", - "dev": true - }, - "node_modules/stop-iteration-iterator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", - "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", - "dependencies": { - "internal-slot": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/streamsearch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", - "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string.prototype.padend": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.5.tgz", - "integrity": "sha512-DOB27b/2UTTD+4myKUFh+/fXWcu/UDyASIXfg+7VzoCNNGOfWvoyU/x5pvVHr++ztyt/oSYI1BcWBBG/hmlNjA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trim": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz", - "integrity": "sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz", - "integrity": "sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz", - "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", - "dev": true, - "dependencies": { - "min-indent": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/sucrase": { - "version": "3.34.0", - "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.34.0.tgz", - "integrity": "sha512-70/LQEZ07TEcxiU2dz51FKaE6hCTWC6vr7FOk3Gr0U60C3shtAN+H+BFr9XlYe5xqf3RA8nrc+VIwzCfnxuXJw==", - "dev": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.2", - "commander": "^4.0.0", - "glob": "7.1.6", - "lines-and-columns": "^1.1.6", - "mz": "^2.7.0", - "pirates": "^4.0.1", - "ts-interface-checker": "^0.1.9" - }, - "bin": { - "sucrase": "bin/sucrase", - "sucrase-node": "bin/sucrase-node" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/sucrase/node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/svelte": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.1.tgz", - "integrity": "sha512-LpLqY2Jr7cRxkrTc796/AaaoMLF/1ax7cto8Ot76wrvKQhrPmZ0JgajiWPmg9mTSDqO16SSLiD17r9MsvAPTmw==", - "dependencies": { - "@ampproject/remapping": "^2.2.1", - "@jridgewell/sourcemap-codec": "^1.4.15", - "@jridgewell/trace-mapping": "^0.3.18", - "acorn": "^8.9.0", - "aria-query": "^5.3.0", - "axobject-query": "^3.2.1", - "code-red": "^1.0.3", - "css-tree": "^2.3.1", - "estree-walker": "^3.0.3", - "is-reference": "^3.0.1", - "locate-character": "^3.0.0", - "magic-string": "^0.30.0", - "periscopic": "^3.1.0" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/svelte-check": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-3.5.2.tgz", - "integrity": "sha512-5a/YWbiH4c+AqAUP+0VneiV5bP8YOk9JL3jwvN+k2PEPLgpu85bjQc5eE67+eIZBBwUEJzmO3I92OqKcqbp3fw==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.17", - "chokidar": "^3.4.1", - "fast-glob": "^3.2.7", - "import-fresh": "^3.2.1", - "picocolors": "^1.0.0", - "sade": "^1.7.4", - "svelte-preprocess": "^5.0.4", - "typescript": "^5.0.3" - }, - "bin": { - "svelte-check": "bin/svelte-check" - }, - "peerDependencies": { - "svelte": "^3.55.0 || ^4.0.0-next.0 || ^4.0.0" - } - }, - "node_modules/svelte-eslint-parser": { - "version": "0.33.0", - "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-0.33.0.tgz", - "integrity": "sha512-5awZ6Bs+Tb/zQwa41PSdcLynAVQTwW0HGyCBjtbAQ59taLZqDgQSMzRlDmapjZdDtzERm0oXDZNE0E+PKJ6ryg==", - "dev": true, - "dependencies": { - "eslint-scope": "^7.0.0", - "eslint-visitor-keys": "^3.0.0", - "espree": "^9.0.0", - "postcss": "^8.4.28", - "postcss-scss": "^4.0.7" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ota-meshi" - }, - "peerDependencies": { - "svelte": "^3.37.0 || ^4.0.0" - }, - "peerDependenciesMeta": { - "svelte": { - "optional": true - } - } - }, - "node_modules/svelte-hmr": { - "version": "0.15.3", - "resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.15.3.tgz", - "integrity": "sha512-41snaPswvSf8TJUhlkoJBekRrABDXDMdpNpT2tfHIv4JuhgvHqLMhEPGtaQn0BmbNSTkuz2Ed20DF2eHw0SmBQ==", - "engines": { - "node": "^12.20 || ^14.13.1 || >= 16" - }, - "peerDependencies": { - "svelte": "^3.19.0 || ^4.0.0" - } - }, - "node_modules/svelte-preprocess": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.0.4.tgz", - "integrity": "sha512-ABia2QegosxOGsVlsSBJvoWeXy1wUKSfF7SWJdTjLAbx/Y3SrVevvvbFNQqrSJw89+lNSsM58SipmZJ5SRi5iw==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "@types/pug": "^2.0.6", - "detect-indent": "^6.1.0", - "magic-string": "^0.27.0", - "sorcery": "^0.11.0", - "strip-indent": "^3.0.0" - }, - "engines": { - "node": ">= 14.10.0" - }, - "peerDependencies": { - "@babel/core": "^7.10.2", - "coffeescript": "^2.5.1", - "less": "^3.11.3 || ^4.0.0", - "postcss": "^7 || ^8", - "postcss-load-config": "^2.1.0 || ^3.0.0 || ^4.0.0", - "pug": "^3.0.0", - "sass": "^1.26.8", - "stylus": "^0.55.0", - "sugarss": "^2.0.0 || ^3.0.0 || ^4.0.0", - "svelte": "^3.23.0 || ^4.0.0-next.0 || ^4.0.0", - "typescript": ">=3.9.5 || ^4.0.0 || ^5.0.0" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "coffeescript": { - "optional": true - }, - "less": { - "optional": true - }, - "postcss": { - "optional": true - }, - "postcss-load-config": { - "optional": true - }, - "pug": { - "optional": true - }, - "sass": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "typescript": { - "optional": true - } - } - }, - "node_modules/svelte/node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "node_modules/svelte/node_modules/is-reference": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz", - "integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==", - "dependencies": { - "@types/estree": "*" - } - }, - "node_modules/svelte/node_modules/magic-string": { - "version": "0.30.3", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.3.tgz", - "integrity": "sha512-B7xGbll2fG/VjP+SWg4sX3JynwIU0mjoTc6MPpKNuIvftk6u6vqhDnk1R80b8C2GBR6ywqy+1DcKBrevBg+bmw==", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/svg.draggable.js": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz", - "integrity": "sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw==", - "dependencies": { - "svg.js": "^2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/svg.easing.js": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/svg.easing.js/-/svg.easing.js-2.0.0.tgz", - "integrity": "sha512-//ctPdJMGy22YoYGV+3HEfHbm6/69LJUTAqI2/5qBvaNHZ9uUFVC82B0Pl299HzgH13rKrBgi4+XyXXyVWWthA==", - "dependencies": { - "svg.js": ">=2.3.x" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/svg.filter.js": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/svg.filter.js/-/svg.filter.js-2.0.2.tgz", - "integrity": "sha512-xkGBwU+dKBzqg5PtilaTb0EYPqPfJ9Q6saVldX+5vCRy31P6TlRCP3U9NxH3HEufkKkpNgdTLBJnmhDHeTqAkw==", - "dependencies": { - "svg.js": "^2.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/svg.js": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/svg.js/-/svg.js-2.7.1.tgz", - "integrity": "sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA==" - }, - "node_modules/svg.pathmorphing.js": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/svg.pathmorphing.js/-/svg.pathmorphing.js-0.1.3.tgz", - "integrity": "sha512-49HWI9X4XQR/JG1qXkSDV8xViuTLIWm/B/7YuQELV5KMOPtXjiwH4XPJvr/ghEDibmLQ9Oc22dpWpG0vUDDNww==", - "dependencies": { - "svg.js": "^2.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/svg.resize.js": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/svg.resize.js/-/svg.resize.js-1.4.3.tgz", - "integrity": "sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw==", - "dependencies": { - "svg.js": "^2.6.5", - "svg.select.js": "^2.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/svg.resize.js/node_modules/svg.select.js": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-2.1.2.tgz", - "integrity": "sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ==", - "dependencies": { - "svg.js": "^2.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/svg.select.js": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-3.0.1.tgz", - "integrity": "sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw==", - "dependencies": { - "svg.js": "^2.6.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/tailwind-merge": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-1.14.0.tgz", - "integrity": "sha512-3mFKyCo/MBcgyOTlrY8T7odzZFx+w+qKSMAmdFzRvqBfLlSigU6TZnlFHK0lkMwj9Bj8OYU+9yW9lmGuS0QEnQ==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/dcastil" - } - }, - "node_modules/tailwindcss": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.3.tgz", - "integrity": "sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w==", - "dev": true, - "dependencies": { - "@alloc/quick-lru": "^5.2.0", - "arg": "^5.0.2", - "chokidar": "^3.5.3", - "didyoumean": "^1.2.2", - "dlv": "^1.1.3", - "fast-glob": "^3.2.12", - "glob-parent": "^6.0.2", - "is-glob": "^4.0.3", - "jiti": "^1.18.2", - "lilconfig": "^2.1.0", - "micromatch": "^4.0.5", - "normalize-path": "^3.0.0", - "object-hash": "^3.0.0", - "picocolors": "^1.0.0", - "postcss": "^8.4.23", - "postcss-import": "^15.1.0", - "postcss-js": "^4.0.1", - "postcss-load-config": "^4.0.1", - "postcss-nested": "^6.0.1", - "postcss-selector-parser": "^6.0.11", - "resolve": "^1.22.2", - "sucrase": "^3.32.0" - }, - "bin": { - "tailwind": "lib/cli.js", - "tailwindcss": "lib/cli.js" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tar": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", - "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", - "dev": true, - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/tar/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, - "node_modules/thenify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", - "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "dev": true, - "dependencies": { - "any-promise": "^1.0.0" - } - }, - "node_modules/thenify-all": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "dev": true, - "dependencies": { - "thenify": ">= 3.1.0 < 4" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/tiny-glob": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz", - "integrity": "sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==", - "dependencies": { - "globalyzer": "0.1.0", - "globrex": "^0.1.2" - } - }, - "node_modules/tiny-inflate": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", - "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==" - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/totalist": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", - "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true - }, - "node_modules/ts-api-utils": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", - "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", - "dev": true, - "engines": { - "node": ">=16.13.0" - }, - "peerDependencies": { - "typescript": ">=4.2.0" - } - }, - "node_modules/ts-interface-checker": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", - "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", - "dev": true - }, - "node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typed-array-buffer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", - "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1", - "is-typed-array": "^1.1.10" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/typed-array-byte-length": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", - "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "has-proto": "^1.0.1", - "is-typed-array": "^1.1.10" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-byte-offset": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", - "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", - "dev": true, - "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "has-proto": "^1.0.1", - "is-typed-array": "^1.1.10" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-length": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", - "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "is-typed-array": "^1.1.9" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typesafe-i18n": { - "version": "5.26.2", - "resolved": "https://registry.npmjs.org/typesafe-i18n/-/typesafe-i18n-5.26.2.tgz", - "integrity": "sha512-2QAriFmiY5JwUAJtG7yufoE/XZ1aFBY++wj7YFS2yo89a3jLBfKoWSdq5JfQYk1V2BS7V2c/u+KEcaCQoE65hw==", - "dev": true, - "bin": { - "typesafe-i18n": "cli/typesafe-i18n.mjs" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ivanhofer" - }, - "peerDependencies": { - "typescript": ">=3.5.1" - } - }, - "node_modules/typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/unbox-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/undici": { - "version": "5.25.2", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.25.2.tgz", - "integrity": "sha512-tch8RbCfn1UUH1PeVCXva4V8gDpGAud/w0WubD6sHC46vYQ3KDxL+xv1A2UxK0N6jrVedutuPHxe1XIoqerwMw==", - "dependencies": { - "busboy": "^1.6.0" - }, - "engines": { - "node": ">=14.0" - } - }, - "node_modules/unicode-properties": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz", - "integrity": "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==", - "dependencies": { - "base64-js": "^1.3.0", - "unicode-trie": "^2.0.0" - } - }, - "node_modules/unicode-trie": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz", - "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==", - "dependencies": { - "pako": "^0.2.5", - "tiny-inflate": "^1.0.0" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", - "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true - }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "node_modules/vite": { - "version": "4.4.9", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz", - "integrity": "sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==", - "dependencies": { - "esbuild": "^0.18.10", - "postcss": "^8.4.27", - "rollup": "^3.27.1" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - }, - "peerDependencies": { - "@types/node": ">= 14", - "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 - } - } - }, - "node_modules/vite-plugin-watch-and-run": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/vite-plugin-watch-and-run/-/vite-plugin-watch-and-run-1.1.3.tgz", - "integrity": "sha512-wxC2ptj17GmOEvSKp786AeYJvUoqaKqcRLRA+PdlU1RkVdpY+A6gx+As5s9d0KgZXYn0ZVM4qZabeWpVAwqK4A==", - "dev": true, - "dependencies": { - "@kitql/helper": "0.6.1", - "micromatch": "4.0.5" - } - }, - "node_modules/vitefu": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-0.2.4.tgz", - "integrity": "sha512-fanAXjSaf9xXtOOeno8wZXIhgia+CZury481LsDaV++lSvcU2R9Ch2bPh3PYFyoHW+w9LqAeYRISVQjUIew14g==", - "peerDependencies": { - "vite": "^3.0.0 || ^4.0.0" - }, - "peerDependenciesMeta": { - "vite": { - "optional": true - } - } - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dev": true, - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dependencies": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-collection": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", - "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", - "dependencies": { - "is-map": "^2.0.1", - "is-set": "^2.0.1", - "is-weakmap": "^2.0.1", - "is-weakset": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-typed-array": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz", - "integrity": "sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==", - "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/wide-align": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", - "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "dev": true, - "dependencies": { - "string-width": "^1.0.2 || 2 || 3 || 4" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/yaml": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.2.tgz", - "integrity": "sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg==", - "dev": true, - "engines": { - "node": ">= 14" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } -} +{ + "name": "esa", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "esa", + "version": "0.0.1", + "dependencies": { + "@auth/core": "^0.34.2", + "@auth/sveltekit": "^1.4.2", + "canvas": "^2.11.2", + "classnames": "^2.5.1", + "feiertagejs": "^1.4.0", + "flowbite-svelte": "^0.46.15", + "flowbite-svelte-icons": "^2.0.0-next.3", + "ical-generator": "^7.2.0", + "intl": "^1.2.5", + "leaflet": "^1.9.4", + "markdown-it": "^14.1.0", + "modern-diacritics": "^2.3.1", + "node-cron": "^3.0.3", + "nodemailer": "^6.9.12", + "pdfjs-dist": "^4.1.392", + "postgres": "^3.4.3", + "sharp": "^0.33.4" + }, + "devDependencies": { + "@sveltejs/adapter-auto": "^3.0.0", + "@sveltejs/adapter-node": "^5.0.1", + "@sveltejs/kit": "^2.0.0", + "@sveltejs/vite-plugin-svelte": "^3.0.0", + "@types/eslint": "^9.6.0", + "@types/geojson": "^7946.0.14", + "@types/intl": "^1.2.2", + "@types/leaflet": "^1.9.8", + "@types/markdown-it": "^14.1.2", + "@types/node": "^22.1.0", + "@types/node-cron": "^3.0.11", + "@types/nodemailer": "^6.4.14", + "@types/pdfjs-dist": "^2.10.378", + "@typescript-eslint/eslint-plugin": "^8.0.1", + "@typescript-eslint/parser": "^8.0.1", + "autoprefixer": "^10.4.16", + "eslint": "^9.8.0", + "eslint-plugin-svelte": "^2.36.0-next.4", + "npm-run-all": "^4.1.5", + "postcss": "^8.4.32", + "postcss-load-config": "^6.0.1", + "svelte": "^5.0.0-next.1", + "svelte-check": "^3.6.0", + "tailwindcss": "^3.3.6", + "tslib": "^2.4.1", + "typescript": "^5.0.0", + "vite": "^5.0.3", + "vite-plugin-watch-and-run": "^1.6.0" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@auth/core": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@auth/core/-/core-0.34.2.tgz", + "integrity": "sha512-KywHKRgLiF3l7PLyL73fjLSIBe1YNcA6sMeew4yMP6cfCWGXZrkkXd32AjRi1hlJ9nvovUBGZHvbn+LijO6ZeQ==", + "license": "ISC", + "dependencies": { + "@panva/hkdf": "^1.1.1", + "@types/cookie": "0.6.0", + "cookie": "0.6.0", + "jose": "^5.1.3", + "oauth4webapi": "^2.10.4", + "preact": "10.11.3", + "preact-render-to-string": "5.2.3" + }, + "peerDependencies": { + "@simplewebauthn/browser": "^9.0.1", + "@simplewebauthn/server": "^9.0.2", + "nodemailer": "^6.8.0" + }, + "peerDependenciesMeta": { + "@simplewebauthn/browser": { + "optional": true + }, + "@simplewebauthn/server": { + "optional": true + }, + "nodemailer": { + "optional": true + } + } + }, + "node_modules/@auth/sveltekit": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@auth/sveltekit/-/sveltekit-1.4.2.tgz", + "integrity": "sha512-47xHm+e0i26HwXNeG+DRYj3Dv7+qbATPvXh5thYMxEal5oWItf9DT9ohhkEwVQvrmH0QsDYvlGztegZiB1MHMA==", + "license": "ISC", + "dependencies": { + "@auth/core": "0.34.2", + "set-cookie-parser": "^2.6.0" + }, + "peerDependencies": { + "@simplewebauthn/browser": "^9.0.1", + "@simplewebauthn/server": "^9.0.3", + "@sveltejs/kit": "^1.0.0 || ^2.0.0", + "nodemailer": "^6.6.5", + "svelte": "^3.54.0 || ^4.0.0 || ^5" + }, + "peerDependenciesMeta": { + "@simplewebauthn/browser": { + "optional": true + }, + "@simplewebauthn/server": { + "optional": true + }, + "nodemailer": { + "optional": true + } + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.2.0.tgz", + "integrity": "sha512-bV21/9LQmcQeCPEg3BDFtvwL6cwiTMksYNWQQ4KOxCZikEGalWtenoZ0wCiukJINlGCIi2KXx01g4FoH/LxpzQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", + "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.17.1.tgz", + "integrity": "sha512-BlYOpej8AQ8Ev9xVqroV7a02JK3SkBAaN9GfMMH9W6Ch8FlQlkjGw4Ir7+FgYwfirivAf4t+GtzuAxqfukmISA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.4", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", + "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/eslint-visitor-keys": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", + "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/espree": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz", + "integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.12.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.8.0.tgz", + "integrity": "sha512-MfluB7EUfxXtv3i/++oh89uzAr4PDI4nn201hsp+qaXqsjAWzinlZEHEfPgAX4doIlKvPG/i0A9dpKxOLII8yA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", + "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.5.tgz", + "integrity": "sha512-8GrTWmoFhm5BsMZOTHeGD2/0FLKLQQHvO/ZmQga4tKempYRLz8aqJGqXVuQgisnMObq2YZ2SgkwctN1LOOxcqA==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.5" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.8.tgz", + "integrity": "sha512-kx62rP19VZ767Q653wsP1XZCGIirkE09E0QUGNYTM/ttbbQHqcGPdSfWFxUyyNLc/W6aoJRBajOSXhP6GXjC0Q==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.5" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.5.tgz", + "integrity": "sha512-sTcG+QZ6fdEUObICavU+aB3Mp8HY4n14wYHdxK4fXjPmv3PXZZeY5RaguJmGyeH/CJQhX3fqKUtS4qc1LoHwhQ==", + "license": "MIT" + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.0.tgz", + "integrity": "sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.33.4", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.4.tgz", + "integrity": "sha512-p0suNqXufJs9t3RqLBO6vvrgr5OhgbWp76s5gTRvdmxmuv9E1rcaqGUsl3l4mKVmXPkTkTErXediAui4x+8PSA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "glibc": ">=2.26", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.0.2" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.33.4", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.4.tgz", + "integrity": "sha512-0l7yRObwtTi82Z6ebVI2PnHT8EB2NxBgpK2MiKJZJ7cz32R4lxd001ecMhzzsZig3Yv9oclvqqdV93jo9hy+Dw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "glibc": ">=2.26", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.0.2" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.2.tgz", + "integrity": "sha512-tcK/41Rq8IKlSaKRCCAuuY3lDJjQnYIW1UXU1kxcEKrfL8WR7N6+rzNoOxoQRJWTAECuKwgAHnPvqXGN8XfkHA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "macos": ">=11", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.2.tgz", + "integrity": "sha512-Ofw+7oaWa0HiiMiKWqqaZbaYV3/UGL2wAPeLuJTx+9cXpCRdvQhCLG0IH8YGwM0yGWGLpsF4Su9vM1o6aer+Fw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "macos": ">=10.13", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.2.tgz", + "integrity": "sha512-iLWCvrKgeFoglQxdEwzu1eQV04o8YeYGFXtfWU26Zr2wWT3q3MTzC+QTCO3ZQfWd3doKHT4Pm2kRmLbupT+sZw==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.28", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.2.tgz", + "integrity": "sha512-x7kCt3N00ofFmmkkdshwj3vGPCnmiDh7Gwnd4nUwZln2YjqPxV1NlTyZOvoDWdKQVDL911487HOueBvrpflagw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.26", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.2.tgz", + "integrity": "sha512-cmhQ1J4qVhfmS6szYW7RT+gLJq9dH2i4maq+qyXayUSn9/3iY2ZeWpbAgSpSVbV2E1JUL2Gg7pwnYQ1h8rQIog==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.28", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.2.tgz", + "integrity": "sha512-E441q4Qdb+7yuyiADVi5J+44x8ctlrqn8XgkDTwr4qPJzWkaHwD489iZ4nGDgcuya4iMN3ULV6NwbhRZJ9Z7SQ==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.26", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.2.tgz", + "integrity": "sha512-3CAkndNpYUrlDqkCM5qhksfE+qSIREVpyoeHIU6jd48SJZViAmznoQQLAv4hVXF7xyUB9zf+G++e2v1ABjCbEQ==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "musl": ">=1.2.2", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.2.tgz", + "integrity": "sha512-VI94Q6khIHqHWNOh6LLdm9s2Ry4zdjWJwH56WoiJU7NTeDwyApdZZ8c+SADC8OH98KWNQXnE01UdJ9CSfZvwZw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "musl": ">=1.2.2", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.33.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.4.tgz", + "integrity": "sha512-RUgBD1c0+gCYZGCCe6mMdTiOFS0Zc/XrN0fYd6hISIKcDUbAW5NtSQW9g/powkrXYm6Vzwd6y+fqmExDuCdHNQ==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.28", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.0.2" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.33.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.4.tgz", + "integrity": "sha512-2800clwVg1ZQtxwSoTlHvtm9ObgAax7V6MTAB/hDT945Tfyy3hVkmiHpeLPCKYqYR1Gcmv1uDZ3a4OFwkdBL7Q==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.26", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.0.2" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.33.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.4.tgz", + "integrity": "sha512-h3RAL3siQoyzSoH36tUeS0PDmb5wINKGYzcLB5C6DIiAn2F3udeFAum+gj8IbA/82+8RGCTn7XW8WTFnqag4tQ==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.31", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.0.2" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.33.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.4.tgz", + "integrity": "sha512-GoR++s0XW9DGVi8SUGQ/U4AeIzLdNjHka6jidVwapQ/JebGVQIpi52OdyxCNVRE++n1FCLzjDovJNozif7w/Aw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.26", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.0.2" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.33.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.4.tgz", + "integrity": "sha512-nhr1yC3BlVrKDTl6cO12gTpXMl4ITBUZieehFvMntlCXFzH2bvKG76tBL2Y/OqhupZt81pR7R+Q5YhJxW0rGgQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "musl": ">=1.2.2", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.0.2" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.33.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.4.tgz", + "integrity": "sha512-uCPTku0zwqDmZEOi4ILyGdmW76tH7dm8kKlOIV1XC5cLyJ71ENAAqarOHQh0RLfpIpbV5KOpXzdU6XkJtS0daw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "musl": ">=1.2.2", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.0.2" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.33.4", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.4.tgz", + "integrity": "sha512-Bmmauh4sXUsUqkleQahpdNXKvo+wa1V9KhT2pDA4VJGKwnKMJXiSTGphn0gnJrlooda0QxCtXc6RX1XAU6hMnQ==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.1.1" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.33.4", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.4.tgz", + "integrity": "sha512-99SJ91XzUhYHbx7uhK3+9Lf7+LjwMGQZMDlO/E/YVJ7Nc3lyDFZPGhjwiYdctoH2BOzW9+TnfqcaMKt0jHLdqw==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.33.4", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.4.tgz", + "integrity": "sha512-3QLocdTRVIrFNye5YocZl+KKpYKP+fksi1QhmOArgx7GyhIbQp/WrJRu176jm8IxromS7RIkzMiMINVdBtC8Aw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@kitql/helpers": { + "version": "0.8.9", + "resolved": "https://registry.npmjs.org/@kitql/helpers/-/helpers-0.8.9.tgz", + "integrity": "sha512-uDBFBvCYUT4UaZZKv7gJejQvbrOp4YyI1S0Z92DPiMbyLq0DPDXz3Lt2ZqUZKlQrinBX+W1TO6w0RudEX6Q6WA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esm-env": "^1.0.0" + }, + "engines": { + "node": "^16.14 || >=18" + } + }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "license": "BSD-3-Clause", + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@panva/hkdf": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.2.1.tgz", + "integrity": "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.25", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.25.tgz", + "integrity": "sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@rollup/plugin-commonjs": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-26.0.1.tgz", + "integrity": "sha512-UnsKoZK6/aGIH6AdkptXhNvhaqftcjq3zZdT+LY5Ftms6JR06nADcDsYp5hTU9E2lbJUEOhdlY5J4DNTneM+jQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "commondir": "^1.0.1", + "estree-walker": "^2.0.2", + "glob": "^10.4.1", + "is-reference": "1.2.1", + "magic-string": "^0.30.3" + }, + "engines": { + "node": ">=16.0.0 || 14 >= 14.17" + }, + "peerDependencies": { + "rollup": "^2.68.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-json": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz", + "integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.1.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "15.2.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz", + "integrity": "sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==", + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-builtin-module": "^3.2.1", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", + "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.20.0.tgz", + "integrity": "sha512-TSpWzflCc4VGAUJZlPpgAJE1+V60MePDQnBd7PPkpuEmOy8i87aL6tinFGKBFKuEDikYpig72QzdT3QPYIi+oA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.20.0.tgz", + "integrity": "sha512-u00Ro/nok7oGzVuh/FMYfNoGqxU5CPWz1mxV85S2w9LxHR8OoMQBuSk+3BKVIDYgkpeOET5yXkx90OYFc+ytpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.20.0.tgz", + "integrity": "sha512-uFVfvzvsdGtlSLuL0ZlvPJvl6ZmrH4CBwLGEFPe7hUmf7htGAN+aXo43R/V6LATyxlKVC/m6UsLb7jbG+LG39Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.20.0.tgz", + "integrity": "sha512-xbrMDdlev53vNXexEa6l0LffojxhqDTBeL+VUxuuIXys4x6xyvbKq5XqTXBCEUA8ty8iEJblHvFaWRJTk/icAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.20.0.tgz", + "integrity": "sha512-jMYvxZwGmoHFBTbr12Xc6wOdc2xA5tF5F2q6t7Rcfab68TT0n+r7dgawD4qhPEvasDsVpQi+MgDzj2faOLsZjA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.20.0.tgz", + "integrity": "sha512-1asSTl4HKuIHIB1GcdFHNNZhxAYEdqML/MW4QmPS4G0ivbEcBr1JKlFLKsIRqjSwOBkdItn3/ZDlyvZ/N6KPlw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.20.0.tgz", + "integrity": "sha512-COBb8Bkx56KldOYJfMf6wKeYJrtJ9vEgBRAOkfw6Ens0tnmzPqvlpjZiLgkhg6cA3DGzCmLmmd319pmHvKWWlQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.20.0.tgz", + "integrity": "sha512-+it+mBSyMslVQa8wSPvBx53fYuZK/oLTu5RJoXogjk6x7Q7sz1GNRsXWjn6SwyJm8E/oMjNVwPhmNdIjwP135Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.20.0.tgz", + "integrity": "sha512-yAMvqhPfGKsAxHN8I4+jE0CpLWD8cv4z7CK7BMmhjDuz606Q2tFKkWRY8bHR9JQXYcoLfopo5TTqzxgPUjUMfw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.20.0.tgz", + "integrity": "sha512-qmuxFpfmi/2SUkAw95TtNq/w/I7Gpjurx609OOOV7U4vhvUhBcftcmXwl3rqAek+ADBwSjIC4IVNLiszoj3dPA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.20.0.tgz", + "integrity": "sha512-I0BtGXddHSHjV1mqTNkgUZLnS3WtsqebAXv11D5BZE/gfw5KoyXSAXVqyJximQXNvNzUo4GKlCK/dIwXlz+jlg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.20.0.tgz", + "integrity": "sha512-y+eoL2I3iphUg9tN9GB6ku1FA8kOfmF4oUEWhztDJ4KXJy1agk/9+pejOuZkNFhRwHAOxMsBPLbXPd6mJiCwew==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.20.0.tgz", + "integrity": "sha512-hM3nhW40kBNYUkZb/r9k2FKK+/MnKglX7UYd4ZUy5DJs8/sMsIbqWK2piZtVGE3kcXVNj3B2IrUYROJMMCikNg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.20.0.tgz", + "integrity": "sha512-psegMvP+Ik/Bg7QRJbv8w8PAytPA7Uo8fpFjXyCRHWm6Nt42L+JtoqH8eDQ5hRP7/XW2UiIriy1Z46jf0Oa1kA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.20.0.tgz", + "integrity": "sha512-GabekH3w4lgAJpVxkk7hUzUf2hICSQO0a/BLFA11/RMxQT92MabKAqyubzDZmMOC/hcJNlc+rrypzNzYl4Dx7A==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.20.0.tgz", + "integrity": "sha512-aJ1EJSuTdGnM6qbVC4B5DSmozPTqIag9fSzXRNNo+humQLG89XpPgdt16Ia56ORD7s+H8Pmyx44uczDQ0yDzpg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sveltejs/adapter-auto": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-3.2.2.tgz", + "integrity": "sha512-Mso5xPCA8zgcKrv+QioVlqMZkyUQ5MjDJiEPuG/Z7cV/5tmwV7LmcVWk5tZ+H0NCOV1x12AsoSpt/CwFwuVXMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "import-meta-resolve": "^4.1.0" + }, + "peerDependencies": { + "@sveltejs/kit": "^2.0.0" + } + }, + "node_modules/@sveltejs/adapter-node": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@sveltejs/adapter-node/-/adapter-node-5.2.0.tgz", + "integrity": "sha512-HVZoei2078XSyPmvdTHE03VXDUD0ytTvMuMHMQP0j6zX4nPDpCcKrgvU7baEblMeCCMdM/shQvstFxOJPQKlUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/plugin-commonjs": "^26.0.1", + "@rollup/plugin-json": "^6.1.0", + "@rollup/plugin-node-resolve": "^15.2.3", + "rollup": "^4.9.5" + }, + "peerDependencies": { + "@sveltejs/kit": "^2.4.0" + } + }, + "node_modules/@sveltejs/kit": { + "version": "2.5.20", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.5.20.tgz", + "integrity": "sha512-47rJ5BoYwURE/Rp7FNMLp3NzdbWC9DQ/PmKd0mebxT2D/PrPxZxcLImcD3zsWdX2iS6oJk8ITJbO/N2lWnnUqA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@types/cookie": "^0.6.0", + "cookie": "^0.6.0", + "devalue": "^5.0.0", + "esm-env": "^1.0.0", + "import-meta-resolve": "^4.1.0", + "kleur": "^4.1.5", + "magic-string": "^0.30.5", + "mrmime": "^2.0.0", + "sade": "^1.8.1", + "set-cookie-parser": "^2.6.0", + "sirv": "^2.0.4", + "tiny-glob": "^0.2.9" + }, + "bin": { + "svelte-kit": "svelte-kit.js" + }, + "engines": { + "node": ">=18.13" + }, + "peerDependencies": { + "@sveltejs/vite-plugin-svelte": "^3.0.0", + "svelte": "^4.0.0 || ^5.0.0-next.0", + "vite": "^5.0.3" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-3.1.1.tgz", + "integrity": "sha512-rimpFEAboBBHIlzISibg94iP09k/KYdHgVhJlcsTfn7KMBhc70jFX/GRWkRdFCc2fdnk+4+Bdfej23cMDnJS6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sveltejs/vite-plugin-svelte-inspector": "^2.1.0", + "debug": "^4.3.4", + "deepmerge": "^4.3.1", + "kleur": "^4.1.5", + "magic-string": "^0.30.10", + "svelte-hmr": "^0.16.0", + "vitefu": "^0.2.5" + }, + "engines": { + "node": "^18.0.0 || >=20" + }, + "peerDependencies": { + "svelte": "^4.0.0 || ^5.0.0-next.0", + "vite": "^5.0.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte-inspector": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-2.1.0.tgz", + "integrity": "sha512-9QX28IymvBlSCqsCll5t0kQVxipsfhFFL+L2t3nTWfXnddYwxBuAEtTtlaVQpRz9c37BhJjltSeY4AJSC03SSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.0.0 || >=20" + }, + "peerDependencies": { + "@sveltejs/vite-plugin-svelte": "^3.0.0", + "svelte": "^4.0.0 || ^5.0.0-next.0", + "vite": "^5.0.0" + } + }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "license": "MIT" + }, + "node_modules/@types/eslint": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.0.tgz", + "integrity": "sha512-gi6WQJ7cHRgZxtkQEoyHMppPjq9Kxo5Tjn2prSKDSmZrCz8TZ3jSRCeTJm+WoM+oB0WG37bRqLzaaU3q7JypGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/@types/geojson": { + "version": "7946.0.14", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.14.tgz", + "integrity": "sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/intl": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@types/intl/-/intl-1.2.2.tgz", + "integrity": "sha512-A4g5UEAA9c56JWKhc4LSJ9vzDMztjxVXDAXtVK4EbS3hW1V7Sz05VfcTgDQIvkOYJp1NqH222eg62P72HwltfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/leaflet": { + "version": "1.9.12", + "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.12.tgz", + "integrity": "sha512-BK7XS+NyRI291HIo0HCfE18Lp8oA30H1gpi1tf0mF3TgiCEzanQjOqNZ4x126SXzzi2oNSZhZ5axJp1k0iM6jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/markdown-it": { + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.1.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.1.0.tgz", + "integrity": "sha512-AOmuRF0R2/5j1knA3c6G3HOk523Ga+l+ZXltX8SF1+5oqcXijjfTd8fY3XRZqSihEu9XhtQnKYLmkFaoxgsJHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.13.0" + } + }, + "node_modules/@types/node-cron": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/node-cron/-/node-cron-3.0.11.tgz", + "integrity": "sha512-0ikrnug3/IyneSHqCBeslAhlK2aBfYek1fGo4bP4QnZPmiqSGRK+Oy7ZMisLWkesffJvQ1cqAcBnJC+8+nxIAg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/nodemailer": { + "version": "6.4.15", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.15.tgz", + "integrity": "sha512-0EBJxawVNjPkng1zm2vopRctuWVCxk34JcIlRuXSf54habUWdz1FB7wHDqOqvDa8Mtpt0Q3LTXQkAs2LNyK5jQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/pdfjs-dist": { + "version": "2.10.378", + "resolved": "https://registry.npmjs.org/@types/pdfjs-dist/-/pdfjs-dist-2.10.378.tgz", + "integrity": "sha512-TRdIPqdsvKmPla44kVy4jv5Nt5vjMfVjbIEke1CRULIrwKNRC4lIiZvNYDJvbUMNCFPNIUcOKhXTyMJrX18IMA==", + "deprecated": "This is a stub types definition. pdfjs-dist provides its own type definitions, so you do not need this installed.", + "dev": true, + "license": "MIT", + "dependencies": { + "pdfjs-dist": "*" + } + }, + "node_modules/@types/pug": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.10.tgz", + "integrity": "sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.0.1.tgz", + "integrity": "sha512-5g3Y7GDFsJAnY4Yhvk8sZtFfV6YNF2caLzjrRPUBzewjPCaj0yokePB4LJSobyCzGMzjZZYFbwuzbfDHlimXbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.0.1", + "@typescript-eslint/type-utils": "8.0.1", + "@typescript-eslint/utils": "8.0.1", + "@typescript-eslint/visitor-keys": "8.0.1", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.0.1.tgz", + "integrity": "sha512-5IgYJ9EO/12pOUwiBKFkpU7rS3IU21mtXzB81TNwq2xEybcmAZrE9qwDtsb5uQd9aVO9o0fdabFyAmKveXyujg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "8.0.1", + "@typescript-eslint/types": "8.0.1", + "@typescript-eslint/typescript-estree": "8.0.1", + "@typescript-eslint/visitor-keys": "8.0.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.0.1.tgz", + "integrity": "sha512-NpixInP5dm7uukMiRyiHjRKkom5RIFA4dfiHvalanD2cF0CLUuQqxfg8PtEUo9yqJI2bBhF+pcSafqnG3UBnRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.0.1", + "@typescript-eslint/visitor-keys": "8.0.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.0.1.tgz", + "integrity": "sha512-+/UT25MWvXeDX9YaHv1IS6KI1fiuTto43WprE7pgSMswHbn1Jm9GEM4Txp+X74ifOWV8emu2AWcbLhpJAvD5Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "8.0.1", + "@typescript-eslint/utils": "8.0.1", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.0.1.tgz", + "integrity": "sha512-PpqTVT3yCA/bIgJ12czBuE3iBlM3g4inRSC5J0QOdQFAn07TYrYEQBBKgXH1lQpglup+Zy6c1fxuwTk4MTNKIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.0.1.tgz", + "integrity": "sha512-8V9hriRvZQXPWU3bbiUV4Epo7EvgM6RTs+sUmxp5G//dBGy402S7Fx0W0QkB2fb4obCF8SInoUzvTYtc3bkb5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "8.0.1", + "@typescript-eslint/visitor-keys": "8.0.1", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.0.1.tgz", + "integrity": "sha512-CBFR0G0sCt0+fzfnKaciu9IBsKvEKYwN9UZ+eeogK1fYHg4Qxk1yf/wLQkLXlq8wbU2dFlgAesxt8Gi76E8RTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.0.1", + "@typescript-eslint/types": "8.0.1", + "@typescript-eslint/typescript-estree": "8.0.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.0.1.tgz", + "integrity": "sha512-W5E+o0UfUcK5EgchLZsyVWqARmsM7v54/qEq6PY3YI5arkgmCzHiuk0zKSJJbm71V0xdRna4BGomkCTXz2/LkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.0.1", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@yr/monotone-cubic-spline": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz", + "integrity": "sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA==", + "license": "MIT" + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "license": "ISC" + }, + "node_modules/acorn": { + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-typescript": { + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/acorn-typescript/-/acorn-typescript-1.4.13.tgz", + "integrity": "sha512-xsc9Xv0xlVfwp2o7sQ+GCQ1PgbkdcpWdTzrwXxO3xDMTAywVS3oXVOcOHuRjAPkS4P9b+yc/qNF15460v+jp4Q==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": ">=8.9.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/apexcharts": { + "version": "3.52.0", + "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.52.0.tgz", + "integrity": "sha512-7dg0ADKs8AA89iYMZMe2sFDG0XK5PfqllKV9N+i3hKHm3vEtdhwz8AlXGm+/b0nJ6jKiaXsqci5LfVxNhtB+dA==", + "license": "MIT", + "dependencies": { + "@yr/monotone-cubic-spline": "^1.0.3", + "svg.draggable.js": "^2.2.2", + "svg.easing.js": "^2.0.0", + "svg.filter.js": "^2.0.2", + "svg.pathmorphing.js": "^0.1.3", + "svg.resize.js": "^1.4.3", + "svg.select.js": "^3.0.1" + } + }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "license": "ISC" + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.20", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", + "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.3", + "caniuse-lite": "^1.0.30001646", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", + "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001646", + "electron-to-chromium": "^1.5.4", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-crc32": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", + "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001649", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001649.tgz", + "integrity": "sha512-fJegqZZ0ZX8HOWr6rcafGr72+xcgJKI9oWfDW5DrD7ExUtgZC7a7R7ZYmZqplh7XDocFdGeIFn7roAxhOeYrPQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/canvas": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.11.2.tgz", + "integrity": "sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.0", + "nan": "^2.17.0", + "simple-get": "^3.0.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" + }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "license": "ISC", + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "license": "ISC" + }, + "node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "license": "MIT", + "dependencies": { + "mimic-response": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "license": "MIT" + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-indent": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", + "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/devalue": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.0.0.tgz", + "integrity": "sha512-gO+/OMXF7488D+u3ue+G7Y4AA3ZmUnB3eHJXmBTgNHvr4ZNzl36A0ZtG+XCRNYCkYx/bFmw4qtkoFLa+wSrwAA==", + "dev": true, + "license": "MIT" + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "license": "Apache-2.0" + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "license": "MIT" + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.4.tgz", + "integrity": "sha512-orzA81VqLyIGUEA77YkVA1D+N+nNfl2isJVjjmOyrlxuooZ19ynb+dOlaDTqd/idKRS9lDCSBmtzM+kyCsMnkA==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", + "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "hasown": "^2.0.2", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.1", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.3", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.13", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.2", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.6", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.15" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es6-promise": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", + "integrity": "sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.8.0.tgz", + "integrity": "sha512-K8qnZ/QJzT2dLKdZJVX6W4XOwBzutMYmt0lqUS+JdXgd+HTYFlonFgkJ8s44d/zMPPCnOOk0kMWCApCPhiOy9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.11.0", + "@eslint/config-array": "^0.17.1", + "@eslint/eslintrc": "^3.1.0", + "@eslint/js": "9.8.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.3.0", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.0.2", + "eslint-visitor-keys": "^4.0.0", + "espree": "^10.1.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/eslint-compat-utils": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.5.1.tgz", + "integrity": "sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, + "node_modules/eslint-plugin-svelte": { + "version": "2.43.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-2.43.0.tgz", + "integrity": "sha512-REkxQWvg2pp7QVLxQNa+dJ97xUqRe7Y2JJbSWkHSuszu0VcblZtXkPBPckkivk99y5CdLw4slqfPylL2d/X4jQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@jridgewell/sourcemap-codec": "^1.4.15", + "eslint-compat-utils": "^0.5.1", + "esutils": "^2.0.3", + "known-css-properties": "^0.34.0", + "postcss": "^8.4.38", + "postcss-load-config": "^3.1.4", + "postcss-safe-parser": "^6.0.0", + "postcss-selector-parser": "^6.1.0", + "semver": "^7.6.2", + "svelte-eslint-parser": "^0.41.0" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0-0 || ^9.0.0-0", + "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0-next.191" + }, + "peerDependenciesMeta": { + "svelte": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-svelte/node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-plugin-svelte/node_modules/postcss-load-config": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", + "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lilconfig": "^2.0.5", + "yaml": "^1.10.2" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-svelte/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.2.tgz", + "integrity": "sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", + "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/espree": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz", + "integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.12.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/esm-env": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.0.0.tgz", + "integrity": "sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrap": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esrap/-/esrap-1.2.2.tgz", + "integrity": "sha512-F2pSJklxx1BlQIQgooczXCPHmcWpn6EsP5oo73LQfonG9fIlIENQ8vMmfGXeojP9MrkzUNAfyU5vdFlR9shHAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15", + "@types/estree": "^1.0.1" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/feiertagejs": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/feiertagejs/-/feiertagejs-1.4.1.tgz", + "integrity": "sha512-+tXhTi6KG9rEw9iDR0TZGA5/lChAjwXuVYXiuHbzAOlfdmCo3MUMPYeL7GW4qjxS5KwsWqfi877JmzXZyhiimA==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true, + "license": "ISC" + }, + "node_modules/flowbite": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/flowbite/-/flowbite-2.5.1.tgz", + "integrity": "sha512-7jP1jy9c3QP7y+KU9lc8ueMkTyUdMDvRP+lteSWgY5TigSZjf9K1kqZxmqjhbx2gBnFQxMl1GAjVThCa8cEpKA==", + "license": "MIT", + "dependencies": { + "@popperjs/core": "^2.9.3", + "flowbite-datepicker": "^1.3.0", + "mini-svg-data-uri": "^1.4.3" + } + }, + "node_modules/flowbite-datepicker": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/flowbite-datepicker/-/flowbite-datepicker-1.3.0.tgz", + "integrity": "sha512-CLVqzuoE2vkUvWYK/lJ6GzT0be5dlTbH3uuhVwyB67+PjqJWABm2wv68xhBf5BqjpBxvTSQ3mrmLHpPJ2tvrSQ==", + "license": "MIT", + "dependencies": { + "@rollup/plugin-node-resolve": "^15.2.3", + "flowbite": "^2.0.0" + } + }, + "node_modules/flowbite-svelte": { + "version": "0.46.15", + "resolved": "https://registry.npmjs.org/flowbite-svelte/-/flowbite-svelte-0.46.15.tgz", + "integrity": "sha512-xWhyLDez/gafTAmQayPMPKmWj1BAoq80SoA48yHZ12Wk3Vu3hFrELLUZf0UksxnjQL9hMOKRUlYl3/IH6pHwnQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.6.7", + "apexcharts": "^3.49.2", + "flowbite": "^2.4.1", + "tailwind-merge": "^2.3.0" + }, + "engines": { + "node": ">=18.0.0", + "pnpm": ">=8.0.0" + }, + "peerDependencies": { + "svelte": "^3.55.1 || ^4.0.0 || ^5.0.0" + } + }, + "node_modules/flowbite-svelte-icons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/flowbite-svelte-icons/-/flowbite-svelte-icons-2.0.0.tgz", + "integrity": "sha512-zLFrt48kYDDGJhLOpV6GoylzTVdBjPR8vjWILpEl6a0e/2V9gegSmNu4G1hCDqDZ9cjsGUBU0GBtD4oPIoRJpQ==", + "license": "MIT", + "dependencies": { + "tailwind-merge": "^2.2.1", + "tailwindcss": "^3.4.1" + }, + "peerDependencies": { + "svelte": "^5.0.0", + "tailwind-merge": "^2.0.0", + "tailwindcss": "^3.3.2" + } + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/foreground-child": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz", + "integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" + }, + "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, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/gauge/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/gauge/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, + "node_modules/gauge/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globalyzer": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.0.tgz", + "integrity": "sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", + "dev": true, + "license": "MIT" + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "license": "ISC" + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true, + "license": "ISC" + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/ical-generator": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/ical-generator/-/ical-generator-7.2.0.tgz", + "integrity": "sha512-7I34QvxWqIRthaao81lmapa0OjftfDaSBZmADjV0IqxVMUWT5ywlATRsv/hZN9Rgf2VgRsnMY+xUUaA4ZvAJLA==", + "license": "MIT", + "dependencies": { + "uuid-random": "^1.3.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@touch4it/ical-timezones": ">=1.6.0", + "@types/luxon": ">= 1.26.0", + "@types/mocha": ">= 8.2.1", + "dayjs": ">= 1.10.0", + "luxon": ">= 1.26.0", + "moment": ">= 2.29.0", + "moment-timezone": ">= 0.5.33", + "rrule": ">= 2.6.8" + }, + "peerDependenciesMeta": { + "@touch4it/ical-timezones": { + "optional": true + }, + "@types/luxon": { + "optional": true + }, + "@types/mocha": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "dayjs": { + "optional": true + }, + "luxon": { + "optional": true + }, + "moment": { + "optional": true + }, + "moment-timezone": { + "optional": true + }, + "rrule": { + "optional": true + } + } + }, + "node_modules/ignore": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-meta-resolve": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", + "integrity": "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/internal-slot": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/intl": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/intl/-/intl-1.2.5.tgz", + "integrity": "sha512-rK0KcPHeBFBcqsErKSpvZnrOmWOj+EmDkyJ57e90YWaQNqbcivcqmKDlHEeNprDWOsKzPsh1BfSpPQdDvclHVw==", + "license": "MIT" + }, + "node_modules/is-array-buffer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-builtin-module": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", + "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", + "license": "MIT", + "dependencies": { + "builtin-modules": "^3.3.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.0.tgz", + "integrity": "sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", + "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "license": "MIT" + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-reference": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", + "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jiti": { + "version": "1.21.6", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", + "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/jose": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-5.6.3.tgz", + "integrity": "sha512-1Jh//hEEwMhNYPDDLwXHa2ePWgWiFNNUadVmguAAw2IJ6sj9mNxV5tGXJNqlMkJAybF6Lgw1mISDxTePP/187g==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/known-css-properties": { + "version": "0.34.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.34.0.tgz", + "integrity": "sha512-tBECoUqNFbyAY4RrbqsBQqDFpGXAEbdD5QKr8kACx3+rnArmuuR22nKQWKazvp07N9yjTyDZaw/20UIH8tL9DQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/leaflet": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", + "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==", + "license": "BSD-2-Clause" + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lilconfig": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", + "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/locate-character": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", + "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/magic-string": { + "version": "0.30.11", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", + "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "license": "MIT", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "license": "MIT" + }, + "node_modules/memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", + "dev": true, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", + "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/mini-svg-data-uri": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz", + "integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==", + "license": "MIT", + "bin": { + "mini-svg-data-uri": "cli.js" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/modern-diacritics": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/modern-diacritics/-/modern-diacritics-2.3.1.tgz", + "integrity": "sha512-jOI0qtelWfGZ4HTB/7pKTOzAA5miXgfbMl0RYirYA5CRuNY4JYTXER+BUiH9JCxqX1gIHs6GaVTwFhvUV8MPWg==", + "license": "MIT", + "engines": { + "node": "^14 || >=16" + } + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/mrmime": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", + "integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nan": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.20.0.tgz", + "integrity": "sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw==", + "license": "MIT" + }, + "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" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-cron": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.3.tgz", + "integrity": "sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A==", + "license": "ISC", + "dependencies": { + "uuid": "8.3.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-releases": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "dev": true, + "license": "MIT" + }, + "node_modules/nodemailer": { + "version": "6.9.14", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.14.tgz", + "integrity": "sha512-Dobp/ebDKBvz91sbtRKhcznLThrKxKt97GI2FAlAyy+fk19j73Uz3sBXolVtmcXjaorivqsbbbjDY+Jkt4/bQA==", + "license": "MIT-0", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "license": "ISC", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/normalize-package-data/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-all": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", + "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "chalk": "^2.4.1", + "cross-spawn": "^6.0.5", + "memorystream": "^0.3.1", + "minimatch": "^3.0.4", + "pidtree": "^0.3.0", + "read-pkg": "^3.0.0", + "shell-quote": "^1.6.1", + "string.prototype.padend": "^3.0.0" + }, + "bin": { + "npm-run-all": "bin/npm-run-all/index.js", + "run-p": "bin/run-p/index.js", + "run-s": "bin/run-s/index.js" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/npm-run-all/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/npm-run-all/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/npm-run-all/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/npm-run-all/node_modules/cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/npm-run-all/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/npm-run-all/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/npm-run-all/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/npm-run-all/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-all/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-all/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, + "node_modules/oauth4webapi": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-2.11.1.tgz", + "integrity": "sha512-aNzOnL98bL6izG97zgnZs1PFEyO4WDVRhz2Pd066NPak44w5ESLRCYmJIyey8avSBPOMtBjhF3ZDDm7bIb7UOg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", + "license": "BlueOak-1.0.0" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path2d": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/path2d/-/path2d-0.2.1.tgz", + "integrity": "sha512-Fl2z/BHvkTNvkuBzYTpTuirHZg6wW9z8+4SND/3mDTEcYbbNKWAy21dz9D3ePNNwrrK8pqZO5vLPZ1hLF6T7XA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pdfjs-dist": { + "version": "4.5.136", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-4.5.136.tgz", + "integrity": "sha512-V1BALcAN/FmxBEShLxoP73PlQZAZtzlaNfRbRhJrKvXzjLC5VaIlBAQUJuWP8iaYUmIdmdLHmt3E2TBglxOm3w==", + "license": "Apache-2.0", + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "canvas": "^2.11.2", + "path2d": "^0.2.1" + } + }, + "node_modules/picocolors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pidtree": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz", + "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==", + "dev": true, + "license": "MIT", + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.4.40", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.40.tgz", + "integrity": "sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q==", + "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" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.1", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-safe-parser": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz", + "integrity": "sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.3.3" + } + }, + "node_modules/postcss-scss": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-4.0.9.tgz", + "integrity": "sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss-scss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.4.29" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.1.tgz", + "integrity": "sha512-b4dlw/9V8A71rLIDsSwVmak9z2DuBUB7CA1/wSdelNEzqsjoSPeADTWNO09lpH49Diy3/JIZ2bSPB1dI3LJCHg==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" + }, + "node_modules/postgres": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/postgres/-/postgres-3.4.4.tgz", + "integrity": "sha512-IbyN+9KslkqcXa8AO9fxpk97PA4pzewvpi2B3Dwy9u4zpV32QicaEdgmF3eSQUzdRk7ttDHQejNgAEr4XoeH4A==", + "license": "Unlicense", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/porsager" + } + }, + "node_modules/preact": { + "version": "10.11.3", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.11.3.tgz", + "integrity": "sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/preact-render-to-string": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.3.tgz", + "integrity": "sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA==", + "license": "MIT", + "dependencies": { + "pretty-format": "^3.8.0" + }, + "peerDependencies": { + "preact": ">=10" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/pretty-format": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz", + "integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==", + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/read-cache/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg/node_modules/path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/rollup": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.20.0.tgz", + "integrity": "sha512-6rbWBChcnSGzIlXeIdNIZTopKYad8ZG8ajhl78lGRLsI2rX8IkaotQhVas2Ma+GPxJav19wrSzvRvuiv0YKzWw==", + "dev": true, + "license": "MIT", + "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.20.0", + "@rollup/rollup-android-arm64": "4.20.0", + "@rollup/rollup-darwin-arm64": "4.20.0", + "@rollup/rollup-darwin-x64": "4.20.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.20.0", + "@rollup/rollup-linux-arm-musleabihf": "4.20.0", + "@rollup/rollup-linux-arm64-gnu": "4.20.0", + "@rollup/rollup-linux-arm64-musl": "4.20.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.20.0", + "@rollup/rollup-linux-riscv64-gnu": "4.20.0", + "@rollup/rollup-linux-s390x-gnu": "4.20.0", + "@rollup/rollup-linux-x64-gnu": "4.20.0", + "@rollup/rollup-linux-x64-musl": "4.20.0", + "@rollup/rollup-win32-arm64-msvc": "4.20.0", + "@rollup/rollup-win32-ia32-msvc": "4.20.0", + "@rollup/rollup-win32-x64-msvc": "4.20.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", + "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-regex-test": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-regex": "^1.1.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sander": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/sander/-/sander-0.5.1.tgz", + "integrity": "sha512-3lVqBir7WuKDHGrKRDn/1Ye3kwpXaDOMsiRP1wd6wpZW56gJhsbp5RqQpA6JG/P+pkXizygnr1dKR8vzWaVsfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es6-promise": "^3.1.2", + "graceful-fs": "^4.1.3", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.2" + } + }, + "node_modules/sander/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/sander/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sander/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/sander/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC" + }, + "node_modules/set-cookie-parser": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.0.tgz", + "integrity": "sha512-lXLOiqpkUumhRdFF3k1osNXCy9akgx/dyPZ5p8qAg9seJzXr5ZrlqZuWIMuY6ejOsVLE6flJ5/h3lsn57fQ/PQ==", + "license": "MIT" + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/sharp": { + "version": "0.33.4", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.4.tgz", + "integrity": "sha512-7i/dt5kGl7qR4gwPRD2biwD2/SvBn3O04J77XKFgL2OnZtQw+AG9wnuS/csmu80nPRHLYE9E41fyEiG8nhH6/Q==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.3", + "semver": "^7.6.0" + }, + "engines": { + "libvips": ">=8.15.2", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.33.4", + "@img/sharp-darwin-x64": "0.33.4", + "@img/sharp-libvips-darwin-arm64": "1.0.2", + "@img/sharp-libvips-darwin-x64": "1.0.2", + "@img/sharp-libvips-linux-arm": "1.0.2", + "@img/sharp-libvips-linux-arm64": "1.0.2", + "@img/sharp-libvips-linux-s390x": "1.0.2", + "@img/sharp-libvips-linux-x64": "1.0.2", + "@img/sharp-libvips-linuxmusl-arm64": "1.0.2", + "@img/sharp-libvips-linuxmusl-x64": "1.0.2", + "@img/sharp-linux-arm": "0.33.4", + "@img/sharp-linux-arm64": "0.33.4", + "@img/sharp-linux-s390x": "0.33.4", + "@img/sharp-linux-x64": "0.33.4", + "@img/sharp-linuxmusl-arm64": "0.33.4", + "@img/sharp-linuxmusl-x64": "0.33.4", + "@img/sharp-wasm32": "0.33.4", + "@img/sharp-win32-ia32": "0.33.4", + "@img/sharp-win32-x64": "0.33.4" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz", + "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", + "license": "MIT", + "dependencies": { + "decompress-response": "^4.2.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/simple-swizzle/node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "license": "MIT" + }, + "node_modules/sirv": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", + "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/sorcery": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/sorcery/-/sorcery-0.11.1.tgz", + "integrity": "sha512-o7npfeJE6wi6J9l0/5LKshFzZ2rMatRiCDwYeDQaOzqdzRJwALhX7mk/A/ecg6wjMu7wdZbmXfD2S/vpOg0bdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.14", + "buffer-crc32": "^1.0.0", + "minimist": "^1.2.0", + "sander": "^0.5.0" + }, + "bin": { + "sorcery": "bin/sorcery" + } + }, + "node_modules/source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true, + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.18", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.18.tgz", + "integrity": "sha512-xxRs31BqRYHwiMzudOrpSiHtZ8i/GeionCBDSilhYRj+9gIcI8wCZTlXZKu9vZIVqViP3dcp9qE5G6AlIaD+TQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/string.prototype.padend": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.6.tgz", + "integrity": "sha512-XZpspuSB7vJWhvJc9DLSlrXl1mcA2BdoY5jjnS135ydXqLoqhs96JjDtCkjJEQHvfqZIp9hBuBMgI589peyx9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", + "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", + "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/sucrase": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "^10.3.10", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svelte": { + "version": "5.0.0-next.210", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.0.0-next.210.tgz", + "integrity": "sha512-6QZpzr31weKDyAKifOdXJHK9oEeBE2Z/z/h1IX4tmJRuWPE/2Wc7Lzpfxl+0irS19GZH6V5YZnZLNTRJKjGzfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.1", + "@jridgewell/sourcemap-codec": "^1.4.15", + "@types/estree": "^1.0.5", + "acorn": "^8.11.3", + "acorn-typescript": "^1.4.13", + "aria-query": "^5.3.0", + "axobject-query": "^4.0.0", + "esm-env": "^1.0.0", + "esrap": "^1.2.2", + "is-reference": "^3.0.2", + "locate-character": "^3.0.0", + "magic-string": "^0.30.5", + "zimmerframe": "^1.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/svelte-check": { + "version": "3.8.5", + "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-3.8.5.tgz", + "integrity": "sha512-3OGGgr9+bJ/+1nbPgsvulkLC48xBsqsgtc8Wam281H4G9F5v3mYGa2bHRsPuwHC5brKl4AxJH95QF73kmfihGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.17", + "chokidar": "^3.4.1", + "picocolors": "^1.0.0", + "sade": "^1.7.4", + "svelte-preprocess": "^5.1.3", + "typescript": "^5.0.3" + }, + "bin": { + "svelte-check": "bin/svelte-check" + }, + "peerDependencies": { + "svelte": "^3.55.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0" + } + }, + "node_modules/svelte-eslint-parser": { + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-0.41.0.tgz", + "integrity": "sha512-L6f4hOL+AbgfBIB52Z310pg1d2QjRqm7wy3kI1W6hhdhX5bvu7+f0R6w4ykp5HoDdzq+vGhIJmsisaiJDGmVfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "postcss": "^8.4.39", + "postcss-scss": "^4.0.9" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + }, + "peerDependencies": { + "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0-next.191" + }, + "peerDependenciesMeta": { + "svelte": { + "optional": true + } + } + }, + "node_modules/svelte-hmr": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.16.0.tgz", + "integrity": "sha512-Gyc7cOS3VJzLlfj7wKS0ZnzDVdv3Pn2IuVeJPk9m2skfhcu5bq3wtIZyQGggr7/Iim5rH5cncyQft/kRLupcnA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^12.20 || ^14.13.1 || >= 16" + }, + "peerDependencies": { + "svelte": "^3.19.0 || ^4.0.0" + } + }, + "node_modules/svelte-preprocess": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.1.4.tgz", + "integrity": "sha512-IvnbQ6D6Ao3Gg6ftiM5tdbR6aAETwjhHV+UKGf5bHGYR69RQvF1ho0JKPcbUON4vy4R7zom13jPjgdOWCQ5hDA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@types/pug": "^2.0.6", + "detect-indent": "^6.1.0", + "magic-string": "^0.30.5", + "sorcery": "^0.11.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">= 16.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.10.2", + "coffeescript": "^2.5.1", + "less": "^3.11.3 || ^4.0.0", + "postcss": "^7 || ^8", + "postcss-load-config": "^2.1.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", + "pug": "^3.0.0", + "sass": "^1.26.8", + "stylus": "^0.55.0", + "sugarss": "^2.0.0 || ^3.0.0 || ^4.0.0", + "svelte": "^3.23.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0", + "typescript": ">=3.9.5 || ^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "coffeescript": { + "optional": true + }, + "less": { + "optional": true + }, + "postcss": { + "optional": true + }, + "postcss-load-config": { + "optional": true + }, + "pug": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/svelte/node_modules/is-reference": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz", + "integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/svg.draggable.js": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz", + "integrity": "sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw==", + "license": "MIT", + "dependencies": { + "svg.js": "^2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.easing.js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/svg.easing.js/-/svg.easing.js-2.0.0.tgz", + "integrity": "sha512-//ctPdJMGy22YoYGV+3HEfHbm6/69LJUTAqI2/5qBvaNHZ9uUFVC82B0Pl299HzgH13rKrBgi4+XyXXyVWWthA==", + "license": "MIT", + "dependencies": { + "svg.js": ">=2.3.x" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.filter.js": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/svg.filter.js/-/svg.filter.js-2.0.2.tgz", + "integrity": "sha512-xkGBwU+dKBzqg5PtilaTb0EYPqPfJ9Q6saVldX+5vCRy31P6TlRCP3U9NxH3HEufkKkpNgdTLBJnmhDHeTqAkw==", + "license": "MIT", + "dependencies": { + "svg.js": "^2.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.js": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/svg.js/-/svg.js-2.7.1.tgz", + "integrity": "sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA==", + "license": "MIT" + }, + "node_modules/svg.pathmorphing.js": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/svg.pathmorphing.js/-/svg.pathmorphing.js-0.1.3.tgz", + "integrity": "sha512-49HWI9X4XQR/JG1qXkSDV8xViuTLIWm/B/7YuQELV5KMOPtXjiwH4XPJvr/ghEDibmLQ9Oc22dpWpG0vUDDNww==", + "license": "MIT", + "dependencies": { + "svg.js": "^2.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.resize.js": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/svg.resize.js/-/svg.resize.js-1.4.3.tgz", + "integrity": "sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw==", + "license": "MIT", + "dependencies": { + "svg.js": "^2.6.5", + "svg.select.js": "^2.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.resize.js/node_modules/svg.select.js": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-2.1.2.tgz", + "integrity": "sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ==", + "license": "MIT", + "dependencies": { + "svg.js": "^2.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.select.js": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-3.0.1.tgz", + "integrity": "sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw==", + "license": "MIT", + "dependencies": { + "svg.js": "^2.6.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/tailwind-merge": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.4.0.tgz", + "integrity": "sha512-49AwoOQNKdqKPd9CViyH5wJoSKsCDjUlzL8DxuGp3P1FsGY36NJDAa18jLZcaHAUUuTj+JB8IAo8zWgBNvBF7A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.7.tgz", + "integrity": "sha512-rxWZbe87YJb4OcSopb7up2Ba4U82BoiSGUdoDr3Ydrg9ckxFS/YWsvhN323GMcddgU65QRy7JndC7ahhInhvlQ==", + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.0", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tailwindcss/node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/tailwindcss/node_modules/postcss-load-config": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", + "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.0.0", + "yaml": "^2.3.4" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/tailwindcss/node_modules/postcss-load-config/node_modules/lilconfig": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", + "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/tar/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tiny-glob": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz", + "integrity": "sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "globalyzer": "0.1.0", + "globrex": "^0.1.2" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/ts-api-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "license": "Apache-2.0" + }, + "node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "devOptional": true, + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", + "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "license": "MIT" + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici-types": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.13.0.tgz", + "integrity": "sha512-xtFJHudx8S2DSoujjMd1WeWvn7KKWFRESZTMeL1RptAYERu29D6jphMjjY+vn96jvN3kVPDNxU/E13VTaXj6jg==", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", + "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.1.2", + "picocolors": "^1.0.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/uuid-random": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/uuid-random/-/uuid-random-1.3.2.tgz", + "integrity": "sha512-UOzej0Le/UgkbWEO8flm+0y+G+ljUon1QWTEZOq1rnMAsxo2+SckbiZdKzAHHlVh6gJqI1TjC/xwgR50MuCrBQ==", + "license": "MIT" + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/vite": { + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.3.5.tgz", + "integrity": "sha512-MdjglKR6AQXQb9JGiS7Rc2wC6uMjcm7Go/NHNO63EwiJXfuk9PgqiP/n5IDJCziMkfw9n4Ubp7lttNwz+8ZVKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.39", + "rollup": "^4.13.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 + } + } + }, + "node_modules/vite-plugin-watch-and-run": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/vite-plugin-watch-and-run/-/vite-plugin-watch-and-run-1.7.0.tgz", + "integrity": "sha512-f6TUUxDvOeFPMJ1/NDK8N1y/65w8h4jPZGsuOOYVnaK4lkutN95rTNAunsr0fcgTVo1BRUMxDTY7iFlsGuiCig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@kitql/helpers": "0.8.9", + "micromatch": "4.0.5" + }, + "engines": { + "node": "^16.14 || >=18" + } + }, + "node_modules/vite-plugin-watch-and-run/node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/vitefu": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-0.2.5.tgz", + "integrity": "sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "license": "ISC", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/wide-align/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/wide-align/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.0.tgz", + "integrity": "sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zimmerframe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.2.tgz", + "integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/package.json b/package.json index 9706fe44ce8b23636c5a7dafd1bd0cb15ce16779..ba59698c5222751b5e1c9e9440e17eb4a4fd0128 100644 --- a/package.json +++ b/package.json @@ -1,58 +1,63 @@ -{ - "name": "esa-website", - "version": "0.0.1", - "private": true, - "scripts": { - "dev": "npm-run-all -p render-flyer-images typesafe-i18n vite", - "vite": "vite dev --host", - "optimize": "vite optimize", - "build": "vite build", - "preview": "vite preview", - "check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json", - "check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch", - "lint": "prettier --plugin-search-dir . --check . && eslint .", - "format": "prettier --plugin-search-dir . --write .", - "typesafe-i18n": "typesafe-i18n", - "render-flyer-images": "node src/lib/server/renderFlyerImages.js" - }, - "devDependencies": { - "@sveltejs/kit": "^1.20.4", - "@types/geojson": "^7946.0.11", - "@types/intl": "^1.2.0", - "@types/leaflet": "^1.9.6", - "@types/node": "^20.8.0", - "@typescript-eslint/eslint-plugin": "^6.7.3", - "@typescript-eslint/parser": "^6.7.3", - "autoprefixer": "^10.4.14", - "canvas": "^2.11.2", - "eslint": "^8.28.0", - "eslint-config-prettier": "^8.5.0", - "eslint-plugin-svelte": "^2.30.0", - "flowbite-svelte-icons": "^0.3.6", - "npm-run-all": "^4.1.5", - "pdfjs-dist": "^3.10.111", - "postcss": "^8.4.24", - "postcss-load-config": "^4.0.1", - "prettier": "^2.8.0", - "prettier-plugin-svelte": "^2.10.1", - "prisma": "^5.2.0", - "svelte": "^4.0.0", - "svelte-check": "^3.4.3", - "svelte-preprocess": "^5.0.3", - "tailwindcss": "^3.3.2", - "typesafe-i18n": "^5.24.3", - "typescript": "^5.0.0", - "vite": "^4.3.0", - "vite-plugin-watch-and-run": "^1.1.3" - }, - "type": "module", - "dependencies": { - "@sveltejs/adapter-node": "^1.3.1", - "@sveltejs/adapter-static": "^2.0.3", - "canvas2pdf": "github:joshua-gould/canvas2pdf", - "classnames": "^2.3.2", - "flowbite-svelte": "^0.44.4", - "intl": "^1.2.5", - "leaflet": "^1.9.4" - } -} +{ + "name": "esa", + "version": "0.0.1", + "private": true, + "scripts": { + "dev": "vite dev", + "build": "vite build", + "preview": "vite preview", + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", + "lint": "eslint ." + }, + "devDependencies": { + "@sveltejs/adapter-auto": "^3.0.0", + "@sveltejs/adapter-node": "^5.0.1", + "@sveltejs/kit": "^2.0.0", + "@sveltejs/vite-plugin-svelte": "^3.0.0", + "@types/eslint": "^9.6.0", + "@types/geojson": "^7946.0.14", + "@types/intl": "^1.2.2", + "@types/leaflet": "^1.9.8", + "@types/markdown-it": "^14.1.2", + "@types/node": "^22.1.0", + "@types/node-cron": "^3.0.11", + "@types/nodemailer": "^6.4.14", + "@types/pdfjs-dist": "^2.10.378", + "@typescript-eslint/eslint-plugin": "^8.0.1", + "@typescript-eslint/parser": "^8.0.1", + "autoprefixer": "^10.4.16", + "eslint": "^9.8.0", + "eslint-plugin-svelte": "^2.36.0-next.4", + "npm-run-all": "^4.1.5", + "postcss": "^8.4.32", + "postcss-load-config": "^6.0.1", + "svelte": "^5.0.0-next.1", + "svelte-check": "^3.6.0", + "tailwindcss": "^3.3.6", + "tslib": "^2.4.1", + "typescript": "^5.0.0", + "vite": "^5.0.3", + "vite-plugin-watch-and-run": "^1.6.0" + }, + "type": "module", + "dependencies": { + "@auth/core": "^0.34.2", + "@auth/sveltekit": "^1.4.2", + "canvas": "^2.11.2", + "classnames": "^2.5.1", + "feiertagejs": "^1.4.0", + "flowbite-svelte": "^0.46.15", + "flowbite-svelte-icons": "^2.0.0-next.3", + "ical-generator": "^7.2.0", + "intl": "^1.2.5", + "leaflet": "^1.9.4", + "markdown-it": "^14.1.0", + "modern-diacritics": "^2.3.1", + "node-cron": "^3.0.3", + "nodemailer": "^6.9.12", + "pdfjs-dist": "^4.1.392", + "postgres": "^3.4.3", + "sharp": "^0.33.4" + } +} diff --git a/postcss.config.cjs b/postcss.config.cjs index e68d4de2682cb6e51ebff26af677abba324798cd..e48cff588de3bbd2aa7250fe710a332ba01c811f 100644 --- a/postcss.config.cjs +++ b/postcss.config.cjs @@ -2,12 +2,12 @@ const tailwindcss = require("tailwindcss"); const autoprefixer = require("autoprefixer"); const config = { - plugins: [ - //Some plugins, like tailwindcss/nesting, need to run before Tailwind, - tailwindcss(), - //But others, like autoprefixer, need to run after, - autoprefixer, - ], + plugins: [ + //Some plugins, like tailwindcss/nesting, need to run before Tailwind, + tailwindcss(), + //But others, like autoprefixer, need to run after, + autoprefixer, + ], }; module.exports = config; diff --git a/prisma/schema.prisma b/prisma/schema.prisma deleted file mode 100644 index 2c232a72c291a62b8f1d8262f0997d400615c20d..0000000000000000000000000000000000000000 --- a/prisma/schema.prisma +++ /dev/null @@ -1,72 +0,0 @@ -// This is your Prisma schema file, -// learn more about it in the docs: https://pris.ly/d/prisma-schema - -generator client { - provider = "prisma-client-js" -} - -datasource db { - provider = "sqlite" - url = env("DATABASE_URL") -} - -model Config { - id Int @id @default(autoincrement()) - currentSemester String @default("WiSe 2023/24") - fresherWeekStart String @default("2023-10-02") - fresherWeekEnd String @default("2023-10-06") - tutorRegistrationOpen Boolean @default(false) - shirtSizes String @default("S;M;L;XL;2XL;3XL;4XL") - rallyeRegistrationOpen Boolean @default(false) - rallyeDate DateTime @default(now()) - rallyePreMeetingDate DateTime @default(now()) -} - -model TutorTraining { - id Int @id @default(autoincrement()) - date String - place String - maxParticipants Int -} - -model Tutor { - id Int @id @default(autoincrement()) - firstname String - lastname String - nickname String - birthday String - email String - phone String - address String - gender String - shirtSize String - studyProgram String - training String - coTutorWish String - mentor Boolean @default(false) - comment String @default("") - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt -} - -// When we already have enought tutor registrations for a study program -// we add the study program to this list, people can still register -// but they will be put on a waitlist and notified if we need more tutors -model Waitlist { - studyProgram String @unique -} - -model RallyeStationSupervisor { - id Int @id @default(autoincrement()) - firstname String - lastname String - birthday String - email String - phone String - morning Boolean - afternoon Boolean - coSupervisorWish String - comment String @default("") - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt -} diff --git a/src/app.d.ts b/src/app.d.ts index c42dc4a5d24a7f8ed8b24ec1e87b9be75a811d02..9f62f910c34a30c4b727f4856aaba432ce39e327 100644 --- a/src/app.d.ts +++ b/src/app.d.ts @@ -1,17 +1,31 @@ // See https://kit.svelte.dev/docs/types#app +// for information about these interfaces -import type { Locales } from "$lib/i18n/i18n-types"; +import type { Locale } from "$lib/i18n/i18n"; +import type { User } from "$lib/server/database/entities/User.entity"; -// for information about these interfaces declare global { namespace App { // interface Error {} interface Locals { - locale: Locales + locale: Locale; + user: User; } // interface PageData {} + // interface PageState {} // interface Platform {} } } +declare module "@auth/core/types" { + interface Session { + user: { + name: string, + userId: string, + email: string, + groups: string[], + }; + } +} + export {}; diff --git a/src/app.html b/src/app.html index 04d25d4233fa8d227ec4009febacd598c7cf7b46..e2d23026741d146109023613cabc737cca88697d 100644 --- a/src/app.html +++ b/src/app.html @@ -1,9 +1,9 @@ -<!DOCTYPE html> +<!doctype html> <html lang="%lang%"> <head> <meta charset="utf-8" /> <link rel="icon" href="%sveltekit.assets%/favicon.png" /> - <meta name="viewport" content="width=device-width" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> %sveltekit.head% </head> <body data-sveltekit-preload-data="hover" class="bg-white dark:bg-gray-900"> diff --git a/src/app.postcss b/src/app.pcss similarity index 100% rename from src/app.postcss rename to src/app.pcss diff --git a/src/hooks.server.ts b/src/hooks.server.ts index ae5d286e80166e88293a1947bcb7cd92e2851615..6a3fe7bd7d0e3bb3bb5a61e18b690baa51305aba 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -1,23 +1,88 @@ -import { detectLocale } from "$lib/i18n/i18n-util"; -import type { Handle } from "@sveltejs/kit"; -import { initAcceptLanguageHeaderDetector, initRequestCookiesDetector } from "typesafe-i18n/detectors"; +import { KEYCLOAK_ID, KEYCLOAK_ISSUER, KEYCLOAK_SECRET, KEYCLOAK_SCOPES, REQUIRED_GROUP } from '$env/static/private'; +import { locales, type Locale, defaultLocale } from '$lib/i18n/i18n'; +import { User } from '$lib/server/database/entities/User.entity'; +import { sendTrainingMails } from '$lib/server/mail'; +import { SvelteKitAuth } from '@auth/sveltekit'; +import KeycloakProvider from '@auth/sveltekit/providers/keycloak'; +import { error, redirect, type Handle, type RequestEvent } from '@sveltejs/kit'; +import { sequence } from '@sveltejs/kit/hooks'; +import cron from "node-cron"; -export const handle: Handle = async ({ event, resolve })=>{ - const locale = detectLocale(initRequestCookiesDetector({cookies: event.request.headers.get("Cookie") || ""}, "lang"), initAcceptLanguageHeaderDetector(event.request)); - event.locals.locale = locale; - if(event.url.pathname.startsWith("/admin/") || event.url.pathname === "/admin"){ - if(!isLoggedIn()){ - return new Response(undefined, { - status: 302, - headers: { - "location": "/login", +cron.schedule("0 10 * * *", async ()=>{ + await sendTrainingMails(); +}); + +function detectLocale(event: RequestEvent): Locale { + const cookie = event.cookies.get("lang"); + const paramLocale = event.url.searchParams.get("fb_locale"); // for og tag localization + if(paramLocale && locales.includes(paramLocale as Locale)) return paramLocale as Locale; + if(cookie && locales.includes(cookie as Locale)) return cookie as Locale; + const accept = event.request.headers.get("accept-language"); + if(accept) { + const langs = accept.split(",").map(lang => lang.split(";")[0].split("-")[0]); + for(const lang of langs) { + if(locales.includes(lang as Locale)) return lang as Locale; + } + } + return defaultLocale; +} + +const auth = SvelteKitAuth({ + providers: [ + KeycloakProvider({ + clientId: KEYCLOAK_ID, + clientSecret: KEYCLOAK_SECRET, + issuer: KEYCLOAK_ISSUER, + profile(profile){ + return { + name: profile.name ?? profile.preferred_username, + userId: profile.sub, + email: profile.email, + groups: profile.groups, + }; + }, + authorization: { + params: { + scope: KEYCLOAK_SCOPES, }, - }); + }, + }), + ], + callbacks: { + jwt({token, user}){ + if(user) return {...token, user}; + else return token; + }, + session({session, token}){ + if(token?.user) session.user = token.user; + return session; } } - return resolve(event, { transformPageChunk: ({html}) => html.replace("%lang%", locale) }); +}); +const handleAuthentication: Handle = async ({event, resolve})=>{ + return auth.handle({event, resolve}); }; -function isLoggedIn(){ - return true; // TODO: implement +const handleLocale: Handle = ({event, resolve})=>{ + const locale = detectLocale(event); + event.locals.locale = locale; + return resolve(event, { transformPageChunk: ({html})=>html.replace("%lang%", locale) }); } + +const handleAuthorization: Handle = async ({event, resolve})=>{ + if(event.url.pathname.startsWith("/admin")){ + const session = await event.locals.auth(); + if(!session) redirect(303, "/"); + if(REQUIRED_GROUP && !session.user.groups.includes(REQUIRED_GROUP)) error(403, `Missing required group ${REQUIRED_GROUP}`); + const user = await User.getById(session.user.userId); + if(!user) redirect(303, "/"); + event.locals.user = user; + } + return resolve(event); +}; + +export const handle = sequence( + handleAuthentication, + handleAuthorization, + handleLocale, +); diff --git a/src/lib/HelveticaNeueLTStd45Light.otf b/src/lib/HelveticaNeueLTStd45Light.otf deleted file mode 100644 index 1b27e9f67dd3c10b48e7696bef0cb0dd80514094..0000000000000000000000000000000000000000 Binary files a/src/lib/HelveticaNeueLTStd45Light.otf and /dev/null differ diff --git a/src/lib/components/AdminLayout.svelte b/src/lib/components/AdminLayout.svelte index 2df887032cc43f22d182c5ba6517ee3bae6eaaef..c1c8ef8f95b0237a7c17a30d598a183387d931e3 100644 --- a/src/lib/components/AdminLayout.svelte +++ b/src/lib/components/AdminLayout.svelte @@ -1,19 +1,46 @@ -<script> - import { NavBrand, NavHamburger, NavLi, NavUl, Navbar } from "flowbite-svelte"; - import { page } from "$app/stores"; -</script> - -<Navbar let:hidden let:toggle> - <NavBrand href="/admin">Fachschaft I/1</NavBrand> - <NavHamburger on:click={toggle} /> - <NavUl {hidden}> - <NavLi href="/admin" active={$page.route.id=="/admin"}>Home</NavLi> - <NavLi href="/admin/eswe" active={$page.route.id=="/admin/eswe"}>ESWE</NavLi> - <NavLi href="/admin/tutor" active={$page.route.id=="/admin/tutor"}>Tutoren</NavLi> - <NavLi href="/admin/rallye" active={$page.route.id=="/admin/rallye"}>Rallye</NavLi> - </NavUl> -</Navbar> - -<div class="container mx-auto px-4"> - <slot /> -</div> +<script lang="ts"> + import { signOut } from "@auth/sveltekit/client"; + import { DarkMode, Dropdown, DropdownDivider, DropdownItem, NavBrand, NavHamburger, NavLi, NavUl, Navbar } from "flowbite-svelte"; + import { ChevronDownOutline } from "flowbite-svelte-icons"; + import type { Snippet } from "svelte"; + + let { children }: {children: Snippet} = $props(); +</script> + +<Navbar> + <NavBrand href="/admin">Fachschaft I/1</NavBrand> + <NavHamburger /> + <NavUl> + <NavLi class="cursor-pointer">Einstellungen<ChevronDownOutline class="w-4 h-4 ms-1 text-primary-800 dark:text-white inline" /></NavLi> + <Dropdown class="w-44 z-20"> + <DropdownItem href="/admin">Allgemein</DropdownItem> + <DropdownItem href="/admin/schedule">Stundenpläne</DropdownItem> + <DropdownItem href="/admin/studyprogram">Studiengänge</DropdownItem> + <DropdownItem href="/admin/templates">Mail-Templates</DropdownItem> + <DropdownDivider /> + <DropdownItem href="/admin/user">Nutzer</DropdownItem> + </Dropdown> + <NavLi href="/admin/eswe">ESWE</NavLi> + <NavLi class="cursor-pointer">Tutoren<ChevronDownOutline class="w-4 h-4 ms-1 text-primary-800 dark:text-white inline" /></NavLi> + <Dropdown class="w-44 z-20"> + <DropdownItem href="/admin/tutor">Übersicht</DropdownItem> + <DropdownItem href="/admin/tutor/training">Schulungen</DropdownItem> + <DropdownItem href="/admin/tutor/mail">Mail senden</DropdownItem> + <DropdownDivider /> + <DropdownItem href="/admin/tutor/former">Alttutoren</DropdownItem> + </Dropdown> + <NavLi class="cursor-pointer">Rallye<ChevronDownOutline class="w-4 h-4 ms-1 text-primary-800 dark:text-white inline" /></NavLi> + <Dropdown class="w-44 z-20"> + <DropdownItem href="/admin/rallye/supervisor">Stationsbetreuer</DropdownItem> + <DropdownItem href="/admin/rallye/mail">Mail senden</DropdownItem> + </Dropdown> + <NavLi href="/admin/rabatte">Rabatte</NavLi> + <NavLi href="/admin/flyer">Flyer</NavLi> + <NavLi href="javascript:void 0" onclick={()=>signOut({callbackUrl: "/"})}>Logout</NavLi> + <li><DarkMode btnClass="block py-2 pr-4 pl-3 md:p-0 rounded text-gray-700 hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-primary-700 dark:text-gray-400 md:dark:hover:text-white dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent cursor-pointer w-full" /></li> + </NavUl> +</Navbar> + +<div class="container mx-auto px-4 mb-10 mt-4"> + {@render children()} +</div> diff --git a/src/lib/components/Image.svelte b/src/lib/components/Image.svelte new file mode 100644 index 0000000000000000000000000000000000000000..0ebef914c6d4238a0e333eea841e1df1ccb5cf9d --- /dev/null +++ b/src/lib/components/Image.svelte @@ -0,0 +1,50 @@ +<script lang="ts"> + import type { ImageMetadata } from "$lib/server/images"; + import { locale } from "$lib/i18n/i18n"; + import { onMount } from "svelte"; + + export let sizes: string | undefined = undefined; + export let src: ImageMetadata<any>; + export let autosize = false; + + function groupBy<T>(arr: T[], propExtractor: (t: T)=>string): Record<string, T[]> { + let result = {} as Record<string, T[]>; + for(let item of arr){ + let prop = propExtractor(item); + if(!result[prop]) result[prop] = []; + result[prop].push(item); + } + return result; + } + + let imgElement: HTMLImageElement; + + let defaultImage = src.sizes[src.sizes.length-1]; + let types = groupBy(src.sizes, s=>s.type); + let sources = Object.entries(types).map(([type, sizes])=>({ + type, + sizes: sizes.map(s=>`${s.url} ${s.width}w`).join(", ") + })); + let availableSizes = src.sizes.map(s=>s.width).sort((a,b)=>a-b).filter((v,i,a)=>a.indexOf(v)===i); + sizes = sizes || [...availableSizes.slice(0, availableSizes.length-1).map(s=>`(max-width: ${s}px) ${s}px`), `${availableSizes[availableSizes.length-1]}px`].join(", "); + + onMount(()=>{ + if(autosize){ + let observer = new ResizeObserver(entries=>{ + let entry = entries[0]; + let width = Math.ceil(entry.contentRect.width); + //let size = availableSizes.find(s=>s>=width) || availableSizes[availableSizes.length-1]; + sizes = `${width}px`; + }); + observer.observe(imgElement); + return ()=>observer.disconnect(); + } + }) +</script> + +<picture> + {#each sources as {type, sizes: srcset}} + <source {type} {srcset} /> + {/each} + <img bind:this={imgElement} src={defaultImage.url} {sizes} width="{src.width}" height="{src.height}" alt={src.description?.[$locale]} {...$$restProps} /> +</picture> diff --git a/src/lib/components/LocalizedText.svelte b/src/lib/components/LocalizedText.svelte index 973ac45548a9871614af518d7df20b3cab965dff..3ac141a670af121a1e05b8ab3b93770db42796d8 100644 --- a/src/lib/components/LocalizedText.svelte +++ b/src/lib/components/LocalizedText.svelte @@ -1,11 +1,18 @@ -<script> - import { LL } from '$lib/i18n/i18n-svelte'; +<script lang="ts"> + import { LL, LLL, type Translation, type TranslationFunction } from '$lib/i18n/i18n'; + import type { ObjectToDotProp } from '$lib/utils'; + import type { Snippet } from 'svelte'; - /** @type {string} */ - export let key; - export let params = {}; - $: text = traverse($LL, key)(params); - $: parts = parse(text); + let { key, params = [], children, ...snippets }: { + key: ObjectToDotProp<Translation, TranslationFunction>, + params: any[], + children: Snippet, + [key: string]: Snippet + } = $props(); + + // @ts-expect-error type instantiation is excessively deep and possibly infinite, happens when using ObjectToDotProp + let text = $derived(traverse($LL, key).raw); + let parts = $derived(parse(text)); /* * User input should never be passed to the translation function directly. * If user input needs to be inserted into the translation, use a slot instead. @@ -14,36 +21,34 @@ * A way to avoid this would be to parse the raw translation string and then only * format each part individually using the LLL function, but for that we'd need * to be able to get the raw translation string. - * - * import { locale } from "$lib/i18n/i18n-svelte"; - * import { i18nString } from "$lib/i18n/i18n-util"; - * $: LLL = i18nString($locale); - * LLL(part.text, params) // render part */ - $: if(!text) console.warn(`No translation found for key "${key}"`), console.trace(); - /** @param {object} object @param {string} path*/ - function traverse(object, path){ - let result = object; + $effect(()=>{ + if(!text){ + console.warn(`No translation found for key "${key}"`); + console.trace(); + } + }); + + function traverse<T extends object, R extends object>(object: T, path: ObjectToDotProp<T, R>): R { + let result = object as any; for(const part of path.split(".")){ result = result[part]; } return result; } + type TextPart = {type:"slot",name:string}|{type:"text",text:string}|{type:"tag",tag:string}; /** * Splits the input into text parts and slot parts. * * Syntax for slots: <>slotname<> * - * Example: "Please email <>1<> for information" -> [{type:"text",text:"Please email "},{type:"slot",name:"1"},{type:"text",text:" for information"}] - * @typedef {{type:"slot",name:string}|{type:"text",text:string}|{type:"tag",tag:string}} TextPart - * @param {string} input - * @returns {TextPart[]} + * Example: "Please email <>email<> for information" -> [{type:"text",text:"Please email "},{type:"slot",name:"email"},{type:"text",text:" for information"}] */ - function parse(input){ + function parse(input: string): TextPart[] { const tags = ["br"]; - let result = []; + let result = [] as TextPart[]; let start = 0; let tagStarted = false; let inTag = false; @@ -69,7 +74,7 @@ inTag = true; result.push({ type: "text", - text: input.substring(start, i-1) + text: LLL(input.substring(start, i-1), params), }); start = i+1; } @@ -99,29 +104,10 @@ {#if part.type === "text"} {part.text} {:else if part.type === "slot"} - <!--<slot name={part.name} />--> - <!-- slot names cant be dynamic, which is kinda sad --> - <!-- workaround: just have some dummy slots --> - {#if part.name === "1"} - <slot name="1" /> - {:else if part.name === "2"} - <slot name="2" /> - {:else if part.name === "3"} - <slot name="3" /> - {:else if part.name === "4"} - <slot name="4" /> - {:else if part.name === "5"} - <slot name="5" /> - {:else if part.name === "6"} - <slot name="6" /> - {:else if part.name === "7"} - <slot name="7" /> - {:else if part.name === "8"} - <slot name="8" /> - {:else if part.name === "9"} - <slot name="9" /> - {:else if part.name === ""} - <slot /> + {#if part.name===""} + {@render children()} + {:else} + {@render snippets[part.name]()} {/if} {:else if part.type === "tag"} <svelte:element this={part.tag} /> diff --git a/src/lib/components/MailInput.svelte b/src/lib/components/MailInput.svelte new file mode 100644 index 0000000000000000000000000000000000000000..aa4de07fdad46165f875b8b69f44546eb719b9d6 --- /dev/null +++ b/src/lib/components/MailInput.svelte @@ -0,0 +1,223 @@ +<script lang="ts"> + import Degree from "$lib/degrees"; + import Gender from "$lib/genders"; + import { locale, locales } from "$lib/i18n/i18n"; + import { variables, conditions, renderTemplate, parseTemplate, compileTemplate } from "$lib/mail"; + import type { StudyProgram } from "$lib/server/database/entities/StudyProgram.entity"; + import type { Tutor } from "$lib/server/database/entities/Tutor.entity"; + import type { TutorTraining } from "$lib/server/database/entities/TutorTraining.entity"; + import type { Localized } from "$lib/utils"; + import { Heading, Textarea, Helper, Accordion, AccordionItem, Span, Label, Select, Checkbox, List, Li, P, Input, Button, A } from "flowbite-svelte"; + + let { + text = $bindable(), + subject = $bindable(), + replyTo = $bindable("-"), + replyToOptions = [], + allowCustomReplyTo = false, + studyPrograms, + trainings, + onsubmit, + buttonText = "Senden", + buttonType = "button", + disabled = false, + buttonDisabled = disabled, + }: {text: Localized, subject: Localized, replyTo?: string|null, replyToOptions?: string[], allowCustomReplyTo?: boolean, studyPrograms: StudyProgram[], trainings: TutorTraining[], onsubmit: ()=>void, buttonText?: string, buttonType: HTMLButtonElement["type"], buttonDisabled?: boolean, disabled?: boolean} = $props(); + + let customReplyTo = $state(false); + + if(replyTo && replyTo !== "-" && !replyToOptions.includes(replyTo)) { + if(allowCustomReplyTo) { + customReplyTo = true; + } else { + replyTo = "-"; + } + } + + let exampleTutor: Tutor = $state({ + id: 0, + firstname: "Maximilian", + nickname: "Max", + lastname: "Mustermann", + email: "max.mustermann@rwth-aachen.de", + phone: "+49 123 456789", + address: "Musterstraße 1", + birthday: "1970-01-01", + coTutorWish: "Maxine Musterfrau", + degree: "Bachelor", + gender: "m", + mentor: false, + notes: "Montag noch nicht in Aachen", + shirtSize: "L", + studyProgram: studyPrograms[0], + trained: false, + dietaryRestriction: "vegan, Eiweißallergie", + training: trainings[0], + }); + + let [mailHtml, compilationError]: [string, string|null] = $derived.by(()=>{ + try { + return [renderTemplate(compileTemplate(parseTemplate(text), exampleTutor)), null] as const; + } catch(ex) { + return ["", (ex as Error).message] as const; + } + }); + let [compiledSubject, subjectError]: [string, string|null] = $derived.by(()=>{ + try { + return [compileTemplate(parseTemplate(subject), exampleTutor, ({de, en})=>`${de} / ${en}`), null] as const; + } catch(ex) { + return ["", (ex as Error).message] as const; + } + }); +</script> + +<Heading tag="h2" customSize="text-3xl font-bold" class="mt-6">Text</Heading> + +<Label class="mb-1"> + Antwort an + {#if customReplyTo} + <Input name="replyTo" bind:value={replyTo} placeholder="Antwortadresse" class="w-full" {disabled} /> + {:else} + <Select name="replyTo" value={replyTo} on:change={e=>{ + customReplyTo = e.target!.value==="custom"; + if(!customReplyTo) replyTo = e.target!.value; + else if(replyTo === "-") replyTo = ""; + }} {disabled}> + <option value={"-"}>keine</option> + {#each replyToOptions as option} + <option value={option}>{option}</option> + {/each} + {#if allowCustomReplyTo} + <option value="custom">Benutzerdefiniert</option> + {/if} + </Select> + {/if} +</Label> +<div class="flex gap-2 mb-1"> + {#each locales as locale} + <Label class="w-full"> + Betreff ({locale}) + <Input name="subject[{locale}]" bind:value={subject[locale]} oninput={e=>subject=Object.assign(subject,{[locale]:e.target.value})} {disabled} /> + </Label> + {/each} +</div> +{#if subjectError} +<Helper color="red">{subjectError}</Helper> +{/if} +{#each locales as locale} +<Label class="mb-1"> + Text ({locale}) + <Textarea name="text[{locale}]" bind:value={text[locale]} class="min-h-36" {disabled} /> +</Label> +{/each} +{#if compilationError} +<Helper color="red">{compilationError}</Helper> +{/if} + +<Heading tag="h2" customSize="text-3xl font-bold" class="mt-6">Vorschau</Heading> + +<Span>{compiledSubject}</Span> +<iframe srcdoc={mailHtml} title="Email Preview" class="bg-gray-200 w-full mb-4" onload={function(){this.style.height=Math.ceil(this.contentWindow.document.documentElement.getBoundingClientRect().height)+"px";}}></iframe> + +<Button color="green" onclick={onsubmit} type={buttonType} class="mb-3 min-w-40" disabled={buttonDisabled}>{buttonText}</Button> + +<Accordion> + <AccordionItem> + <Span slot="header">Beispiel-Tutor</Span> + <Label> + Studiengang + <Select value={exampleTutor.studyProgram.id} on:change={e=>exampleTutor.studyProgram=studyPrograms.find(s=>s.id==e.target!.value)}> + {#each studyPrograms as studyProgram} + <option value={studyProgram.id}>{studyProgram.name[$locale]}</option> + {/each} + </Select> + </Label> + <Label> + Abschluss + <Select bind:value={exampleTutor.degree}> + {#each Object.entries(Degree) as degree} + <option value={degree[0]}>{degree[1][$locale]}</option> + {/each} + </Select> + </Label> + <Label> + Geschlecht + <Select bind:value={exampleTutor.gender}> + {#each Object.entries(Gender) as [key, {de: name}]} + <option value={key}>{name}</option> + {/each} + </Select> + </Label> + <Label> + <Checkbox bind:checked={exampleTutor.mentor}>Mentor</Checkbox> + </Label> + <Label> + <Checkbox bind:checked={exampleTutor.trained}>Geschult</Checkbox> + </Label> + <Label> + Schulung + <Select value={exampleTutor.training?.id ?? -1} on:change={e=>exampleTutor.training=trainings.find(t=>t.id===e.target!.value)}> + <option value={-1}>keine</option> + {#each trainings as training} + <option value={training.id}>{training.date}</option> + {/each} + </Select> + </Label> + <Label> + Co-Tutor-Wunsch + <Input bind:value={exampleTutor.coTutorWish} /> + </Label> + </AccordionItem> + <AccordionItem> + <Span slot="header">Variablen</Span> + <List tag="ul" position="outside" class="ml-4"> + {#each variables as variable} + <Li><code>{"{{"}{variable.name}{"}}"}</code> - {variable.description}</Li> + {/each} + </List> + </AccordionItem> + <AccordionItem> + <Span slot="header">Bedingungen</Span> + <List tag="ul" position="outside" class="ml-4"> + {#each conditions as condition} + <Li> + <code>{"{{if "}{condition.name}{#if condition.arguments}{" ...args"}{/if}{"}}"}</code> - {condition.description} + {#if condition.arguments} + <br />Argumente: {condition.arguments} + {/if} + </Li> + {/each} + </List> + </AccordionItem> + <AccordionItem> + <Span slot="header">Syntax</Span> + <P> + In den E-Mail-Eingaben können Variablen und bedingte Anweisungen verwenden werden, um den Inhalt dynamisch anzupassen. Die folgenden Regeln gelten: + <List tag="ul" position="outside" class="ml-4 mt-2"> + <Li> + <b>Variablen</b> + <List tag="ul" position="outside" class="ml-6"> + <Li>Variablen werden durch doppelte geschweifte Klammern gekennzeichnet: <code>{"{{"}variablenname{"}}"}</code>.</Li> + </List> + </Li> + <Li> + <b>Bedingte Anweisungen</b> + <List tag="ul" position="outside" class="ml-6"> + <Li>Bedingter Text kann mit <code>{"{{"}if bedingung{"}}"}</code> angezeigt werden.</Li> + <Li>Argumente für Bedingungen werden durch Leerzeichen getrennt nach der Bedingung geschrieben: <code>{"{{"}if bedingung arg1 arg2{"}}"}</code>.</Li> + <Li>Ende eines if-Blocks wird mit <code>{"{{/if}}"}</code> markiert.</Li> + <Li>Alternativer Text kann mit <code>{"{{"}else{"}}"}</code> definiert werden, um den Fall zu behandeln, dass die Bedingung falsch ist.</Li> + <Li>Zusätzliche Bedingungen innerhalb eines if-Blocks können mit <code>{"{{"}else if bedingung arg1 arg2{"}}"}</code> geprüft werden. Der Inhalt wird angezeigt, wenn die vorherige Bedingung falsch ist, aber die Bedingung des <code>else if</code> selbst wahr ist.</Li> + <Li>Negation einer Bedingung kann mit <code>{"{{"}if not bedingung arg1 arg2{"}}"}</code> durchgeführt werden.</Li> + </List> + </Li> + <Li> + <b>Markdown-Unterstützung</b> + <List tag="ul" position="outside" class="ml-6"> + <Li>Das Textfeld unterstützt Markdown zur Formatierung des Textes. Einen Leitfaden dazu ist <A href="https://www.markdownguide.org/basic-syntax/" target="_blank" rel="noopener">hier</A> zu finden.</Li> + </List> + </Li> + </List> + </P> + </AccordionItem> +</Accordion> diff --git a/src/lib/components/MainLayout.svelte b/src/lib/components/MainLayout.svelte index a7fe80bc932149743bcfc8cc31725dee3e3552c0..648f75e4bedbe611d15a0ef290690f0919509701 100644 --- a/src/lib/components/MainLayout.svelte +++ b/src/lib/components/MainLayout.svelte @@ -1,100 +1,76 @@ -<script> - import { Button, DarkMode, Dropdown, DropdownItem, Footer, FooterCopyright, FooterLink, FooterLinkGroup, Modal, NavBrand, NavHamburger, NavLi, NavUl, Navbar, P } from 'flowbite-svelte'; - import { Icon } from "flowbite-svelte-icons"; - import { page } from '$app/stores'; - import { browser } from '$app/environment'; - import { setLocale, LL } from '$lib/i18n/i18n-svelte'; - import { loadLocaleAsync } from '$lib/i18n/i18n-util.async'; - import { onDestroy } from 'svelte'; - - $: pathname = $page.url.pathname; - - /** @type {import('$lib/i18n/i18n-types').Locales} */ - export let locale; - $: { - if(browser){ - document.cookie = `lang=${locale};path=/;SameSite=Strict`; - } - (async ()=>{ - await loadLocaleAsync(locale); - setLocale(locale); - })(); - } - - const firstPath = $page.url.pathname; - let showModal = false; - let unsubscribe = ()=>{}; - unsubscribe = !browser || firstPath !== "/rabatte" || localStorage.getItem("didShowDiscountLeaveWarning") === "1" ? ()=>{} : page.subscribe(page=>{ - if(page.url.pathname !== "/rabatte"){ - unsubscribe(); - showModal = true; - } - }); - function acceptWarning(){ - showModal = false; - localStorage.setItem("didShowDiscountLeaveWarning", "1"); - } - onDestroy(unsubscribe); - - let showLanguageDropdown = false; -</script> - -<div class="h-screen flex flex-col"> - <Navbar let:hidden let:toggle> - <NavBrand href="/">{$LL.Navbar.Branding()}</NavBrand> - <NavHamburger on:click={toggle} /> - <NavUl {hidden} activeUrl={pathname}> - <!--<NavLi href="/" active={pathname==="/"}>{$LL.Navbar.Home()}</NavLi>--> - <!--<NavLi id="nav-ersti" class="cursor-pointer"><Chevron aligned>Ersti</Chevron></NavLi>--> - <NavLi href="/information">{$LL.Navbar.Information()}</NavLi> - <!--<NavLi href="/rabatte">{$LL.Navbar.Discounts()}</NavLi> - <NavLi href="/eswe">{$LL.Navbar.ESWE()}</NavLi> - <NavLi href="/flyer">{$LL.Navbar.Flyer()}</NavLi>--> - <!-- - <NavLi href="/eswe" active={pathname==="/eswe"}>{$LL.Navbar.ESWE()}</NavLi> - <NavLi href="/tutor" active={pathname==="/tutor"||pathname.startsWith("/tutor/")}>{$LL.Navbar.Tutor()}</NavLi> - <NavLi href="/rallye" active={pathname==="/rallye"}>{$LL.Navbar.Rally()}</NavLi> - <NavLi href="/rabatte" active={pathname==="/rabatte"}>{$LL.Navbar.Discounts()}</NavLi> - --> - <NavLi id="language-select" class="cursor-pointer">{$LL.Navbar.Language()}<Icon name="chevron-down-outline" size="xs" class="ml-2 text-primary-800 dark:text-white inline" /></NavLi> - <li><DarkMode btnClass="block py-2 pr-4 pl-3 md:p-0 rounded text-gray-700 hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-primary-700 dark:text-gray-400 md:dark:hover:text-white dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent cursor-pointer w-full" /></li> - <!--<Dropdown triggeredBy="#nav-ersti" class="w-44 z-20"> - <DropdownItem href="/ersti/erstiwoche" active={pathname==="/ersti/erstiwoche"}>Erstiwoche</DropdownItem> - <DropdownItem href="/ersti/eswe" active={pathname==="/ersti/eswe"}>Erstsemesterwochenende</DropdownItem> - </Dropdown>--> - <Dropdown triggeredBy="#language-select" class="w-36" bind:open={showLanguageDropdown}> - <DropdownItem class={locale==="de"?"text-primary-700 dark:text-primary-500 hover:text-primary-900 dark:hover:text-primary-400":""} on:click={()=>{ - locale = "de"; - showLanguageDropdown = false; - }}>{$LL.Navbar.German()}</DropdownItem> - <DropdownItem class={locale==="en"?"text-primary-700 dark:text-primary-500 hover:text-primary-900 dark:hover:text-primary-400":""} on:click={()=>{ - locale = "en"; - showLanguageDropdown = false; - }}>{$LL.Navbar.English()}</DropdownItem> - </Dropdown> - </NavUl> - </Navbar> - - <div class="mb-auto"> - <div class="container relative mx-auto px-4 max-w-6xl mb-10"> - <slot /> - </div> - </div> - <Modal title={$LL.DiscountLeaveWarning.Title()} open={showModal} dismissable={false}> - <P>{$LL.DiscountLeaveWarning.Content()}</P> - <svelte:fragment slot="footer"> - <Button color="alternative" on:click={acceptWarning}>{$LL.DiscountLeaveWarning.Accept()}</Button> - </svelte:fragment> - </Modal> - - <Footer> - <FooterCopyright by={$LL.Footer.Copyright()} copyrightMessage=""/> - <FooterLinkGroup ulClass="flex flex-wrap items-center mt-3 text-sm text-gray-500 dark:text-gray-400 sm:mt-0"> - <FooterLink href="/impressum">{$LL.Footer.Legal()}</FooterLink> - <!-- - <FooterLink href="/datenschutz">{$LL.Footer.PrivacyPolicy()}</FooterLink> - <FooterLink href="/login">{$LL.Footer.Login()}</FooterLink> - --> - </FooterLinkGroup> - </Footer> -</div> +<script lang="ts"> + import { page } from "$app/stores"; + import { L, LL, locale, locales, setLocale, type Translation } from "$lib/i18n/i18n"; + import { signIn } from "@auth/sveltekit/client"; + import { DarkMode, Dropdown, DropdownItem, Footer, FooterCopyright, FooterLink, FooterLinkGroup, NavBrand, NavHamburger, NavLi, NavUl, Navbar } from "flowbite-svelte"; + import { ChevronDownOutline } from "flowbite-svelte-icons"; + import type { Snippet } from "svelte"; + + let { headerLinks, children }: {headerLinks: string[], children: Snippet} = $props(); + + function processActiveUrl(url: string) { + return url; + } + + const activeUrl = $derived(processActiveUrl($page.url.pathname)); +</script> + +<svelte:head> + <meta property="og:title" content="Erstiarbeit der Fachschaft I/1"> <!-- TODO i18n --> + <meta property="og:description" content=""> <!-- TODO add description --> + <meta property="og:image" content="/favicon.png" itemprop="image"> + <meta property="og:locale" content={$LL.ogLocale()}> + {#each locales as lang} + {#if lang !== $locale} + <meta property="og:locale:alternate" content={L[lang].ogLocale()}> + {/if} + {/each} + <meta property="og:type" content="website"> + <meta property="og:url" content="https://esa.fsmpi.rwth-aachen.de"> <!-- TODO set correct url --> + <meta property="og:site_name" content="Erstiarbeit der Fachschaft I/1"> <!-- TODO i18n --> +</svelte:head> + +<div class="h-screen flex flex-col"> + <Navbar> + <NavBrand href="/">{$LL.Navbar.Branding()}</NavBrand> + <NavHamburger /> + <NavUl {activeUrl}> + <!--<NavLi href="/information">{$LL.Navbar.Information()}</NavLi> + <NavLi href="/rabatte">{$LL.Navbar.Discounts()}</NavLi> + <NavLi href="/eswe">{$LL.Navbar.ESWE()}</NavLi> + <NavLi href="/flyer">{$LL.Navbar.Flyer()}</NavLi> + <NavLi href="/tutor">{$LL.Navbar.Tutor()}</NavLi>--> + {#each headerLinks as link} + <NavLi href={link}>{$LL.Navbar.Links[link as keyof Translation["Navbar"]["Links"]]()}</NavLi> + {/each} + <NavLi href="/upload">Momente teilen</NavLi> <!-- TODO i18n --> + <NavLi class="cursor-pointer"> + {$LL.Navbar.Language()}<ChevronDownOutline class="w-4 h-4 ms-1 text-primary-800 dark:text-white inline" /> + </NavLi> + <Dropdown class="w-36 z-20"> + {#each locales as locale} + <DropdownItem on:click={()=>setLocale(locale)}>{$LL.Navbar.Languages[locale]()}</DropdownItem> + {/each} + </Dropdown> + <li><DarkMode btnClass="block py-2 pr-4 pl-3 md:p-0 rounded text-gray-700 hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-primary-700 dark:text-gray-400 md:dark:hover:text-white dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent cursor-pointer w-full" /></li> + </NavUl> + </Navbar> + + <div class="mb-auto"> + <div class="container relative mx-auto px-4 max-w-5xl mb-10 mt-4"> + {@render children()} + </div> + </div> + + <Footer footerType="socialmedia"> + <div class="sm:flex sm:items-center sm:justify-between"> + <FooterCopyright by={$LL.Footer.Branding()} copyrightMessage="" year={2024} /> + <FooterLinkGroup class="flex mt-4 sm:justify-center sm:mt-0" ulClass="flex flex-wrap items-center mt-3 text-sm text-gray-500 dark:text-gray-400 sm:mt-0"> + <FooterLink href="/impressum">{$LL.Footer.Legal()}</FooterLink> + <FooterLink href="/datenschutz">{$LL.Footer.PrivacyPolicy()}</FooterLink> + <FooterLink href="javascript:void 0;" onclick={()=>signIn("keycloak", {callbackUrl: "/login"})}>{$LL.Footer.Login()}</FooterLink> + <!-- TODO maybe link website and instagram here? --> + </FooterLinkGroup> + </div> + </Footer> +</div> diff --git a/src/lib/components/Message.svelte b/src/lib/components/Message.svelte new file mode 100644 index 0000000000000000000000000000000000000000..7947acfa133c706ec1c12a019fecaa4fed5140f7 --- /dev/null +++ b/src/lib/components/Message.svelte @@ -0,0 +1,54 @@ +<script lang="ts"> + import { removeMessage, type Message } from "$lib/messages"; + import { CloseButton } from "flowbite-svelte"; + import { scale } from "svelte/transition"; + + let { message }: { message: Message } = $props(); + + const bgClasses = { + success: "bg-green-400 dark:bg-green-700", + info: "bg-blue-400 dark:bg-blue-700", + error: "bg-red-400 dark:bg-red-600", + warning: "bg-yellow-300 dark:bg-yellow-700", + }; + const textClasses = { + success: "text-green-900 dark:text-green-200", + info: "text-blue-900 dark:text-blue-200", + error: "text-red-900 dark:text-red-200", + warning: "text-yellow-900 dark:text-yellow-200", + }; + const borderClasses = { + success: "border-green-700 dark:border-green-400", + info: "border-blue-700 dark:border-blue-400", + error: "border-red-700 dark:border-red-400", + warning: "border-yellow-700 dark:border-yellow-400", + }; + const barClasses = { + success: "bg-green-700 dark:bg-green-400", + info: "bg-blue-700 dark:bg-blue-400", + error: "bg-red-700 dark:bg-red-400", + warning: "bg-yellow-700 dark:bg-yellow-400", + }; + const closeClasses = { + success: "text-green-900 dark:text-green-200", + info: "text-blue-900 dark:text-blue-200", + error: "text-red-900 dark:text-red-200", + warning: "text-yellow-900 dark:text-yellow-200", + }; +</script> + +<style> + .bar { + animation: fillBar linear forwards; + } + @keyframes fillBar { + 0% { width: 0%; } + 100% { width: 100%; } + } +</style> + +<div class="transition-opacity group {bgClasses[message.type]} {borderClasses[message.type]} opacity-90 hover:opacity-100 grid-cols-[1fr_2em] relative py-1 pl-3 border rounded grid gap-2 items-center mb-2" in:scale out:scale> + <div class={textClasses[message.type]}>{message.text}</div> + <CloseButton class="float-right relative right-1 !bg-opacity-0 hover:!bg-opacity-10 {closeClasses[message.type]}" on:click={()=>removeMessage(message)} /> + <div class="bar h-[3px] bottom-0 left-0 absolute group-hover:[animation-play-state:paused] {barClasses[message.type]}" style="animation-duration:{message.duration||10}s" onanimationend={()=>removeMessage(message)}></div> +</div> diff --git a/src/lib/components/MessageList.svelte b/src/lib/components/MessageList.svelte new file mode 100644 index 0000000000000000000000000000000000000000..6d47267844c8e1e8579f4f9ff2a9715f8c779c9a --- /dev/null +++ b/src/lib/components/MessageList.svelte @@ -0,0 +1,10 @@ +<script lang="ts"> + import { messages } from "$lib/messages"; + import Message from "./Message.svelte"; +</script> + +<div class="fixed top-4 right-4 w-[60vw] z-[99999]"> + {#each $messages as message (message.id)} + <Message {message} /> + {/each} +</div> diff --git a/src/lib/components/TutorFAQ.svelte b/src/lib/components/TutorFAQ.svelte index 5710c9c852833251e5de790143cdf2ce49d749e5..6e0c834dfc801fca9dc2e7ef98316795bbd362dd 100644 --- a/src/lib/components/TutorFAQ.svelte +++ b/src/lib/components/TutorFAQ.svelte @@ -1,6 +1,6 @@ <script> import { Accordion, AccordionItem, Span, P, A, Heading } from "flowbite-svelte"; - import LL from "$lib/i18n/i18n-svelte"; + import { LL } from "$lib/i18n/i18n"; import LocalizedText from "./LocalizedText.svelte"; export let title = $LL.Tutor.FAQ.Headline(); diff --git a/src/lib/csv.ts b/src/lib/csv.ts new file mode 100644 index 0000000000000000000000000000000000000000..6e02aa76cf44f1654dc08656dd11493ed930f905 --- /dev/null +++ b/src/lib/csv.ts @@ -0,0 +1,13 @@ +export function toCSV<T extends Record<string, unknown>>(data: T[], fields: (keyof T)[]): string { + return fields.map(f=>escape(f as string)).join(",") + "\n" + data.map(o=>{ + return fields.map(f=>{ + const val = o[f]; + if(val === undefined || val === null) return ""; + else return escape(String(o[f])); + }).join(",") + "\n"; + }).join(""); +} + +function escape(s: string): string { + return `"${s.replaceAll('"', '""')}"`; +} diff --git a/src/lib/degrees.ts b/src/lib/degrees.ts index 6318fcf1c458502afc7615b3e94b7aeca3213f5c..7123c57b0afa75503b776452f207618a7231c23d 100644 --- a/src/lib/degrees.ts +++ b/src/lib/degrees.ts @@ -1,7 +1,13 @@ -// When changing, also edit $LL.Degree for all locales! -export const degrees = [ - "Bachelor", - "Master", -] as const; -export default degrees; -export type Degree = typeof degrees[number]; +import type { Localized } from "./utils"; + +export const Degree = { + Bachelor: { + de: "Bachelor", + en: "Bachelor", + }, + Master: { + de: "Master", + en: "Master", + }, +} satisfies Record<string, Localized>; +export default Degree; diff --git a/src/lib/genders.ts b/src/lib/genders.ts index 8dcbaf41a796a967c3ed8021f804cee4c8d6a05f..09dffdf8100a8caa0141696f5718991c706befe3 100644 --- a/src/lib/genders.ts +++ b/src/lib/genders.ts @@ -1,9 +1,21 @@ -// When changing, also edit $LL.Gender for all locales! -export const genders = [ - "männlich", - "weiblich", - "divers", - "keine Angabe", -] as const; -export default genders; -export type Gender = typeof genders[number]; +import type { Localized } from "./utils"; + +export const Gender = { + m: { + de: "männlich", + en: "male", + }, + f: { + de: "weiblich", + en: "female", + }, + d: { + de: "divers", + en: "diverse", + }, + "-": { + de: "keine Angabe", + en: "prefer not to say", + }, +} satisfies Record<string, Localized>; +export default Gender; diff --git a/src/lib/i18n/de/index.ts b/src/lib/i18n/de.ts similarity index 63% rename from src/lib/i18n/de/index.ts rename to src/lib/i18n/de.ts index 8947daaf80bcaa8dec5ec6c7860a3d50f0e4a6b5..d7faa25b326169acdd5b12eeb5c21d80f613b697 100644 --- a/src/lib/i18n/de/index.ts +++ b/src/lib/i18n/de.ts @@ -1,94 +1,112 @@ -import type { Translation } from "../i18n-types"; - -const de = { - StudyProgram: { // Studiengänge, definiert in $lib/studyPrograms.ts - Physik: "Physik", - Mathematik: "Mathematik", - Informatik: "Informatik", - Wirtschaftsmathematik: "Wirtschaftsmathematik", - Lehramt: "Lehramt", - }, - Gender: { // bei Anmeldung auswählbare Geschlechter, definiert in $lib/genders.ts - "männlich": "männlich", - weiblich: "weiblich", - divers: "divers", - "keine Angabe": "keine Angabe", - }, - Degree: { // bei Anmeldung auswählbare Abschlüsse, definiert in $lib/degrees.ts - Bachelor: "Bachelor", - Master: "Master", +export default { + ogLocale: "de_DE", + PageTitle: { + "/(non-admin)/datenschutz": "Datenschutzerklärung", + "/(non-admin)/eswe": "Erstsemesterwochenende", + "/(non-admin)/flyer": "Flyer", + "/(non-admin)/impressum": "Impressum", + "/(non-admin)/information": "Informationen", + "/(non-admin)/rabatte": "Rabatte", + "/(non-admin)/tutor": "Tutor:in werden", + "/(non-admin)/tutor/register": "Anmeldung als Tutor:in", + "/admin": "Einstellungen", + "/admin/eswe": "ESWE-Einstellungen", + "/admin/flyer": "Flyer verwalten", + "/admin/rabatte": "Rabatte verwalten", + "/admin/rabatte/[id]": "Rabatt bearbeiten: {discount.title}", + "/admin/schedule": "Stundenpläne verwalten", + "/admin/schedule/[id]": "Stundenplan bearbeiten: {schedule.studyProgram.name.de}", + "/admin/schedule/fonts": "Schriftarten verwalten", + "/admin/setup": "Instanz einrichten", + "/admin/studyprogram": "Studiengänge verwalten", + "/admin/templates": "E-Mail-Vorlagen verwalten", + "/admin/tutor": "Tutor:innen verwalten", + "/admin/tutor/[id]": "Tutor:in bearbeiten: {tutor.firstname} {tutor.lastname}", + "/admin/tutor/mail": "E-Mail senden", + "/admin/user": "Berechtigungen verwalten", }, - Navbar: { // Navigationsleiste oben auf jeder Seite - Branding: "Fachschaft I/1", // Text ganz links - // Einträge mit Links - Home: "Startseite", - ESWE: "Erstsemesterwochenende", - Flyer: "Flyer", - Information: "Informationen", - Tutor: "Tutor", - Rally: "Rallye", - Discounts: "Rabatte", + Navbar: { + Branding: "Fachschaft I/1", + Links: { + "/information": "Informationen", + "/rabatte": "Rabatte", + "/eswe": "Erstsemesterwochenende", + "/flyer": "Flyer", + "/tutor": "Tutor", + }, Language: "Sprache", - // auswählbare Sprachen - German: "Deutsch", - English: "Englisch", + Languages: { + de: "Deutsch", + en: "Englisch", + }, }, - Footer: { // Footer am Ende jeder Seite - Copyright: "Fachschaft Mathematik/Physik/Informatik", // Text ganz links (ohne Copyright und Jahr) - // klickbare Links auf der rechten Seite + Footer: { + Branding: "Fachschaft Mathematik/Physik/Informatik", Legal: "Impressum", PrivacyPolicy: "Datenschutzerklärung", Login: "Login", }, - PageTitle: { // Titel, die unter entsprechenden Pfaden angezeigt werden sollen, /(non-admin) kann ignoriert werden - "/(non-admin)": "Startseite", - "/(non-admin)/eswe": "Erstsemesterwochenende", - "/(non-admin)/tutor": "Tutor werden", - "/(non-admin)/tutor/register": "Tutoranmeldung", - "/(non-admin)/information": "Informationen", - "/(non-admin)/rabatte": "Rabatte", - "/(non-admin)/flyer": "Flyer", - "fallback": "", // TODO: Add a fallback title + Information: { // Text auf /information + Schedule: "Ablauf der Erstiwoche", // Titel von /information + StudyProgram: "Studiengang", // Name der Auswahlbox für den Studiengang + StudyPrograms: { // im Dropdown auswählbare Studiengänge + informatik: "Bachelor Informatik", + physik: "Bachelor Physik", + mathematik: "Bachelor Mathematik", + wirtschaftsmathematik: "Bachelor Wirtschaftsmathematik", + lehramt: "Bachelor Lehramt", + "master-deutsch": "Master (deutsch)", + "master-englisch": "Master (englisch)", + }, + ScheduleAlt: "Stundenplan Erstiwoche {semester} {studyProgram}", // Alternativtext für das Bild, welches für den ausgewählten Studiengang angezeigt wird + Download: "Herunterladen", // Button zum Herunterladen des Studenplans + DownloadFilename: "erstiwoche-{semester|lowercase|sanitize}-stundenplan-{studyProgram|lowercase|sanitize}", // Dateiname, unter welchem der Stundenplan gespeichert wird + Information: "Informationen zum Studium", // Titel der ES-Info-Sektion + Short: "Kurzfassung", // Linktext für die Kurzfassung + Long: "Langfassung", // Linktext für die Langfassung + ShortDownloadFilename: "Erstsemester-Information {semester|sanitize} (kurz)", // Dateiname für die heruntergeladene Kurzfassung + LongDownloadFilename: "Erstsemester-Information {semester|sanitize} (lang)", // Dateiname für die heruntergeladene Langfassung }, Tutor: { // Informationen zur Anmeldung als Erstitutor - Headline: "Tutor Anmeldung", + Headline: "Anmeldung als Tutor:in", Greeting: "Hallo!", - Paragraph: "Du bist volljährig und möchtest Ersti-Tutor/in der Fachschaft Mathematik/Physik/Informatik im Wintersemester {yearOfSemester:string} werden? Hier bist du richtig!", + Paragraph: "Du bist volljährig und möchtest Ersti-Tutor:in der Fachschaft Mathematik/Physik/Informatik im {semester} werden? Hier bist du richtig!", WhatTitle: "Was mache ich da?", - WhatContent: "Zu deinen Aufgaben gehört die Betreuung einer Gruppe von ca. 15 Erstis während dieser Woche. Du bist erste/r Ansprechpartner/in für alle Fragen der Erstis rund ums Studium, die Uni und die Stadt. Gemeinsam mit einem/r weiteren Tutor/in bist du dafür verantwortlich, die Ersti zu den Veranstaltungen der Ersti-Woche (Kneipenabend, Rallye, Projekttage, ...) zu begleiten, sie in RWTHmoodle und RWTH Online einzuweisen, sie bei der Anmeldung für ihre Vorlesungen und Übungen zu unterstützen und natürlich dafür zu sorgen, dass sie viel Spaß haben.", + WhatContent: "Zu deinen Aufgaben gehört die Betreuung einer Gruppe von ca. 15-25 Erstis während dieser Woche. Du bist erste:r Ansprechpartner:in für alle Fragen der Erstis rund ums Studium, die Uni und die Stadt. Gemeinsam mit einem/r weiteren Tutor:in bist du dafür verantwortlich, die Ersti zu den Veranstaltungen der Ersti-Woche (Kneipenabend, Rallye, Projekttage, ...) zu begleiten, sie in RWTHmoodle und RWTHOnline einzuweisen, sie bei der Anmeldung für ihre Vorlesungen und Übungen zu unterstützen und natürlich dafür zu sorgen, dass sie viel Spaß haben.", NeedTitle: "Was brauche ich dafür?", - NeedContent: "In erster Linie Offenheit, Freude am Umgang mit Menschen und ein gewisses Verantwortungsbewusstsein. Außerdem musst du – außer, du warst in den letzten Jahren bereits Tutor/in – verpflichtend an einer von vier Tutschulungen teilnehmen. Falls du einen Co-Tutor angegeben hast: Es ist es nicht notwendig, dass ihr dieselbe Tutschulung besucht.", + NeedContent: "In erster Linie Offenheit, Freude am Umgang mit Menschen und ein gewisses Verantwortungsbewusstsein. Außerdem musst du – außer, du warst in den letzten Jahren bereits Tutor:in – verpflichtend an einer Tutschulung teilnehmen. Falls du eine/n Co-Tutor:in angegeben hast: Es ist es nicht notwendig, dass ihr dieselbe Tutschulung besucht.", WhenTitle: "Wann?", - WhenContent: "Die Ersti-Woche findet vom {fresherWeek:string} statt. Tutschulungen dauern jeweils von 9:45 bis ca. 18:00 Uhr und finden an folgenden Terminen statt:", + WhenContent: "Die Ersti-Woche findet vom {fresherWeek|dateRangeLong} statt. Tutschulungen dauern jeweils von {start} bis ca. {end} Uhr und finden an folgenden Terminen statt:", + NoDates: "Die Termine für die Tutschulungen stehen noch nicht fest", FurtherQuestions: "Falls ihr noch Fragen habt, schreibt an <><>!", - ClosingFormula: "Liebe Grüße\nDie ErstSemesterInnen-Arbeit (ESA) der I/1", + ClosingFormula: "Liebe Grüße\nDie Erstsemester-Arbeit (ESA) der I/1", RegistrationButton: "Zur verbindlichen Anmeldung", RegistrationClosed: "Die Anmeldung ist aktuell geschlossen", FAQ: { Headline: "FAQ", Money: { Question: "Gibt's dafür Geld?", - Answer: "Es gibt eine Aufwandsentschädigung von 50€ pro Tutor, also 100€ pro Tutorium. Wir freuen uns, wenn ihr diese Summe dazu einsetzt, etwas für eure Erstis zu organisieren. Vielleicht ein gemeinsames Frühstück, eine gemeinsame Aktivität oder auch nur eine Runde Cocktails für alle beim Kneipenabend.", + Answer: "Es gibt eine Aufwandsentschädigung von 50€ pro Tutor:in, also 100€ pro Tutorium. Wir freuen uns, wenn ihr diese Summe dazu einsetzt, etwas für eure Erstis zu organisieren. Vielleicht ein gemeinsames Frühstück, eine gemeinsame Aktivität oder auch nur eine Runde Cocktails für alle beim Kneipenabend.", }, Rally: { Question: "Ich will lieber eine Station bei der Ersti-Rallye betreuen! Wo melde ich mich?", Answer: "Schreib uns einfach eine Mail an <><>.", }, Mentoring: { - Question: "Ich studiere Informatik und will Tutor/in werden. Wie läuft das mit dem Mentoring?", - Answer: "Ein Tutorium in der Informatik muss von einem/r Mentor/in und einem/r Tutorin betreut werden. Bei Fragen melde dich einfach beim Mentoring: <><>.", + Question: "Ich studiere Informatik und will Tutor:in werden. Wie läuft das mit dem Mentoring?", + Answer: "Ein Tutorium in der Informatik muss von einem/r Mentor:in und einem/r Tutor:in betreut werden. Bei Fragen melde dich einfach beim Mentoring: <><>.", }, Trainings: { - Question: "Ich kann bei keinem der Termine für die Tutschulungen da sein. Kann ich trotzdem Tutor/in werden?", + Question: "Ich kann bei keinem der Termine für die Tutschulungen da sein. Kann ich trotzdem Tutor:in werden?", Answer: "Leider musst du an einer Tutschulung teilgenommen haben und wir können da keine Ausnahmen machen. Aber vielleicht klappt es ja nächstes Jahr?", }, Support: { - Question: "Ich will die ESA bei ihrer Arbeit unterstützen, aber nicht (nur) als Tutor/in. Was kann ich tun?", + Question: "Ich will die ESA bei ihrer Arbeit unterstützen, aber nicht (nur) als Tutor:in. Was kann ich tun?", Answer: "Fühl dich herzlich eingeladen, zu einer ESA-Sitzung zu kommen und mitzuplanen! Wir treffen uns immer donnerstags um 20 Uhr in den Räumen der Fachschaft im Augustinerbach 2A.", }, }, SignUp: { // Registrierungsseite - Headline: "Tutor Anmeldung", + Headline: "Anmeldung als Tutor:in", FirstName: "Vorname", LastName: "Nachname", NickName: "Rufname", @@ -101,15 +119,17 @@ const de = { Address: "Adresse", Gender: "Geschlecht", ShirtSize: "T-Shirt-Größe", + DietaryRestriction: "Essgewohnheiten und Unverträglichkeiten", + DietaryRestrictionHelper: "Bei Veranstaltungen wie bspw. der Tutschulung gibt es Verpflegung. Unverträglichkeiten und Einschränkungen wie Veganismus zu kennen hilft uns, allen etwas anbieten zu können.", StudyProgram: "Studiengang", - StudyProgramWaitlist: "Für den Studiengang {studyProgram:string} werden voraussichtlich keine weiteren Tutoren benötigt. Wenn du dich trotzdem anmeldest, wirst du auf die Warteliste gesetzt und wir melden uns bei dir, falls wir dich doch noch brauchen.", + StudyProgramWaitlist: "Für den Studiengang {studyProgram} werden voraussichtlich keine weiteren Tutoren benötigt. Wenn du dich trotzdem anmeldest, wirst du auf die Warteliste gesetzt und wir melden uns bei dir, falls wir dich doch noch brauchen.", Degree: "Abschluss", DegreeHelper: "Der Abschluss, den du gerade anstrebst", Training: "Schulung", AlreadyTrained: "Bereits geschult", CoTutorWish: "Wunsch-Co-Tutor (optional)", - CoTutorWishHelper: "Ihr müsst beide das gleiche Fach studieren. In der Informatik muss ein/e Mentor/in dabei sein.", - IsMentor: "Ich bin Mentor in der Informatik", + CoTutorWishHelper: "Ihr müsst beide das gleiche Fach studieren. In der Informatik muss ein:e Mentor:in dabei sein.", + IsMentor: "Ich bin Mentor:in in der Informatik", ReadPrivacyPolicy: "Ich habe die <><> gelesen und bin damit einverstanden", PrivacyPolicy: "Datenschutzerklärung", Submit: "Anmelden", @@ -120,36 +140,19 @@ const de = { MissingFields: "Bitte fülle alle Pflichtfelder aus", InvalidDate: "Das angegebene Datum ist ungültig", NotOldEnough: "Du musst mindestens 18 Jahre alt sein", - InvalidEmail: "Bitte gib eine gültige RWTH-Mail-Adresse an", + InvalidEmail: [ + "Vermutlich hast du deinen Benutzernamen (ab123456) angegeben, bitte gib deine RWTH-Mail-Adresse an", + "Bitte gib deine RWTH-Mail-Adresse an", + ], InvalidGender: "Bitte wähle ein Geschlecht aus", InvalidShirtSize: "Bitte wähle eine T-Shirt-Größe aus", InvalidStudyProgram: "Bitte wähle einen Studiengang aus", InvalidDegree: "Bitte wähle einen Abschluss aus", InvalidTraining: "Bitte wähle eine Schulung aus", + InvalidForm: "Bitte prüfe alle Eingaben", }, }, }, - Information: { // Text auf /information - Schedule: "Ablauf der Erstiwoche", // Titel von /information - StudyProgram: "Studiengang", // Name der Auswahlbox für den Studiengang - StudyPrograms: { // im Dropdown auswählbare Studiengänge - //informatik: "Bachelor Informatik", - //physik: "Bachelor Physik", - //mathematik: "Bachelor Mathematik", - //wirtschaftsmathematik: "Bachelor Wirtschaftsmathematik", - //lehramt: "Bachelor Lehramt", - "master-deutsch": "Master (deutsch)", - "master-englisch": "Master (englisch)", - }, - ScheduleAlt: "Stundenplan Erstiwoche {semester:string} {studyProgram:string}", // Alternativtext für das Bild, welches für den ausgewählten Studiengang angezeigt wird - Download: "Herunterladen", // Button zum Herunterladen des Studenplans - DownloadFilename: "erstiwoche-{semester:string|sanitize}-stundenplan-{studyProgram:string|lowercase|sanitize}", // Dateiname, unter welchem der Stundenplan gespeichert wird - Information: "Informationen zum Studium", // Titel der ES-Info-Sektion - Short: "Kurzfassung", // Linktext für die Kurzfassung - Long: "Langfassung", // Linktext für die Langfassung - ShortDownloadFilename: "Erstsemester-Information {semester:string|sanitize} (kurz)", // Dateiname für die heruntergeladene Kurzfassung - LongDownloadFilename: "Erstsemester-Information {semester:string|sanitize} (lang)", // Dateiname für die heruntergeladene Langfassung - }, Discounts: { // Text auf /rabatte Map: { CurrentLocation: "Dein Standort", // Titel für den Marker, der die aktuelle Position anzeigt @@ -163,7 +166,7 @@ const de = { OpeningSoon: "Öffnet demnächt", Open: "Geöffnet", Closed: "Geschlossen", - Time: "{start:string} - {end:string} Uhr", // wie die Zeiten in den Öffnungszeiten formatiert werden + Time: "{start} - {end} Uhr", // wie die Zeiten in den Öffnungszeiten formatiert werden TimeClosed: "geschlossen", // Anzeigetext, wenn er Laden an einem Tag komplett geschlossen ist Weekdays: ["Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag"], // Namen der Tage, wie sie in der Tabelle angezeigt werden sollen, startend bei Sonntag }, @@ -173,25 +176,22 @@ const de = { NoResults: "Für deine aktuelle Suche existieren keine Angebote", // Anzeigetext, wenn kein Ergebnis der Suche entspricht }, }, - DiscountLeaveWarning: { // Warnung, wenn man von /rabatte woanders hin navigiert - Title: "Achtung!", - Content: "Die Seite zur Übersicht der Rabatte steht allen Fachschaften und somit allen Erstis zur Verfügung. Alle weiteren Seiten dieser Website sind nur für Erstis der Fachschaft Mathematik/Physik/Informatik (inklusive derer Lehramtserstis und WiMa-Erstis) gedacht. Solltest du dieser Fachschaft nicht zugehören, ist der restliche Inhalt für dich nicht relevant.", - Accept: "Verstanden", // Knopf zum zur-Kenntnis-nehmen - }, - ESWE: { + ESWE: { // Informationen zum Erstsemesterwochenende + // da das ESWE auf Deutsch abgehalten wird, sind die Texte auch auf Deutsch und keine Übersetzung notwendig Headline: "Erstsemesterwochenende", + OnlyAvailableInGerman: "", // auf der deutschen Seite nicht gebraucht }, Flyer: { Notice: "Einige Flyer haben mehr als eine Seite. Wenn du auf das Bild klickst, kommst du zum vollständigen PDF.", }, - Legal: { // Text auf /impressum + Legal: { Headline: "Impressum", Address: "Adresse", Phone: "Telefon", EmailGeneral: "E-Mail (allgemein)", EmailESA: "E-Mail (ESA)", Internet: "Internet", - LiabilityNote: "Haftungshinweis: Trotz sorgfältiger inhaltlicher Kontrolle übernehmen wir keine Haftung für die Inhalte externer Links. Für den Inhalt der verlinkten Seiten sind ausschließlich deren BetreiberInnen verantwortlich.", + LiabilityNote: "Haftungshinweis: Trotz sorgfältiger inhaltlicher Kontrolle übernehmen wir keine Haftung für die Inhalte externer Links. Für den Inhalt der verlinkten Seiten sind ausschließlich deren Betreiber:innen verantwortlich.", }, PrivacyPolicy: { // Text auf /datenschutz Headline: "Datenschutzerklärung", @@ -199,7 +199,7 @@ const de = { Paragraphs: [ { Headline: "Geltungsbereich", - Content: "Diese Datenschutzerklärung soll die Nutzer dieser Website gemäß Bundesdatenschutzgesetz und Telemediengesetz über die Art, den Umfang und den Zweck der Erhebung und Verwendung personenbezogener Daten durch den Websitebetreiber die Studierendenschaft der RWTH, Fachschaft Mathematik/Physik/Informatik Teilkörperschaft d.ö.R, Templergraben 55, 52056 Aachen informieren. Der Anbieter hat organisatorische und technische Sicherheitsmaßnahmen getroffen, um sicher zu stellen, dass die Vorschriften der Datenschutzgesetze eingehalten werden und zufällige oder vorsätzliche Manipulationen, Verlust, Zerstörung oder der Zugriff unberechtigter Personen verhindert wird. Bedenken Sie, dass die Datenübertragung im Internet grundsätzlich mit Sicherheitslücken bedacht sein kann. Ein vollumfänglicher Schutz vor dem Zugriff durch Fremde ist daher nicht realisierbar.", + Content: "Diese Datenschutzerklärung soll die Nutzer dieser Website gemäß Bundesdatenschutzgesetz und Telemediengesetz über die Art, den Umfang und den Zweck der Erhebung und Verwendung personenbezogener Daten durch den Websitebetreiber, die Studierendenschaft der RWTH, Fachschaft Mathematik/Physik/Informatik, Teilkörperschaft d.ö.R, Templergraben 55, 52056 Aachen, informieren. Der Anbieter hat organisatorische und technische Sicherheitsmaßnahmen getroffen, um sicher zu stellen, dass die Vorschriften der Datenschutzgesetze eingehalten werden und zufällige oder vorsätzliche Manipulationen, Verlust, Zerstörung oder der Zugriff unberechtigter Personen verhindert wird. Bedenken Sie, dass die Datenübertragung im Internet grundsätzlich mit Sicherheitslücken bedacht sein kann. Ein vollumfänglicher Schutz vor dem Zugriff durch Fremde ist daher nicht realisierbar.", }, { Headline: "Cookies", @@ -211,7 +211,7 @@ const de = { }, { Headline: "Anmeldung", - Content: "Mit der Anmeldung als Tutor stimmt der Nutzer zu, dass seine personenbezogenen Daten (Vor- und Nachname, E-Mail-Adresse, Studienfach, Telefonnummer, usw.) vom Webseitenbetreiber erhoben und gespeichert werden. Die Daten werden dazu verwendet, um die Erstsemesterarbeit (ESA) an der RWTH zu planen und durchzuführen, sowie um den Nutzer mit Informationen über seine Tätigkeit als Tutor zu versorgen. Zu diesem Zwecke werden die Daten teilweise auch an das Hochschulweite ESA-Team weitergegeben. Abgesehen davon erfolgt keine Weitergabe an Dritte.", + Content: "Mit der Anmeldung als Tutor stimmt der Nutzer zu, dass seine personenbezogenen Daten (Vor- und Nachname, E-Mail-Adresse, Studienfach, Telefonnummer, usw.) vom Webseitenbetreiber erhoben und gespeichert werden. Die Daten werden dazu verwendet, um die Erstsemesterarbeit (ESA) an der RWTH zu planen und durchzuführen, sowie um den Nutzer mit Informationen über seine Tätigkeit als Tutor zu versorgen. Zu diesem Zwecke werden die Daten teilweise auch an das hochschulweite ESA-Team weitergegeben. Abgesehen davon erfolgt keine Weitergabe an Dritte.", }, { Headline: "Umgang mit Kontaktdaten", @@ -228,6 +228,4 @@ const de = { ], Footer: "Sofern nicht anders angegeben, ist die männliche Form in diesem Text nicht geschlechterspezifisch gemeint, sondern wurde aus Gründen der Lesbarkeit gewählt.", }, -} satisfies Translation; - -export default de; +} diff --git a/src/lib/i18n/en/index.ts b/src/lib/i18n/en.ts similarity index 80% rename from src/lib/i18n/en/index.ts rename to src/lib/i18n/en.ts index fa69efb5801c10fafd75eafb366662ba3ed7fc40..75a38f9990e6ab5eece3a040a859f00ed5b636b1 100644 --- a/src/lib/i18n/en/index.ts +++ b/src/lib/i18n/en.ts @@ -1,68 +1,88 @@ -import type { BaseTranslation } from "../i18n-types"; +import type { LocalizedString, Translation } from "./i18n"; -const en = { - StudyProgram: { // Studiengänge, definiert in $lib/studyPrograms.ts - Physik: "Physics", - Mathematik: "Mathematics", - Informatik: "Computer Science", - Wirtschaftsmathematik: "Business Mathematics", - Lehramt: "Teacher Educating Program", - }, - Gender: { // bei Anmeldung auswählbare Geschlechter, definiert in $lib/genders.ts - "männlich": "male", - weiblich: "female", - divers: "other", - "keine Angabe": "prefer not to say", - }, - Degree: { // bei Anmeldung auswählbare Abschlüsse, definiert in $lib/degrees.ts - Bachelor: "bachelor's", - Master: "master's", +export default { + ogLocale: "en_US", + PageTitle: { + "/(non-admin)/datenschutz": "Privacy Policy", + "/(non-admin)/eswe": "Freshers' Weekend", + "/(non-admin)/flyer": "Flyer", + "/(non-admin)/impressum": "Legal Notice", + "/(non-admin)/information": "Information", + "/(non-admin)/rabatte": "Discounts", + "/(non-admin)/tutor": "Become a Tutor", + "/(non-admin)/tutor/register": "Tutor Registration", + "/admin": "Settings", + "/admin/eswe": "ESWE Settings", + "/admin/flyer": "Manage Flyers", + "/admin/rabatte": "Manage Discounts", + "/admin/rabatte/[id]": "Edit Discount: {discount.title}", + "/admin/schedule": "Manage Schedules", + "/admin/schedule/[id]": "Edit Schedule: {schedule.studyProgram.name.en}", + "/admin/schedule/fonts": "Manage Fonts", + "/admin/setup": "Setup Instance", + "/admin/studyprogram": "Manage Study Programs", + "/admin/templates": "Manage Mail Templates", + "/admin/tutor": "Manage Tutors", + "/admin/tutor/[id]": "Edit Tutor: {tutor.firstname} {tutor.lastname}", + "/admin/tutor/mail": "Send Email", + "/admin/user": "Manage Permissions", }, - Navbar: { // Navigationsleiste oben auf jeder Seite - Branding: "Student Council I/1", // Text ganz links - // Einträge mit Links - Home: "Home", - ESWE: "Freshers' Weekend", - Flyer: "Flyer", - Information: "Information", - Tutor: "Tutor", - Rally: "Rally", - Discounts: "Discounts", + Navbar: { + Branding: "Student Council I/1", + Links: { + "/information": "Information", + "/rabatte": "Discounts", + "/eswe": "Freshers' Weekend", + "/flyer": "Flyer", + "/tutor": "Tutor", + }, Language: "Language", - // auswählbare Sprachen - German: "German", - English: "English", + Languages: { + de: "German", + en: "English", + }, }, - Footer: { // Footer am Ende jeder Seite - Copyright: "Student Council Mathematics/Physics/Computer Science", // Text ganz links (ohne Copyright und Jahr) - // klickbare Links auf der rechten Seite + Footer: { + Branding: "Student Council Mathematics/Physics/Computer Science", Legal: "Legal", PrivacyPolicy: "Privacy Policy", Login: "Login", }, - PageTitle: { // Titel, die unter entsprechenden Pfaden angezeigt werden sollen, /(non-admin) kann ignoriert werden - "/(non-admin)": "Home", - "/(non-admin)/eswe": "Freshers' Weekend", - "/(non-admin)/tutor": "Become a Tutor", - "/(non-admin)/tutor/register": "Tutor Registration", - "/(non-admin)/information": "Information", - "/(non-admin)/rabatte": "Discounts", - "/(non-admin)/flyer": "Flyer", - "fallback": "", // TODO: Add a fallback title + Information: { // Text auf /information + Schedule: "Schedule of Freshers' Week", // Titel von /information + StudyProgram: "Study Program", // Name der Auswahlbox für den Studiengang + StudyPrograms: { // im Dropdown auswählbare Studiengänge + informatik: "Computer Science B.S.", + physik: "Physics B.S.", + mathematik: "Mathematics B.S.", + wirtschaftsmathematik: "Business Mathematics B.S.", + lehramt: "Teacher Educating Program B.S.", + "master-deutsch": "Master (German)", + "master-englisch": "Master (English)", + }, + ScheduleAlt: "Schedule Freshers' Week {semester} {studyProgram}", // Alternativtext für das Bild, welches für den ausgewählten Studiengang angezeigt wird + Download: "Download", // Button zum Herunterladen des Studenplans + DownloadFilename: "freshers-week-{semester|lowercase|sanitize}-schedule-{studyProgram|lowercase|sanitize}", // Dateiname, unter welchem der Stundenplan gespeichert wird + Information: "Information for freshers", // Titel der ES-Info-Sektion + Short: "Short version (German only)", // Linktext für die Kurzfassung + Long: "Long version (German only)", // Linktext für die Langfassung + ShortDownloadFilename: "Freshers Information {semester|sanitize} (short, German)", // Dateiname für die heruntergeladene Kurzfassung + LongDownloadFilename: "Freshers Information {semester|sanitize} (long, German)", // Dateiname für die heruntergeladene Langfassung }, Tutor: { // Informationen zur Anmeldung als Erstitutor Headline: "Tutor Registration", // TODO translate beginning here Greeting: "Hallo!", - Paragraph: "Du bist volljährig und möchtest Ersti-Tutor/in der Fachschaft Mathematik/Physik/Informatik im Wintersemester {yearOfSemester:string} werden? Hier bist du richtig!", + Paragraph: "Du bist volljährig und möchtest Ersti-Tutor/in der Fachschaft Mathematik/Physik/Informatik im Wintersemester {semester} werden? Hier bist du richtig!", WhatTitle: "Was mache ich da?", WhatContent: "Zu deinen Aufgaben gehört die Betreuung einer Gruppe von ca. 15 Erstis während dieser Woche. Du bist erste/r Ansprechpartner/in für alle Fragen der Erstis rund ums Studium, die Uni und die Stadt. Gemeinsam mit einem/r weiteren Tutor/in bist du dafür verantwortlich, die Ersti zu den Veranstaltungen der Ersti-Woche (Kneipenabend, Rallye, Projekttage, ...) zu begleiten, sie in RWTHmoodle und RWTH Online einzuweisen, sie bei der Anmeldung für ihre Vorlesungen und Übungen zu unterstützen und natürlich dafür zu sorgen, dass sie viel Spaß haben.", NeedTitle: "Was brauche ich dafür?", NeedContent: "In erster Linie Offenheit, Freude am Umgang mit Menschen und ein gewisses Verantwortungsbewusstsein. Außerdem musst du – außer, du warst in den letzten Jahren bereits Tutor/in – verpflichtend an einer von vier Tutschulungen teilnehmen. Falls du einen Co-Tutor angegeben hast: Es ist es nicht notwendig, dass ihr dieselbe Tutschulung besucht.", WhenTitle: "Wann?", - WhenContent: "Die Ersti-Woche findet vom {fresherWeek:string} statt. Tutschulungen dauern jeweils von 9:45 bis ca. 18:00 Uhr und finden an folgenden Terminen statt:", + WhenContent: "Die Ersti-Woche findet vom {fresherWeek|dateRangeLong} statt. Tutschulungen dauern jeweils von {start} bis ca. {end} Uhr und finden an folgenden Terminen statt:", + NoDates: "Die Termine für die Tutschulungen stehen noch nicht fest", FurtherQuestions: "Falls ihr noch Fragen habt, schreibt an <><>!", - ClosingFormula: "Liebe Grüße\nDie ErstSemesterInnen-Arbeit (ESA) der I/1", + ClosingFormula: "Liebe Grüße\nDie Erstsemester-Arbeit (ESA) der I/1", RegistrationButton: "Zur verbindlichen Anmeldung", RegistrationClosed: "Die Anmeldung ist aktuell geschlossen", FAQ: { @@ -102,6 +122,8 @@ const en = { Address: "Address", Gender: "Gender", ShirtSize: "T-Shirt Size", + DietaryRestriction: "Dietary Restrictions", + DietaryRestrictionHelper: "We offer food for events like the tutor trainings. Knowing about your dietary restrictions helps us to plan the food accordingly.", StudyProgram: "Study Program", StudyProgramWaitlist: "We probably won't need any more tutors for {studyProgram|lowercase} this year. If you sign up anyway, we will put you on the waiting list and contact you if we need more tutors.", Degree: "Degree", @@ -121,36 +143,19 @@ const en = { MissingFields: "Please fill out all required fields", InvalidDate: "The date you entered is invalid", NotOldEnough: "You have to be at least 18 years old", - InvalidEmail: "Please enter a valid RWTH email address", + InvalidEmail: [ + "You probably entered your username (ab123456), please enter your RWTH email address", + "Please enter your RWTH email address", + ], InvalidGender: "Please select a gender", - InvalidPhone: "Please enter a valid phone number", InvalidShirtSize: "Please enter a valid shirt size", + InvalidStudyProgram: "Please select a study program", InvalidDegree: "Please enter a valid degree", InvalidTraining: "Please enter a valid training", + InvalidForm: "Please check all your input", }, }, }, - Information: { // Text auf /information - Schedule: "Schedule of Freshers' Week", // Titel von /information - StudyProgram: "Study Program", // Name der Auswahlbox für den Studiengang - StudyPrograms: { // im Dropdown auswählbare Studiengänge - //informatik: "Computer Science B.S.", - //physik: "Physics B.S.", - //mathematik: "Mathematics B.S.", - //wirtschaftsmathematik: "Business Mathematics B.S.", - //lehramt: "Teacher Educating Program B.S.", - "master-deutsch": "Master (German)", - "master-englisch": "Master (English)", - }, - ScheduleAlt: "Schedule Freshers' Week {semester:string} {studyProgram:string}", // Alternativtext für das Bild, welches für den ausgewählten Studiengang angezeigt wird - Download: "Download", // Button zum Herunterladen des Studenplans - DownloadFilename: "freshers-week-{semester:string|sanitize}-schedule-{studyProgram:string|lowercase|sanitize}", // Dateiname, unter welchem der Stundenplan gespeichert wird - Information: "Information for freshers", // Titel der ES-Info-Sektion - Short: "Short version (German only)", // Linktext für die Kurzfassung - Long: "Long version (German only)", // Linktext für die Langfassung - ShortDownloadFilename: "Freshers Information {semester|sanitize} (short, German)", // Dateiname für die heruntergeladene Kurzfassung - LongDownloadFilename: "Freshers Information {semester|sanitize} (long, German)", // Dateiname für die heruntergeladene Langfassung - }, Discounts: { // Text auf /rabatte Map: { CurrentLocation: "Your Location", // Titel für den Marker, der die aktuelle Position anzeigt @@ -164,7 +169,7 @@ const en = { OpeningSoon: "Opening soon", Open: "Open", Closed: "Closed", - Time: "{start:string} - {end:string}", // wie die Zeiten in den Öffnungszeiten formatiert werden + Time: "{start} - {end}", // wie die Zeiten in den Öffnungszeiten formatiert werden TimeClosed: "closed", // Anzeigetext, wenn er Laden an einem Tag komplett geschlossen ist Weekdays: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], // Namen der Tage, wie sie in der Tabelle angezeigt werden sollen, startend bei Sonntag }, @@ -174,13 +179,9 @@ const en = { NoResults: "There are no discounts for your search", // Anzeigetext, wenn kein Ergebnis der Suche entspricht }, }, - DiscountLeaveWarning: { // Warnung, wenn man von /rabatte woanders hin navigiert - Title: "Attention!", - Content: "The discount overview is accessible to freshers of all student councils. All other pages on this website are intended only for freshers of the Student Council Mathematics/Physics/Computer Science (including teacher training and business mathematics freshers). If you do not belong to this student council, the remaining content is not relevant to you.", - Accept: "Acknowledged", // Knopf zum zur-Kenntnis-nehmen - }, ESWE: { Headline: "Freshers' Weekend", + OnlyAvailableInGerman: "The Freshers' Weekend is an event for bachelor freshers only that will be held completely in German. For more information look at the German version of this page.", }, Flyer: { Notice: "Some flyers have more than one page. You can get to the full PDF by clicking on the image.", @@ -195,6 +196,7 @@ const en = { LiabilityNote: "Liability note: We are not liable for the content of external links. The content of linked pages is the exclusive responsibility of their operators.", }, PrivacyPolicy: { // Text auf /datenschutz + // TODO translate Headline: "Datenschutzerklärung", Disclaimer: "This version is a translation of the German version. In case of doubt, the German version is authoritative.", Paragraphs: [ @@ -229,6 +231,4 @@ const en = { ], Footer: "Sofern nicht anders angegeben, ist die männliche Form in diesem Text nicht geschlechterspezifisch gemeint, sondern wurde aus Gründen der Lesbarkeit gewählt.", }, -} satisfies BaseTranslation; - -export default en; +} satisfies Translation<LocalizedString>; diff --git a/src/lib/i18n/formatters.ts b/src/lib/i18n/formatters.ts deleted file mode 100644 index 47fe4896242624539095451cd2668ec5cb25ff19..0000000000000000000000000000000000000000 --- a/src/lib/i18n/formatters.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { FormattersInitializer } from "typesafe-i18n"; -import type { Locales, Formatters } from "./i18n-types"; -import { /*date,*/ lowercase } from "typesafe-i18n/formatters"; - -// eslint-disable-next-line @typescript-eslint/no-unused-vars -export const initFormatters: FormattersInitializer<Locales, Formatters> = (locale: Locales) => { - - const formatters: Formatters = { - //dayMonth: date(locale, { day: '2-digit', month: 'short' }), - //date: date(locale, { day: '2-digit', month: 'short', year: 'numeric' }), - //dateLong: date(locale, { day: '2-digit', month: 'long', year: 'numeric', weekday: 'long' }), - lowercase, - sanitize: (input: string)=>input.replace(/[^a-zA-Z0-9_ ()äöüÄÖÜß-]/g, "-"), - }; - - return formatters; -}; diff --git a/src/lib/i18n/i18n-svelte.ts b/src/lib/i18n/i18n-svelte.ts deleted file mode 100644 index 6cdffb3e51264a33a9310157d40d187a01f69b47..0000000000000000000000000000000000000000 --- a/src/lib/i18n/i18n-svelte.ts +++ /dev/null @@ -1,12 +0,0 @@ -// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. -/* eslint-disable */ - -import { initI18nSvelte } from 'typesafe-i18n/svelte' -import type { Formatters, Locales, TranslationFunctions, Translations } from './i18n-types' -import { loadedFormatters, loadedLocales } from './i18n-util' - -const { locale, LL, setLocale } = initI18nSvelte<Locales, Translations, TranslationFunctions, Formatters>(loadedLocales, loadedFormatters) - -export { locale, LL, setLocale } - -export default LL diff --git a/src/lib/i18n/i18n-types.ts b/src/lib/i18n/i18n-types.ts deleted file mode 100644 index af2f8d08fcdbb9eefd5cb09c2aa0de5001135436..0000000000000000000000000000000000000000 --- a/src/lib/i18n/i18n-types.ts +++ /dev/null @@ -1,1414 +0,0 @@ -// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. -/* eslint-disable */ -import type { BaseTranslation as BaseTranslationType, LocalizedString, RequiredParams } from 'typesafe-i18n' - -export type BaseTranslation = BaseTranslationType -export type BaseLocale = 'de' - -export type Locales = - | 'de' - | 'en' - -export type Translation = RootTranslation - -export type Translations = RootTranslation - -type RootTranslation = { - StudyProgram: { - /** - * Physik - */ - Physik: string - /** - * Mathematik - */ - Mathematik: string - /** - * Informatik - */ - Informatik: string - /** - * Wirtschaftsmathematik - */ - Wirtschaftsmathematik: string - /** - * Lehramt - */ - Lehramt: string - } - Gender: { - /** - * männlich - */ - männlich: string - /** - * weiblich - */ - weiblich: string - /** - * divers - */ - divers: string - /** - * keine Angabe - */ - 'keine Angabe': string - } - Degree: { - /** - * Bachelor - */ - Bachelor: string - /** - * Master - */ - Master: string - } - Navbar: { - /** - * Fachschaft I/1 - */ - Branding: string - /** - * Startseite - */ - Home: string - /** - * Erstsemesterwochenende - */ - ESWE: string - /** - * Flyer - */ - Flyer: string - /** - * Informationen - */ - Information: string - /** - * Tutor - */ - Tutor: string - /** - * Rallye - */ - Rally: string - /** - * Rabatte - */ - Discounts: string - /** - * Sprache - */ - Language: string - /** - * Deutsch - */ - German: string - /** - * Englisch - */ - English: string - } - Footer: { - /** - * Fachschaft Mathematik/Physik/Informatik - */ - Copyright: string - /** - * Impressum - */ - Legal: string - /** - * Datenschutzerklärung - */ - PrivacyPolicy: string - /** - * Login - */ - Login: string - } - PageTitle: { - /** - * Startseite - */ - '/(non-admin)': string - /** - * Erstsemesterwochenende - */ - '/(non-admin)/eswe': string - /** - * Tutor werden - */ - '/(non-admin)/tutor': string - /** - * Tutoranmeldung - */ - '/(non-admin)/tutor/register': string - /** - * Informationen - */ - '/(non-admin)/information': string - /** - * Rabatte - */ - '/(non-admin)/rabatte': string - /** - * Flyer - */ - '/(non-admin)/flyer': string - fallback: string - } - Tutor: { - /** - * Tutor Anmeldung - */ - Headline: string - /** - * Hallo! - */ - Greeting: string - /** - * Du bist volljährig und möchtest Ersti-Tutor/in der Fachschaft Mathematik/Physik/Informatik im Wintersemester {yearOfSemester} werden? Hier bist du richtig! - * @param {string} yearOfSemester - */ - Paragraph: RequiredParams<'yearOfSemester'> - /** - * Was mache ich da? - */ - WhatTitle: string - /** - * Zu deinen Aufgaben gehört die Betreuung einer Gruppe von ca. 15 Erstis während dieser Woche. Du bist erste/r Ansprechpartner/in für alle Fragen der Erstis rund ums Studium, die Uni und die Stadt. Gemeinsam mit einem/r weiteren Tutor/in bist du dafür verantwortlich, die Ersti zu den Veranstaltungen der Ersti-Woche (Kneipenabend, Rallye, Projekttage, ...) zu begleiten, sie in RWTHmoodle und RWTH Online einzuweisen, sie bei der Anmeldung für ihre Vorlesungen und Übungen zu unterstützen und natürlich dafür zu sorgen, dass sie viel Spaß haben. - */ - WhatContent: string - /** - * Was brauche ich dafür? - */ - NeedTitle: string - /** - * In erster Linie Offenheit, Freude am Umgang mit Menschen und ein gewisses Verantwortungsbewusstsein. Außerdem musst du – außer, du warst in den letzten Jahren bereits Tutor/in – verpflichtend an einer von vier Tutschulungen teilnehmen. Falls du einen Co-Tutor angegeben hast: Es ist es nicht notwendig, dass ihr dieselbe Tutschulung besucht. - */ - NeedContent: string - /** - * Wann? - */ - WhenTitle: string - /** - * Die Ersti-Woche findet vom {fresherWeek} statt. Tutschulungen dauern jeweils von 9:45 bis ca. 18:00 Uhr und finden an folgenden Terminen statt: - * @param {string} fresherWeek - */ - WhenContent: RequiredParams<'fresherWeek'> - /** - * Falls ihr noch Fragen habt, schreibt an <><>! - */ - FurtherQuestions: string - /** - * Liebe Grüße - Die ErstSemesterInnen-Arbeit (ESA) der I/1 - */ - ClosingFormula: string - /** - * Zur verbindlichen Anmeldung - */ - RegistrationButton: string - /** - * Die Anmeldung ist aktuell geschlossen - */ - RegistrationClosed: string - FAQ: { - /** - * FAQ - */ - Headline: string - Money: { - /** - * Gibt's dafür Geld? - */ - Question: string - /** - * Es gibt eine Aufwandsentschädigung von 50€ pro Tutor, also 100€ pro Tutorium. Wir freuen uns, wenn ihr diese Summe dazu einsetzt, etwas für eure Erstis zu organisieren. Vielleicht ein gemeinsames Frühstück, eine gemeinsame Aktivität oder auch nur eine Runde Cocktails für alle beim Kneipenabend. - */ - Answer: string - } - Rally: { - /** - * Ich will lieber eine Station bei der Ersti-Rallye betreuen! Wo melde ich mich? - */ - Question: string - /** - * Schreib uns einfach eine Mail an <><>. - */ - Answer: string - } - Mentoring: { - /** - * Ich studiere Informatik und will Tutor/in werden. Wie läuft das mit dem Mentoring? - */ - Question: string - /** - * Ein Tutorium in der Informatik muss von einem/r Mentor/in und einem/r Tutorin betreut werden. Bei Fragen melde dich einfach beim Mentoring: <><>. - */ - Answer: string - } - Trainings: { - /** - * Ich kann bei keinem der Termine für die Tutschulungen da sein. Kann ich trotzdem Tutor/in werden? - */ - Question: string - /** - * Leider musst du an einer Tutschulung teilgenommen haben und wir können da keine Ausnahmen machen. Aber vielleicht klappt es ja nächstes Jahr? - */ - Answer: string - } - Support: { - /** - * Ich will die ESA bei ihrer Arbeit unterstützen, aber nicht (nur) als Tutor/in. Was kann ich tun? - */ - Question: string - /** - * Fühl dich herzlich eingeladen, zu einer ESA-Sitzung zu kommen und mitzuplanen! Wir treffen uns immer donnerstags um 20 Uhr in den Räumen der Fachschaft im Augustinerbach 2A. - */ - Answer: string - } - } - SignUp: { - /** - * Tutor Anmeldung - */ - Headline: string - /** - * Vorname - */ - FirstName: string - /** - * Nachname - */ - LastName: string - /** - * Rufname - */ - NickName: string - /** - * Geburtsdatum - */ - BirthDate: string - /** - * Bitte gib ein gültiges Datum ein - */ - InvalidDate: string - /** - * Du musst mindestens 18 Jahre alt sein - */ - NotOldEnough: string - /** - * RWTH-Mail - */ - Email: string - /** - * Telefonnummer - */ - Phone: string - /** - * Für kurzfristige Kommunikation v.a. während der Erstiwoche wollen wir dich eventuell anrufen - */ - PhoneHelper: string - /** - * Adresse - */ - Address: string - /** - * Geschlecht - */ - Gender: string - /** - * T-Shirt-Größe - */ - ShirtSize: string - /** - * Studiengang - */ - StudyProgram: string - /** - * Für den Studiengang {studyProgram} werden voraussichtlich keine weiteren Tutoren benötigt. Wenn du dich trotzdem anmeldest, wirst du auf die Warteliste gesetzt und wir melden uns bei dir, falls wir dich doch noch brauchen. - * @param {string} studyProgram - */ - StudyProgramWaitlist: RequiredParams<'studyProgram'> - /** - * Abschluss - */ - Degree: string - /** - * Der Abschluss, den du gerade anstrebst - */ - DegreeHelper: string - /** - * Schulung - */ - Training: string - /** - * Bereits geschult - */ - AlreadyTrained: string - /** - * Wunsch-Co-Tutor (optional) - */ - CoTutorWish: string - /** - * Ihr müsst beide das gleiche Fach studieren. In der Informatik muss ein/e Mentor/in dabei sein. - */ - CoTutorWishHelper: string - /** - * Ich bin Mentor in der Informatik - */ - IsMentor: string - /** - * Ich habe die <><> gelesen und bin damit einverstanden - */ - ReadPrivacyPolicy: string - /** - * Datenschutzerklärung - */ - PrivacyPolicy: string - /** - * Anmelden - */ - Submit: string - /** - * Bitte auswählen - */ - PleaseChoose: string - /** - * Die Anmeldung ist noch nicht geöffnet. Schau später nochmal vorbei!<>br<>Sollte es Fragen geben, dann wende dich gerne an <><>. - */ - SignUpClosed: string - /** - * Deine Anmeldung als Tutor wurde erfolgreich gespeichert - */ - SignedUpSuccessfully: string - FormValidationErrors: { - /** - * Bitte fülle alle Pflichtfelder aus - */ - MissingFields: string - /** - * Das angegebene Datum ist ungültig - */ - InvalidDate: string - /** - * Du musst mindestens 18 Jahre alt sein - */ - NotOldEnough: string - /** - * Bitte gib eine gültige RWTH-Mail-Adresse an - */ - InvalidEmail: string - /** - * Bitte wähle ein Geschlecht aus - */ - InvalidGender: string - /** - * Bitte wähle eine T-Shirt-Größe aus - */ - InvalidShirtSize: string - /** - * Bitte wähle einen Studiengang aus - */ - InvalidStudyProgram: string - /** - * Bitte wähle einen Abschluss aus - */ - InvalidDegree: string - /** - * Bitte wähle eine Schulung aus - */ - InvalidTraining: string - } - } - } - Information: { - /** - * Ablauf der Erstiwoche - */ - Schedule: string - /** - * Studiengang - */ - StudyProgram: string - StudyPrograms: { - /** - * Master (deutsch) - */ - 'master-deutsch': string - /** - * Master (englisch) - */ - 'master-englisch': string - } - /** - * Stundenplan Erstiwoche {semester} {studyProgram} - * @param {string} semester - * @param {string} studyProgram - */ - ScheduleAlt: RequiredParams<'semester' | 'studyProgram'> - /** - * Herunterladen - */ - Download: string - /** - * erstiwoche-{semester|sanitize}-stundenplan-{studyProgram|lowercase|sanitize} - * @param {string} semester - * @param {string} studyProgram - */ - DownloadFilename: RequiredParams<'semester|sanitize' | 'studyProgram|lowercase|sanitize'> - /** - * Informationen zum Studium - */ - Information: string - /** - * Kurzfassung - */ - Short: string - /** - * Langfassung - */ - Long: string - /** - * Erstsemester-Information {semester|sanitize} (kurz) - * @param {string} semester - */ - ShortDownloadFilename: RequiredParams<'semester|sanitize'> - /** - * Erstsemester-Information {semester|sanitize} (lang) - * @param {string} semester - */ - LongDownloadFilename: RequiredParams<'semester|sanitize'> - } - Discounts: { - Map: { - /** - * Dein Standort - */ - CurrentLocation: string - /** - * Details - */ - Details: string - } - Item: { - /** - * mehr anzeigen - */ - ShowMore: string - /** - * weniger anzeigen - */ - ShowLess: string - /** - * Schließt demnächst - */ - ClosingSoon: string - /** - * Öffnet demnächt - */ - OpeningSoon: string - /** - * Geöffnet - */ - Open: string - /** - * Geschlossen - */ - Closed: string - /** - * {start} - {end} Uhr - * @param {string} end - * @param {string} start - */ - Time: RequiredParams<'end' | 'start'> - /** - * geschlossen - */ - TimeClosed: string - Weekdays: { - /** - * Sonntag - */ - '0': string - /** - * Montag - */ - '1': string - /** - * Dienstag - */ - '2': string - /** - * Mittwoch - */ - '3': string - /** - * Donnerstag - */ - '4': string - /** - * Freitag - */ - '5': string - /** - * Samstag - */ - '6': string - } - } - Search: { - /** - * Nur geöffnet - */ - OnlyOpen: string - /** - * Suche - */ - Search: string - /** - * Für deine aktuelle Suche existieren keine Angebote - */ - NoResults: string - } - } - DiscountLeaveWarning: { - /** - * Achtung! - */ - Title: string - /** - * Die Seite zur Übersicht der Rabatte steht allen Fachschaften und somit allen Erstis zur Verfügung. Alle weiteren Seiten dieser Website sind nur für Erstis der Fachschaft Mathematik/Physik/Informatik (inklusive derer Lehramtserstis und WiMa-Erstis) gedacht. Solltest du dieser Fachschaft nicht zugehören, ist der restliche Inhalt für dich nicht relevant. - */ - Content: string - /** - * Verstanden - */ - Accept: string - } - ESWE: { - /** - * Erstsemesterwochenende - */ - Headline: string - } - Flyer: { - /** - * Einige Flyer haben mehr als eine Seite. Wenn du auf das Bild klickst, kommst du zum vollständigen PDF. - */ - Notice: string - } - Legal: { - /** - * Impressum - */ - Headline: string - /** - * Adresse - */ - Address: string - /** - * Telefon - */ - Phone: string - /** - * E-Mail (allgemein) - */ - EmailGeneral: string - /** - * E-Mail (ESA) - */ - EmailESA: string - /** - * Internet - */ - Internet: string - /** - * Haftungshinweis: Trotz sorgfältiger inhaltlicher Kontrolle übernehmen wir keine Haftung für die Inhalte externer Links. Für den Inhalt der verlinkten Seiten sind ausschließlich deren BetreiberInnen verantwortlich. - */ - LiabilityNote: string - } - PrivacyPolicy: { - /** - * Datenschutzerklärung - */ - Headline: string - Disclaimer: string - Paragraphs: { - '0': { - /** - * Geltungsbereich - */ - Headline: string - /** - * Diese Datenschutzerklärung soll die Nutzer dieser Website gemäß Bundesdatenschutzgesetz und Telemediengesetz über die Art, den Umfang und den Zweck der Erhebung und Verwendung personenbezogener Daten durch den Websitebetreiber die Studierendenschaft der RWTH, Fachschaft Mathematik/Physik/Informatik Teilkörperschaft d.ö.R, Templergraben 55, 52056 Aachen informieren. Der Anbieter hat organisatorische und technische Sicherheitsmaßnahmen getroffen, um sicher zu stellen, dass die Vorschriften der Datenschutzgesetze eingehalten werden und zufällige oder vorsätzliche Manipulationen, Verlust, Zerstörung oder der Zugriff unberechtigter Personen verhindert wird. Bedenken Sie, dass die Datenübertragung im Internet grundsätzlich mit Sicherheitslücken bedacht sein kann. Ein vollumfänglicher Schutz vor dem Zugriff durch Fremde ist daher nicht realisierbar. - */ - Content: string - } - '1': { - /** - * Cookies - */ - Headline: string - /** - * Diese Website verwendet Cookies. Dabei handelt es sich um kleine Textdateien, welche auf dem Endgerät des Nutzers gespeichert werden. Sein Browser greift auf diese Dateien zu. Temporäre Cookies werden nach dem Schließen des Browsers gelöscht, permanente Cookies bleiben für einen vorgegebenen Zeitraum erhalten und können beim erneuten Aufruf die gespeicherten Informationen bereitstellen. Durch den Einsatz von Cookies erhöht sich die Benutzerfreundlichkeit und Sicherheit dieser Website. Gängige Browser bieten die Einstellungsoption, Cookies nicht zuzulassen. Es ist nicht gewährleistet, dass auf alle Funktionen dieser Website ohne Einschränkungen zugegriffen werden kann, wenn die entsprechenden Einstellungen vorgenommen wurden. - */ - Content: string - } - '2': { - /** - * Erhebung von Zugriffsdaten - */ - Headline: string - /** - * Der Websitebetreiber erhebt Daten über jeden Zugriff auf die Website (so genannte Serverlogfiles). Zu den Zugriffsdaten gehören Name der abgerufenen Webseite, Datei, Datum und Uhrzeit des Abrufs, übertragene Datenmenge, Meldung über erfolgreichen Abruf, Browsertyp nebst Version, das Betriebssystem des Nutzers, Referrer URL (die zuvor besuchte Seite), IP-Adresse und der anfragende Provider. Der Websitebetreiber verwendet die Protokolldaten ohne Zuordnung zur Person des Nutzers oder sonstiger Profilerstellung entsprechend den gesetzlichen Bestimmungen nur für statistische Auswertungen zum Zweck des Betriebs, der Sicherheit und der Optimierung der Website. Der Websitebetreiber behält sich jedoch vor, die Protokolldaten nachträglich zu überprüfen, wenn aufgrund konkreter Anhaltspunkte der berechtigte Verdacht einer rechtswidrigen Nutzung besteht. - */ - Content: string - } - '3': { - /** - * Anmeldung - */ - Headline: string - /** - * Mit der Anmeldung als Tutor stimmt der Nutzer zu, dass seine personenbezogenen Daten (Vor- und Nachname, E-Mail-Adresse, Studienfach, Telefonnummer, usw.) vom Webseitenbetreiber erhoben und gespeichert werden. Die Daten werden dazu verwendet, um die Erstsemesterarbeit (ESA) an der RWTH zu planen und durchzuführen, sowie um den Nutzer mit Informationen über seine Tätigkeit als Tutor zu versorgen. Zu diesem Zwecke werden die Daten teilweise auch an das Hochschulweite ESA-Team weitergegeben. Abgesehen davon erfolgt keine Weitergabe an Dritte. - */ - Content: string - } - '4': { - /** - * Umgang mit Kontaktdaten - */ - Headline: string - /** - * Nimmt der Nutzer mit dem Websitebetreiber durch die angebotenen Kontaktmöglichkeiten Verbindung auf, werden seine Angaben gespeichert, damit auf diese zur Bearbeitung und Beantwortung seiner Anfrage zurückgegriffen werden kann. Ohne Einwilligung des Nutzers werden diese Daten nicht an Dritte weitergegeben. - */ - Content: string - } - '5': { - /** - * Rechte des Nutzers: Auskunft, Berichtigung und Löschung - */ - Headline: string - /** - * Der Nutzer erhält auf Antrag per Mail an esa@fsmpi.rwth-aachen.de kostenlose Auskunft darüber, welche personenbezogenen Daten über ihn gespeichert wurden. Dabei gelten die Bestimmungen für den Umgang mit Kontaktdaten. Der Nutzer hat ein Anrecht auf Berichtigung falscher Daten und auf die Sperrung oder Löschung seiner personenbezogenen Daten, sofern er dies wünscht. Davon ausgenommen behält der Websitebetreiber sich vor, Daten aufzubewahren welche einer gesetzlichen Pflicht zur Aufbewahrung von Daten (z. B. Vorratsdatenspeicherung) unterliegen oder an denen ein begründetes Interesse für die erfolgreiche Durchführung von Erstsemesterarbeit besteht. - */ - Content: string - } - '6': { - /** - * Änderung der Datenschutzerklärung - */ - Headline: string - /** - * Der Websitebetreiber behält sich vor, die Datenschutzerklärung zu ändern, um sie an geänderte Rechtslage oder bei Änderungen des Dienstes sowie der Datenverarbeitung anzupassen. - */ - Content: string - } - } - /** - * Sofern nicht anders angegeben, ist die männliche Form in diesem Text nicht geschlechterspezifisch gemeint, sondern wurde aus Gründen der Lesbarkeit gewählt. - */ - Footer: string - } -} - -export type TranslationFunctions = { - StudyProgram: { - /** - * Physik - */ - Physik: () => LocalizedString - /** - * Mathematik - */ - Mathematik: () => LocalizedString - /** - * Informatik - */ - Informatik: () => LocalizedString - /** - * Wirtschaftsmathematik - */ - Wirtschaftsmathematik: () => LocalizedString - /** - * Lehramt - */ - Lehramt: () => LocalizedString - } - Gender: { - /** - * männlich - */ - männlich: () => LocalizedString - /** - * weiblich - */ - weiblich: () => LocalizedString - /** - * divers - */ - divers: () => LocalizedString - /** - * keine Angabe - */ - 'keine Angabe': () => LocalizedString - } - Degree: { - /** - * Bachelor - */ - Bachelor: () => LocalizedString - /** - * Master - */ - Master: () => LocalizedString - } - Navbar: { - /** - * Fachschaft I/1 - */ - Branding: () => LocalizedString - /** - * Startseite - */ - Home: () => LocalizedString - /** - * Erstsemesterwochenende - */ - ESWE: () => LocalizedString - /** - * Flyer - */ - Flyer: () => LocalizedString - /** - * Informationen - */ - Information: () => LocalizedString - /** - * Tutor - */ - Tutor: () => LocalizedString - /** - * Rallye - */ - Rally: () => LocalizedString - /** - * Rabatte - */ - Discounts: () => LocalizedString - /** - * Sprache - */ - Language: () => LocalizedString - /** - * Deutsch - */ - German: () => LocalizedString - /** - * Englisch - */ - English: () => LocalizedString - } - Footer: { - /** - * Fachschaft Mathematik/Physik/Informatik - */ - Copyright: () => LocalizedString - /** - * Impressum - */ - Legal: () => LocalizedString - /** - * Datenschutzerklärung - */ - PrivacyPolicy: () => LocalizedString - /** - * Login - */ - Login: () => LocalizedString - } - PageTitle: { - /** - * Startseite - */ - '/(non-admin)': () => LocalizedString - /** - * Erstsemesterwochenende - */ - '/(non-admin)/eswe': () => LocalizedString - /** - * Tutor werden - */ - '/(non-admin)/tutor': () => LocalizedString - /** - * Tutoranmeldung - */ - '/(non-admin)/tutor/register': () => LocalizedString - /** - * Informationen - */ - '/(non-admin)/information': () => LocalizedString - /** - * Rabatte - */ - '/(non-admin)/rabatte': () => LocalizedString - /** - * Flyer - */ - '/(non-admin)/flyer': () => LocalizedString - fallback: () => LocalizedString - } - Tutor: { - /** - * Tutor Anmeldung - */ - Headline: () => LocalizedString - /** - * Hallo! - */ - Greeting: () => LocalizedString - /** - * Du bist volljährig und möchtest Ersti-Tutor/in der Fachschaft Mathematik/Physik/Informatik im Wintersemester {yearOfSemester} werden? Hier bist du richtig! - */ - Paragraph: (arg: { yearOfSemester: string }) => LocalizedString - /** - * Was mache ich da? - */ - WhatTitle: () => LocalizedString - /** - * Zu deinen Aufgaben gehört die Betreuung einer Gruppe von ca. 15 Erstis während dieser Woche. Du bist erste/r Ansprechpartner/in für alle Fragen der Erstis rund ums Studium, die Uni und die Stadt. Gemeinsam mit einem/r weiteren Tutor/in bist du dafür verantwortlich, die Ersti zu den Veranstaltungen der Ersti-Woche (Kneipenabend, Rallye, Projekttage, ...) zu begleiten, sie in RWTHmoodle und RWTH Online einzuweisen, sie bei der Anmeldung für ihre Vorlesungen und Übungen zu unterstützen und natürlich dafür zu sorgen, dass sie viel Spaß haben. - */ - WhatContent: () => LocalizedString - /** - * Was brauche ich dafür? - */ - NeedTitle: () => LocalizedString - /** - * In erster Linie Offenheit, Freude am Umgang mit Menschen und ein gewisses Verantwortungsbewusstsein. Außerdem musst du – außer, du warst in den letzten Jahren bereits Tutor/in – verpflichtend an einer von vier Tutschulungen teilnehmen. Falls du einen Co-Tutor angegeben hast: Es ist es nicht notwendig, dass ihr dieselbe Tutschulung besucht. - */ - NeedContent: () => LocalizedString - /** - * Wann? - */ - WhenTitle: () => LocalizedString - /** - * Die Ersti-Woche findet vom {fresherWeek} statt. Tutschulungen dauern jeweils von 9:45 bis ca. 18:00 Uhr und finden an folgenden Terminen statt: - */ - WhenContent: (arg: { fresherWeek: string }) => LocalizedString - /** - * Falls ihr noch Fragen habt, schreibt an <><>! - */ - FurtherQuestions: () => LocalizedString - /** - * Liebe Grüße - Die ErstSemesterInnen-Arbeit (ESA) der I/1 - */ - ClosingFormula: () => LocalizedString - /** - * Zur verbindlichen Anmeldung - */ - RegistrationButton: () => LocalizedString - /** - * Die Anmeldung ist aktuell geschlossen - */ - RegistrationClosed: () => LocalizedString - FAQ: { - /** - * FAQ - */ - Headline: () => LocalizedString - Money: { - /** - * Gibt's dafür Geld? - */ - Question: () => LocalizedString - /** - * Es gibt eine Aufwandsentschädigung von 50€ pro Tutor, also 100€ pro Tutorium. Wir freuen uns, wenn ihr diese Summe dazu einsetzt, etwas für eure Erstis zu organisieren. Vielleicht ein gemeinsames Frühstück, eine gemeinsame Aktivität oder auch nur eine Runde Cocktails für alle beim Kneipenabend. - */ - Answer: () => LocalizedString - } - Rally: { - /** - * Ich will lieber eine Station bei der Ersti-Rallye betreuen! Wo melde ich mich? - */ - Question: () => LocalizedString - /** - * Schreib uns einfach eine Mail an <><>. - */ - Answer: () => LocalizedString - } - Mentoring: { - /** - * Ich studiere Informatik und will Tutor/in werden. Wie läuft das mit dem Mentoring? - */ - Question: () => LocalizedString - /** - * Ein Tutorium in der Informatik muss von einem/r Mentor/in und einem/r Tutorin betreut werden. Bei Fragen melde dich einfach beim Mentoring: <><>. - */ - Answer: () => LocalizedString - } - Trainings: { - /** - * Ich kann bei keinem der Termine für die Tutschulungen da sein. Kann ich trotzdem Tutor/in werden? - */ - Question: () => LocalizedString - /** - * Leider musst du an einer Tutschulung teilgenommen haben und wir können da keine Ausnahmen machen. Aber vielleicht klappt es ja nächstes Jahr? - */ - Answer: () => LocalizedString - } - Support: { - /** - * Ich will die ESA bei ihrer Arbeit unterstützen, aber nicht (nur) als Tutor/in. Was kann ich tun? - */ - Question: () => LocalizedString - /** - * Fühl dich herzlich eingeladen, zu einer ESA-Sitzung zu kommen und mitzuplanen! Wir treffen uns immer donnerstags um 20 Uhr in den Räumen der Fachschaft im Augustinerbach 2A. - */ - Answer: () => LocalizedString - } - } - SignUp: { - /** - * Tutor Anmeldung - */ - Headline: () => LocalizedString - /** - * Vorname - */ - FirstName: () => LocalizedString - /** - * Nachname - */ - LastName: () => LocalizedString - /** - * Rufname - */ - NickName: () => LocalizedString - /** - * Geburtsdatum - */ - BirthDate: () => LocalizedString - /** - * Bitte gib ein gültiges Datum ein - */ - InvalidDate: () => LocalizedString - /** - * Du musst mindestens 18 Jahre alt sein - */ - NotOldEnough: () => LocalizedString - /** - * RWTH-Mail - */ - Email: () => LocalizedString - /** - * Telefonnummer - */ - Phone: () => LocalizedString - /** - * Für kurzfristige Kommunikation v.a. während der Erstiwoche wollen wir dich eventuell anrufen - */ - PhoneHelper: () => LocalizedString - /** - * Adresse - */ - Address: () => LocalizedString - /** - * Geschlecht - */ - Gender: () => LocalizedString - /** - * T-Shirt-Größe - */ - ShirtSize: () => LocalizedString - /** - * Studiengang - */ - StudyProgram: () => LocalizedString - /** - * Für den Studiengang {studyProgram} werden voraussichtlich keine weiteren Tutoren benötigt. Wenn du dich trotzdem anmeldest, wirst du auf die Warteliste gesetzt und wir melden uns bei dir, falls wir dich doch noch brauchen. - */ - StudyProgramWaitlist: (arg: { studyProgram: string }) => LocalizedString - /** - * Abschluss - */ - Degree: () => LocalizedString - /** - * Der Abschluss, den du gerade anstrebst - */ - DegreeHelper: () => LocalizedString - /** - * Schulung - */ - Training: () => LocalizedString - /** - * Bereits geschult - */ - AlreadyTrained: () => LocalizedString - /** - * Wunsch-Co-Tutor (optional) - */ - CoTutorWish: () => LocalizedString - /** - * Ihr müsst beide das gleiche Fach studieren. In der Informatik muss ein/e Mentor/in dabei sein. - */ - CoTutorWishHelper: () => LocalizedString - /** - * Ich bin Mentor in der Informatik - */ - IsMentor: () => LocalizedString - /** - * Ich habe die <><> gelesen und bin damit einverstanden - */ - ReadPrivacyPolicy: () => LocalizedString - /** - * Datenschutzerklärung - */ - PrivacyPolicy: () => LocalizedString - /** - * Anmelden - */ - Submit: () => LocalizedString - /** - * Bitte auswählen - */ - PleaseChoose: () => LocalizedString - /** - * Die Anmeldung ist noch nicht geöffnet. Schau später nochmal vorbei!<>br<>Sollte es Fragen geben, dann wende dich gerne an <><>. - */ - SignUpClosed: () => LocalizedString - /** - * Deine Anmeldung als Tutor wurde erfolgreich gespeichert - */ - SignedUpSuccessfully: () => LocalizedString - FormValidationErrors: { - /** - * Bitte fülle alle Pflichtfelder aus - */ - MissingFields: () => LocalizedString - /** - * Das angegebene Datum ist ungültig - */ - InvalidDate: () => LocalizedString - /** - * Du musst mindestens 18 Jahre alt sein - */ - NotOldEnough: () => LocalizedString - /** - * Bitte gib eine gültige RWTH-Mail-Adresse an - */ - InvalidEmail: () => LocalizedString - /** - * Bitte wähle ein Geschlecht aus - */ - InvalidGender: () => LocalizedString - /** - * Bitte wähle eine T-Shirt-Größe aus - */ - InvalidShirtSize: () => LocalizedString - /** - * Bitte wähle einen Studiengang aus - */ - InvalidStudyProgram: () => LocalizedString - /** - * Bitte wähle einen Abschluss aus - */ - InvalidDegree: () => LocalizedString - /** - * Bitte wähle eine Schulung aus - */ - InvalidTraining: () => LocalizedString - } - } - } - Information: { - /** - * Ablauf der Erstiwoche - */ - Schedule: () => LocalizedString - /** - * Studiengang - */ - StudyProgram: () => LocalizedString - StudyPrograms: { - /** - * Master (deutsch) - */ - 'master-deutsch': () => LocalizedString - /** - * Master (englisch) - */ - 'master-englisch': () => LocalizedString - } - /** - * Stundenplan Erstiwoche {semester} {studyProgram} - */ - ScheduleAlt: (arg: { semester: string, studyProgram: string }) => LocalizedString - /** - * Herunterladen - */ - Download: () => LocalizedString - /** - * erstiwoche-{semester|sanitize}-stundenplan-{studyProgram|lowercase|sanitize} - */ - DownloadFilename: (arg: { semester: string, studyProgram: string }) => LocalizedString - /** - * Informationen zum Studium - */ - Information: () => LocalizedString - /** - * Kurzfassung - */ - Short: () => LocalizedString - /** - * Langfassung - */ - Long: () => LocalizedString - /** - * Erstsemester-Information {semester|sanitize} (kurz) - */ - ShortDownloadFilename: (arg: { semester: string }) => LocalizedString - /** - * Erstsemester-Information {semester|sanitize} (lang) - */ - LongDownloadFilename: (arg: { semester: string }) => LocalizedString - } - Discounts: { - Map: { - /** - * Dein Standort - */ - CurrentLocation: () => LocalizedString - /** - * Details - */ - Details: () => LocalizedString - } - Item: { - /** - * mehr anzeigen - */ - ShowMore: () => LocalizedString - /** - * weniger anzeigen - */ - ShowLess: () => LocalizedString - /** - * Schließt demnächst - */ - ClosingSoon: () => LocalizedString - /** - * Öffnet demnächt - */ - OpeningSoon: () => LocalizedString - /** - * Geöffnet - */ - Open: () => LocalizedString - /** - * Geschlossen - */ - Closed: () => LocalizedString - /** - * {start} - {end} Uhr - */ - Time: (arg: { end: string, start: string }) => LocalizedString - /** - * geschlossen - */ - TimeClosed: () => LocalizedString - Weekdays: { - /** - * Sonntag - */ - '0': () => LocalizedString - /** - * Montag - */ - '1': () => LocalizedString - /** - * Dienstag - */ - '2': () => LocalizedString - /** - * Mittwoch - */ - '3': () => LocalizedString - /** - * Donnerstag - */ - '4': () => LocalizedString - /** - * Freitag - */ - '5': () => LocalizedString - /** - * Samstag - */ - '6': () => LocalizedString - } - } - Search: { - /** - * Nur geöffnet - */ - OnlyOpen: () => LocalizedString - /** - * Suche - */ - Search: () => LocalizedString - /** - * Für deine aktuelle Suche existieren keine Angebote - */ - NoResults: () => LocalizedString - } - } - DiscountLeaveWarning: { - /** - * Achtung! - */ - Title: () => LocalizedString - /** - * Die Seite zur Übersicht der Rabatte steht allen Fachschaften und somit allen Erstis zur Verfügung. Alle weiteren Seiten dieser Website sind nur für Erstis der Fachschaft Mathematik/Physik/Informatik (inklusive derer Lehramtserstis und WiMa-Erstis) gedacht. Solltest du dieser Fachschaft nicht zugehören, ist der restliche Inhalt für dich nicht relevant. - */ - Content: () => LocalizedString - /** - * Verstanden - */ - Accept: () => LocalizedString - } - ESWE: { - /** - * Erstsemesterwochenende - */ - Headline: () => LocalizedString - } - Flyer: { - /** - * Einige Flyer haben mehr als eine Seite. Wenn du auf das Bild klickst, kommst du zum vollständigen PDF. - */ - Notice: () => LocalizedString - } - Legal: { - /** - * Impressum - */ - Headline: () => LocalizedString - /** - * Adresse - */ - Address: () => LocalizedString - /** - * Telefon - */ - Phone: () => LocalizedString - /** - * E-Mail (allgemein) - */ - EmailGeneral: () => LocalizedString - /** - * E-Mail (ESA) - */ - EmailESA: () => LocalizedString - /** - * Internet - */ - Internet: () => LocalizedString - /** - * Haftungshinweis: Trotz sorgfältiger inhaltlicher Kontrolle übernehmen wir keine Haftung für die Inhalte externer Links. Für den Inhalt der verlinkten Seiten sind ausschließlich deren BetreiberInnen verantwortlich. - */ - LiabilityNote: () => LocalizedString - } - PrivacyPolicy: { - /** - * Datenschutzerklärung - */ - Headline: () => LocalizedString - Disclaimer: () => LocalizedString - Paragraphs: { - '0': { - /** - * Geltungsbereich - */ - Headline: () => LocalizedString - /** - * Diese Datenschutzerklärung soll die Nutzer dieser Website gemäß Bundesdatenschutzgesetz und Telemediengesetz über die Art, den Umfang und den Zweck der Erhebung und Verwendung personenbezogener Daten durch den Websitebetreiber die Studierendenschaft der RWTH, Fachschaft Mathematik/Physik/Informatik Teilkörperschaft d.ö.R, Templergraben 55, 52056 Aachen informieren. Der Anbieter hat organisatorische und technische Sicherheitsmaßnahmen getroffen, um sicher zu stellen, dass die Vorschriften der Datenschutzgesetze eingehalten werden und zufällige oder vorsätzliche Manipulationen, Verlust, Zerstörung oder der Zugriff unberechtigter Personen verhindert wird. Bedenken Sie, dass die Datenübertragung im Internet grundsätzlich mit Sicherheitslücken bedacht sein kann. Ein vollumfänglicher Schutz vor dem Zugriff durch Fremde ist daher nicht realisierbar. - */ - Content: () => LocalizedString - } - '1': { - /** - * Cookies - */ - Headline: () => LocalizedString - /** - * Diese Website verwendet Cookies. Dabei handelt es sich um kleine Textdateien, welche auf dem Endgerät des Nutzers gespeichert werden. Sein Browser greift auf diese Dateien zu. Temporäre Cookies werden nach dem Schließen des Browsers gelöscht, permanente Cookies bleiben für einen vorgegebenen Zeitraum erhalten und können beim erneuten Aufruf die gespeicherten Informationen bereitstellen. Durch den Einsatz von Cookies erhöht sich die Benutzerfreundlichkeit und Sicherheit dieser Website. Gängige Browser bieten die Einstellungsoption, Cookies nicht zuzulassen. Es ist nicht gewährleistet, dass auf alle Funktionen dieser Website ohne Einschränkungen zugegriffen werden kann, wenn die entsprechenden Einstellungen vorgenommen wurden. - */ - Content: () => LocalizedString - } - '2': { - /** - * Erhebung von Zugriffsdaten - */ - Headline: () => LocalizedString - /** - * Der Websitebetreiber erhebt Daten über jeden Zugriff auf die Website (so genannte Serverlogfiles). Zu den Zugriffsdaten gehören Name der abgerufenen Webseite, Datei, Datum und Uhrzeit des Abrufs, übertragene Datenmenge, Meldung über erfolgreichen Abruf, Browsertyp nebst Version, das Betriebssystem des Nutzers, Referrer URL (die zuvor besuchte Seite), IP-Adresse und der anfragende Provider. Der Websitebetreiber verwendet die Protokolldaten ohne Zuordnung zur Person des Nutzers oder sonstiger Profilerstellung entsprechend den gesetzlichen Bestimmungen nur für statistische Auswertungen zum Zweck des Betriebs, der Sicherheit und der Optimierung der Website. Der Websitebetreiber behält sich jedoch vor, die Protokolldaten nachträglich zu überprüfen, wenn aufgrund konkreter Anhaltspunkte der berechtigte Verdacht einer rechtswidrigen Nutzung besteht. - */ - Content: () => LocalizedString - } - '3': { - /** - * Anmeldung - */ - Headline: () => LocalizedString - /** - * Mit der Anmeldung als Tutor stimmt der Nutzer zu, dass seine personenbezogenen Daten (Vor- und Nachname, E-Mail-Adresse, Studienfach, Telefonnummer, usw.) vom Webseitenbetreiber erhoben und gespeichert werden. Die Daten werden dazu verwendet, um die Erstsemesterarbeit (ESA) an der RWTH zu planen und durchzuführen, sowie um den Nutzer mit Informationen über seine Tätigkeit als Tutor zu versorgen. Zu diesem Zwecke werden die Daten teilweise auch an das Hochschulweite ESA-Team weitergegeben. Abgesehen davon erfolgt keine Weitergabe an Dritte. - */ - Content: () => LocalizedString - } - '4': { - /** - * Umgang mit Kontaktdaten - */ - Headline: () => LocalizedString - /** - * Nimmt der Nutzer mit dem Websitebetreiber durch die angebotenen Kontaktmöglichkeiten Verbindung auf, werden seine Angaben gespeichert, damit auf diese zur Bearbeitung und Beantwortung seiner Anfrage zurückgegriffen werden kann. Ohne Einwilligung des Nutzers werden diese Daten nicht an Dritte weitergegeben. - */ - Content: () => LocalizedString - } - '5': { - /** - * Rechte des Nutzers: Auskunft, Berichtigung und Löschung - */ - Headline: () => LocalizedString - /** - * Der Nutzer erhält auf Antrag per Mail an esa@fsmpi.rwth-aachen.de kostenlose Auskunft darüber, welche personenbezogenen Daten über ihn gespeichert wurden. Dabei gelten die Bestimmungen für den Umgang mit Kontaktdaten. Der Nutzer hat ein Anrecht auf Berichtigung falscher Daten und auf die Sperrung oder Löschung seiner personenbezogenen Daten, sofern er dies wünscht. Davon ausgenommen behält der Websitebetreiber sich vor, Daten aufzubewahren welche einer gesetzlichen Pflicht zur Aufbewahrung von Daten (z. B. Vorratsdatenspeicherung) unterliegen oder an denen ein begründetes Interesse für die erfolgreiche Durchführung von Erstsemesterarbeit besteht. - */ - Content: () => LocalizedString - } - '6': { - /** - * Änderung der Datenschutzerklärung - */ - Headline: () => LocalizedString - /** - * Der Websitebetreiber behält sich vor, die Datenschutzerklärung zu ändern, um sie an geänderte Rechtslage oder bei Änderungen des Dienstes sowie der Datenverarbeitung anzupassen. - */ - Content: () => LocalizedString - } - } - /** - * Sofern nicht anders angegeben, ist die männliche Form in diesem Text nicht geschlechterspezifisch gemeint, sondern wurde aus Gründen der Lesbarkeit gewählt. - */ - Footer: () => LocalizedString - } -} - -export type Formatters = { - lowercase: (value: string) => unknown - sanitize: (value: string) => unknown -} diff --git a/src/lib/i18n/i18n-util.async.ts b/src/lib/i18n/i18n-util.async.ts deleted file mode 100644 index 7b72fea35c61d7fb7172dda02d704a162d9e6cf2..0000000000000000000000000000000000000000 --- a/src/lib/i18n/i18n-util.async.ts +++ /dev/null @@ -1,27 +0,0 @@ -// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. -/* eslint-disable */ - -import { initFormatters } from './formatters' -import type { Locales, Translations } from './i18n-types' -import { loadedFormatters, loadedLocales, locales } from './i18n-util' - -const localeTranslationLoaders = { - de: () => import('./de'), - en: () => import('./en'), -} - -const updateDictionary = (locale: Locales, dictionary: Partial<Translations>): Translations => - loadedLocales[locale] = { ...loadedLocales[locale], ...dictionary } - -export const importLocaleAsync = async (locale: Locales): Promise<Translations> => - (await localeTranslationLoaders[locale]()).default as unknown as Translations - -export const loadLocaleAsync = async (locale: Locales): Promise<void> => { - updateDictionary(locale, await importLocaleAsync(locale)) - loadFormatters(locale) -} - -export const loadAllLocalesAsync = (): Promise<void[]> => Promise.all(locales.map(loadLocaleAsync)) - -export const loadFormatters = (locale: Locales): void => - void (loadedFormatters[locale] = initFormatters(locale)) diff --git a/src/lib/i18n/i18n-util.sync.ts b/src/lib/i18n/i18n-util.sync.ts deleted file mode 100644 index 6898c2b38654f561d07970a413fced3128ea59a1..0000000000000000000000000000000000000000 --- a/src/lib/i18n/i18n-util.sync.ts +++ /dev/null @@ -1,26 +0,0 @@ -// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. -/* eslint-disable */ - -import { initFormatters } from './formatters' -import type { Locales, Translations } from './i18n-types' -import { loadedFormatters, loadedLocales, locales } from './i18n-util' - -import de from './de' -import en from './en' - -const localeTranslations = { - de, - en, -} - -export const loadLocale = (locale: Locales): void => { - if (loadedLocales[locale]) return - - loadedLocales[locale] = localeTranslations[locale] as unknown as Translations - loadFormatters(locale) -} - -export const loadAllLocales = (): void => locales.forEach(loadLocale) - -export const loadFormatters = (locale: Locales): void => - void (loadedFormatters[locale] = initFormatters(locale)) diff --git a/src/lib/i18n/i18n-util.ts b/src/lib/i18n/i18n-util.ts deleted file mode 100644 index 1c4a200e99aa082c545af61cf1fc8137d93f0571..0000000000000000000000000000000000000000 --- a/src/lib/i18n/i18n-util.ts +++ /dev/null @@ -1,38 +0,0 @@ -// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. -/* eslint-disable */ - -import { i18n as initI18n, i18nObject as initI18nObject, i18nString as initI18nString } from 'typesafe-i18n' -import type { LocaleDetector } from 'typesafe-i18n/detectors' -import type { LocaleTranslationFunctions, TranslateByString } from 'typesafe-i18n' -import { detectLocale as detectLocaleFn } from 'typesafe-i18n/detectors' -import { initExtendDictionary } from 'typesafe-i18n/utils' -import type { Formatters, Locales, Translations, TranslationFunctions } from './i18n-types' - -export const baseLocale: Locales = 'de' - -export const locales: Locales[] = [ - 'de', - 'en' -] - -export const isLocale = (locale: string): locale is Locales => locales.includes(locale as Locales) - -export const loadedLocales: Record<Locales, Translations> = {} as Record<Locales, Translations> - -export const loadedFormatters: Record<Locales, Formatters> = {} as Record<Locales, Formatters> - -export const extendDictionary = initExtendDictionary<Translations>() - -export const i18nString = (locale: Locales): TranslateByString => initI18nString<Locales, Formatters>(locale, loadedFormatters[locale]) - -export const i18nObject = (locale: Locales): TranslationFunctions => - initI18nObject<Locales, Translations, TranslationFunctions, Formatters>( - locale, - loadedLocales[locale], - loadedFormatters[locale] - ) - -export const i18n = (): LocaleTranslationFunctions<Locales, Translations, TranslationFunctions> => - initI18n<Locales, Translations, TranslationFunctions, Formatters>(loadedLocales, loadedFormatters) - -export const detectLocale = (...detectors: LocaleDetector[]): Locales => detectLocaleFn<Locales>(baseLocale, locales, ...detectors) diff --git a/src/lib/i18n/i18n.ts b/src/lib/i18n/i18n.ts new file mode 100644 index 0000000000000000000000000000000000000000..4b7a019cfd27042f5e0f323bdc73550f1d07c990 --- /dev/null +++ b/src/lib/i18n/i18n.ts @@ -0,0 +1,147 @@ +import { writable } from "svelte/store"; +import de from "./de"; +import en from "./en"; +import { browser } from "$app/environment"; + +export type LocalizedString = string; +export type TranslationFunction = ((...args: unknown[])=>LocalizedString&{raw:string}); +export type Translation<T=TranslationFunction> = ReplaceString<typeof de, T>; + +type ReplaceString<T, R = LocalizedString> = { + [K in keyof T]: + T[K] extends string ? R : + ReplaceString<T[K], R> +}; + +export const L: {[K in Locale]: Translation} = { + de: proxify(generateLObject(de, "de")), + en: proxify(generateLObject(en, "en")), +}; + +export const locales = ["de", "en"] as const; +export type Locale = typeof locales[number]; +export const defaultLocale: Locale = "de"; +let currentLocale: Locale = defaultLocale; +export const locale = writable<Locale>(currentLocale); +export const LL = writable<Translation>(L[currentLocale]); +export default LL; + +export function setLocale(newLocale: Locale) { + currentLocale = newLocale; + locale.set(newLocale); + LL.set(L[newLocale]); + if(browser){ + document.cookie = `lang=${newLocale};path=/;SameSite=Strict`; + document.documentElement.lang = newLocale; + } +} + +type Placeholder = {start: number, end: number, name: string, options: string[]}; +function findPlaceholders(input: string): Placeholder[] { + const result = [] as Placeholder[]; + let startIdx = -1; + let currentOption = -1; + let currentOptions = [] as string[]; + let inOption = false; + let currentName = ""; + for(let i = 0; i < input.length; i++) { + if(startIdx === -1) { + if(input[i] === "{") startIdx = i; + }else{ + if(input[i] === "}"){ + if(inOption) { + currentOptions.push(input.slice(currentOption, i)); + inOption = false; + }else{ + currentName = input.slice(startIdx+1, i); + } + result.push({start: startIdx, end: i+1, name: currentName, options: currentOptions}); + startIdx = -1; + currentOptions = []; + currentOption = -1; + currentName = ""; + }else if(input[i] === "|"){ + if(inOption){ + currentOptions.push(input.slice(currentOption, i)); + currentOption = i+1; + }else{ + currentName = input.slice(startIdx+1, i); + currentOption = i+1; + inOption = true; + } + } + } + } + return result; +} +export function LLL(input: LocalizedString, ...args: unknown[]): LocalizedString { + return formatLocaleString(input, currentLocale, ...args); +} +function formatLocaleString(input: string, locale: Locale = currentLocale, ...args: unknown[]): LocalizedString { + if(typeof input !== "string"){ + console.trace("input is not a string", input); + return ""; + } + for(let i = 0; i < args.length; i++) { + const arg = args[i]; + const toHandle = {} as Record<string, unknown>; + if(typeof arg === "object" && arg !== null) { + for(const key in arg) { + toHandle[key] = arg[key]; + } + } else { + toHandle[i] = arg; + } + const placeholders = findPlaceholders(input); + for(let i = placeholders.length-1; i >= 0; i--) { + const {start, end, name, options} = placeholders[i]; + const value = toHandle[name]; + if(value === undefined) continue; + const formatted = options.reduce((acc, option)=>{ + if(formatters[locale][option]) return formatters[locale][option](acc); + else return acc; + }, value); + input = input.slice(0, start) + formatted + input.slice(end); + } + } + return input; +} + +const nothingProxy: ()=>()=>string = (path: string)=>new Proxy(()=>{ + console.trace("Trying to call non-existing translation function", path); + return ""; +}, { + get: (_, prop: string)=>nothingProxy(path+"."+prop), + has: ()=>false, +}); +function proxify<T extends object>(t: T, path=""): T { + //return t; + return new Proxy(t, { + get: (target, prop)=>{ + if(typeof target[prop] === "function") return target[prop]; + else if(typeof target[prop] === "object") return proxify(target[prop], path+"."+prop); + else return nothingProxy(path+"."+prop); + }, + }); +} + +function generateLObject(obj: typeof de, locale: Locale): Translation { + const result = {} as Translation; + for(const key in obj){ + if(typeof obj[key] === "string"){ + result[key] = (...args: unknown[])=>formatLocaleString(obj[key], locale, ...args); + result[key].raw = obj[key]; + }else{ + result[key] = generateLObject(obj[key], locale); + } + } + return result; +} + +const formatterBuilders: Record<string, (lang: Locale)=>(arg: any)=>unknown> = { + dateLong: (lang: Locale)=>new Intl.DateTimeFormat(lang, {year: "numeric", month: "long", day: "2-digit"}).format, + dateRangeLong: (lang: Locale)=>(value: [number|Date, number|Date])=>new Intl.DateTimeFormat(lang, {year: "numeric", month: "long", day: "2-digit"}).formatRange(value[0], value[1]), + sanitize: ()=>(input: string)=>input.replace(/[^a-zA-Z0-9_ ()äöüÄÖÜß-]/g, "-"), + lowercase: ()=>(input: string)=>input.toLowerCase(), +} +const formatters = Object.fromEntries(locales.map((lang)=>[lang, Object.fromEntries(Object.entries(formatterBuilders).map(([key, builder])=>[key, builder(lang)]))])); diff --git a/src/lib/mail.ts b/src/lib/mail.ts new file mode 100644 index 0000000000000000000000000000000000000000..17b9b8d45ee9e654472f48112bbd5de2e0cfedcd --- /dev/null +++ b/src/lib/mail.ts @@ -0,0 +1,297 @@ +import Degree from "./degrees"; +import Gender from "./genders"; +import { locales, type Locale } from "./i18n/i18n"; +import type { Tutor } from "./server/database/entities/Tutor.entity"; +import markdownit from "markdown-it"; +import type { Localized } from "./utils"; + +const md = markdownit({ + breaks: true, + linkify: true, + quotes: "„“‚‘", + typographer: true, + html: true, +}); + +export type MailTemplate = { + replyTo: string; + subject: Localized; + text: Localized; +}; + +export type Variable = { + name: string; + description: string; + replacement: (t: Tutor, l: Locale)=>string; +}; +export const variables: Variable[] = [ + { + name: "vorname", + description: "Vorname des Empfängers", + replacement: t=>t.firstname, + }, + { + name: "nachname", + description: "Nachname des Empfängers", + replacement: t=>t.lastname, + }, + { + name: "spitzname", + description: "Spitzname des Empfängers", + replacement: t=>t.nickname ?? "", + }, + { + name: "email", + description: "E-Mail-Adresse des Empfängers", + replacement: t=>t.email, + }, + { + name: "telefon", + description: "Telefonnummer des Empfängers", + replacement: t=>t.phone, + }, + { + name: "studiengang", + description: "Studiengang des Empfängers", + replacement: (t, l)=>t.studyProgram.name[l], + }, + { + name: "abschluss", + description: "Abschluss des Empfängers", + replacement: (t, l)=>Degree[t.degree][l], + }, + { + name: "schulung.datum", + description: "Datum der Schulung des Empfängers", + replacement: (t, l) => t.training ? new Intl.DateTimeFormat(l, { year: "numeric", month: "long", day: "2-digit" }).format(Date.parse(t?.training?.date)) : {de: "keine Schulung", en: "no training"}[l], + }, + { + name: "schulung.ort", + description: "Ort der Schulung des Empfängers", + replacement: (t, l)=>t?.training?.location ?? {de: "keine Schulung", en: "no training"}[l], + }, + { + name: "cotutorwunsch", + description: "Name des gewünschten Co-Tutors", + replacement: t=>t.coTutorWish, + }, +]; + +export type Condition = { + name: string; + description: string; + arguments?: string; + condition: (t: Tutor, args: string[])=>boolean; +}; +export const conditions: Condition[] = [ + { + name: "hatCotutorWunsch", + description: "Der Tutor hat einen Co-Tutor-Wunsch", + condition: t=>!!t.coTutorWish, + }, + { + name: "geschult", + description: "Der Tutor wurde geschult", + condition: t=>t.trained, + }, + { + name: "hatSchulung", + description: "Der Tutor hat eine Schulung", + condition: t=>!!t.training, + }, + { + name: "schulung", + description: "Der Tutor hat eine bestimmte Schulung", + arguments: "Datum der Schulungen (YYYY-MM-DD)", + condition: (t, args)=>!!t.training && args.includes(t.training.date), + }, + { + name: "abschluss", + description: `Der Tutor strebt einen bestimmten Abschluss an (${Object.keys(Degree).join("/")})`, + condition: (t, args)=>args.includes(t.degree), + }, + { + name: "hatSpitzname", + description: "Der Tutor hat einen Spitznamen angegeben", + condition: t=>!!t.nickname, + }, + { + name: "mentor", + description: "Der Tutor ist Mentor", + condition: t=>t.mentor, + }, + { + name: "studiengang", + description: "Der Tutor ist in einem Studiengang", + arguments: `Liste aller zu prüfenden Studiengänge (deutscher Name)`, + condition: (t, args)=>args.includes(t.studyProgram.name.de), + }, + { + name: "essenseinschränkung", + description: "Der Tutor hat eine Essenseinschränkung", + condition: t=>!!t.dietaryRestriction, + }, + { + name: "geschlecht", + description: "Der Tutor hat ein bestimmtes Geschlecht", + arguments: `Geschlecht(er) (${Object.keys(Gender).join("/")})`, + condition: (t, args)=>args.includes(t.gender), + }, + { + name: "not", + description: "Negiert eine Bedingung", + arguments: "Bedingung, Argumente falls notwendig", + condition: (t, [name, ...args])=>{ + const condition = conditions.find(c=>c.name === name); + if(!condition) throw new Error(`Unknown condition ${name}`); + return !condition.condition(t, args); + }, + }, +]; + +export function compileTemplate(parts: Localized<Part[]>, tutor: Tutor, formatter: (l: Localized)=>string = ({de, en})=>`-------- english version below --------\n\n${de}\n\n-------- english version --------\n\n${en}`): string { + const result = {} as Localized<string>; + for(const locale of locales){ + result[locale] = parts[locale].map(p=>compilePart(p, tutor, locale)).join(""); + } + return formatter(result); +} + +function compilePart(part: Part, tutor: Tutor, locale: Locale): string { + switch(part.type){ + case "text": + return part.text; + case "variable": + return part.replacement(tutor, locale); + case "condition": + if(part.condition(tutor, part.args)){ + return part.then.map(p=>compilePart(p, tutor, locale)).join(""); + }else{ + return part.else.map(p=>compilePart(p, tutor, locale)).join(""); + } + } +} + +type Token = {type: "string"; value: string} | {type: "template", value: string}; +function tokenize(input: string): Token[] { + const tokens: Token[] = []; + let currentContent = ""; + let inTemplate = false; + for(let i = 0; i < input.length; i++){ + const c = input[i]; + if(!inTemplate){ + if(c === "{" && input[i+1] === "{"){ + if(currentContent){ + tokens.push({type: "string", value: currentContent}); + currentContent = ""; + } + inTemplate = true; + i++; + }else{ + currentContent += c; + } + }else{ + if(c === "}" && input[i+1] === "}"){ + if(!inTemplate) throw new Error(`Unexpected }} at ${i}`); + tokens.push({type: "template", value: currentContent}); + currentContent = ""; + inTemplate = false; + i++; + }else{ + currentContent += c; + } + } + } + if(currentContent){ + if(inTemplate){ + throw new Error("Unexpected end of template"); + }else{ + tokens.push({type: "string", value: currentContent}); + } + } + return tokens; +} + +type Part = {type: "text", text: string} | {type: "variable", replacement: Variable["replacement"]} | {type: "condition", condition: Condition["condition"], args: string[], then: Part[], else: Part[]}; +export function parseTemplate(template: Localized): Localized<Part[]>; +export function parseTemplate(template: string): Part[]; +export function parseTemplate(template: Localized|string){ + if(typeof template === "object"){ + const result = {} as Localized<Part[]>; + for(const locale of locales){ + result[locale] = parseTemplate(template[locale]); + } + return result; + } + const parts: Part[] = []; + const tokens = tokenize(template); + while(tokens.length){ + const token = tokens.shift()!; + if(token.type === "string"){ + parts.push({type: "text", text: token.value}); + }else if(token.type === "template"){ + const [name, ...args] = token.value.split(" "); + if(name === "if" && args.length > 0){ + const conditionName = args.shift(); + const condition = conditions.find(c=>c.name === conditionName); + if(!condition) throw new Error(`Unknown condition ${conditionName}`); + const [then, els] = consumeCondition(tokens); + parts.push({type: "condition", condition: condition.condition, args, then, else: els}); + }else{ + const variable = variables.find(v=>v.name === name); + if(!variable) throw new Error(`Unknown variable ${name}`); + parts.push({type: "variable", replacement: variable.replacement}); + } + } + } + return parts; +} + +function consumeCondition(tokens: Token[]): [Part[], Part[]] { + const thenParts: Part[] = []; + const elseParts: Part[] = []; + let inElse = false; + while(tokens.length){ + const token = tokens.shift()!; + if(token.type === "string"){ + if(inElse) elseParts.push({type: "text", text: token.value}); + else thenParts.push({type: "text", text: token.value}); + }else if(token.type === "template"){ + const [name, ...args] = token.value.split(" "); + if(name === "if" && args.length > 0){ + const conditionName = args.shift(); + const condition = conditions.find(c=>c.name === conditionName); + if(!condition) throw new Error(`Unknown condition ${conditionName}`); + const [then, els] = consumeCondition(tokens); + const part: Part = {type: "condition", condition: condition.condition, args, then, else: els}; + if(inElse) elseParts.push(part); + else thenParts.push(part); + }else if(name === "else"){ + if(inElse) throw new Error("Multiple else blocks"); + if(args.length > 0) { + if(args[0] !== "if") throw new Error(`Unexpected argument "${args[0]}" to else`); + const [, name, ...args2] = args; + const condition = conditions.find(c=>c.name === name); + if(!condition) throw new Error(`Unknown condition ${name}`); + const [then, els] = consumeCondition(tokens); + const part: Part = {type: "condition", condition: condition.condition, args: args2, then, else: els}; + elseParts.push(part); + return [thenParts, elseParts]; + } + inElse = true; + }else if(name === "/if"){ + return [thenParts, elseParts]; + }else{ + const variable = variables.find(v=>v.name === name); + if(!variable) throw new Error(`Unknown variable ${name}`); + if(inElse) elseParts.push({type: "variable", replacement: variable.replacement}); + else thenParts.push({type: "variable", replacement: variable.replacement}); + } + } + } + throw new Error("Unexpected end of template"); +} + +export function renderTemplate(compiledMarkdown: string): string { + return md.render(compiledMarkdown); +} diff --git a/src/lib/messages.ts b/src/lib/messages.ts new file mode 100644 index 0000000000000000000000000000000000000000..7a71a62fbe5a6d6f268371927c3e119159fd5f8c --- /dev/null +++ b/src/lib/messages.ts @@ -0,0 +1,31 @@ +import { browser } from "$app/environment"; +import { writable } from "svelte/store"; + +export type Message = { + type: "error" | "info" | "success" | "warning", + text: string, + duration?: number, + id: number, +}; + +let currMsgId = 0; + +export const messages = writable<Message[]>([]); + +export function addMessage({type, text, duration=10}: {type: Message["type"], text: string, duration?: number}): Message|undefined { + if(!browser) return; + const msg = { + id: currMsgId++, + type, + text, + duration, + } + messages.update(msgs => [...msgs, msg]); + return msg; +} + +export function removeMessage(message: number|Message){ + if(!browser) return; + const id = typeof message === "number" ? message : message.id; + messages.update(msgs => msgs.filter(msg => msg.id !== id)); +} diff --git a/src/lib/perms.ts b/src/lib/perms.ts new file mode 100644 index 0000000000000000000000000000000000000000..95f2fe8909640843e6af9bc3404e25c3503bd9e9 --- /dev/null +++ b/src/lib/perms.ts @@ -0,0 +1,65 @@ +export enum Permission { + ADMIN = 1<<0, + SEND_MAIL = 1<<1, + VIEW_TUTORS = 1<<2, + VIEW_TUTOR_DETAILS = 1<<3, + UPDATE_TUTOR = 1<<4, + ADD_DELETE_TUTOR = 1<<5, + VIEW_OLD_TUTORS = 1<<6, + UPDATE_SCHEDULE_CONFIG = 1<<7, + UPDATE_SCHEDULES = 1<<8, + UPDATE_STUDY_PROGRAMS = 1<<9, + EDIT_TRAININGS = 1<<10, + UPDATE_CONFIG = 1<<11, + UPDATE_MAIL_TEMPLATES = 1<<12, +} +export const PermissionDescription: {[permission in Permission]: string} = { + [Permission.ADMIN]: "Berechtigungen aller Nutzer setzen", + [Permission.SEND_MAIL]: "Senden von E-Mails an alle Tutoren oder Tutoren gewisser Gruppen", + [Permission.VIEW_TUTORS]: "Namen und Studiengang aller Tutoren sehen", + [Permission.VIEW_TUTOR_DETAILS]: "Alle Informationen der Tutoren sehen", + [Permission.UPDATE_TUTOR]: "Informationen von Tutoren bearbeiten", + [Permission.ADD_DELETE_TUTOR]: "Tutoren hinzufügen und löschen", + [Permission.VIEW_OLD_TUTORS]: "Informationen von ehemaligen Tutoren sehen", + [Permission.UPDATE_SCHEDULE_CONFIG]: "Ändern der Konfiguration für Stundenpläne, wie z.B. Farben", + [Permission.UPDATE_SCHEDULES]: "Erstellen, Bearbeiten und Löschen von Stundenplänen", + [Permission.UPDATE_STUDY_PROGRAMS]: "Erstellen, Bearbeiten und Löschen von Studiengängen", + [Permission.EDIT_TRAININGS]: "Erstellen, Bearbeiten und Löschen von Schulungen", + [Permission.UPDATE_CONFIG]: "Grundeinstellungen ändern", + [Permission.UPDATE_MAIL_TEMPLATES]: "E-Mail-Vorlagen bearbeiten", +}; + +export class PermissionSet { + private readonly perms: number; + constructor(perms: number){ + this.perms = perms; + } + has(perm: Permission){ + return (this.perms & perm) === perm; + } + hasAny(...perms: Permission[]){ + return (this.perms & perms.reduce((a, b)=>a|b, 0)) > 0; + } + hasAll(...perms: Permission[]){ + const combined = perms.reduce((a, b)=>a|b, 0); + return (this.perms & combined) === combined; + } + with(...perms: Permission[]){ + return new PermissionSet(perms.reduce((a, b)=>a|b, this.perms)); + } + without(...perms: Permission[]){ + return new PermissionSet(this.perms&~perms.reduce((a, b)=>a|b, 0)); + } + raw(){ + return this.perms; + } + static none(){ + return new PermissionSet(0); + } + static all(){ + return new PermissionSet(Object.values(Permission) + .map(x=>Number(x)) + .filter(x=>!isNaN(x)) + .reduce((a, b)=>a|b, 0)); + } +} diff --git a/src/lib/polyfill.ts b/src/lib/polyfill.ts new file mode 100644 index 0000000000000000000000000000000000000000..4ed5d98e4c47ece5cad54f73839122b5f54bdb04 --- /dev/null +++ b/src/lib/polyfill.ts @@ -0,0 +1,5 @@ +Promise.withResolvers = Promise.withResolvers || function withResolvers() { + let resolve, reject; + const promise = new Promise((res, rej) => { resolve = res; reject = rej; }); + return { promise, resolve, reject }; +}; diff --git a/src/lib/schedules.ts b/src/lib/schedules.ts deleted file mode 100644 index 24705071bdc5bc1d3d7b5c1fd17cf1631938bd62..0000000000000000000000000000000000000000 --- a/src/lib/schedules.ts +++ /dev/null @@ -1,1066 +0,0 @@ -import { type CanvasRenderingContext2D, createCanvas, Image, registerFont } from "canvas"; -import type { Locales } from "./i18n/i18n-types"; - -type Timeslot = { start: number, end: number, title: string, location?: string }; -type TimeslotWithConcurrent = Timeslot & { concurrent: TimeslotWithConcurrent[], maxConcurrent: number, maxNeighborConcurrent: number, startSlot: number, slots: number }; -type Day = { date: string, timeslots: Timeslot[] }; -export type Schedule = { lang: Locales, title: string, updated: string, days: Day[] }; -export type CanvasType = "image" |"svg" | "pdf"; -export type Config = typeof defaultConfig; -export const defaultConfig = { - fontWeight: "100", - fontSize: 20, - fontFamily: '"Helvetica Neue", Roboto, Arial', - titleFontWeight: "600", - titleFontSize: 45, - startTime: 9 * 60 + 0, - endTime: 22 * 60 + 30, - dayWidth: 18, //rem - timeSlotHeight: 1.6, //rem - timeColWidth: 5.5, //rem - lineWidth: 3, - padding: 0, - activityBackgroundColor: "#a20f35", - activityTextColor: "#ffffff", - borderColor: "#000000", - textColor: "#000000", - brandingImageDistance: 30, - branding: { - de: "Fachschaft für\nMathematik/Physik/Informatik\nAugustinerbach 2a, 52062 Aachen", - en: "Student Council for\nMathematics/Physics/Computer Science\nAugustinerbach 2a, 52062 Aachen", - } satisfies {[lang in Locales]: string}, - phoneNumber: "+49 241 80 94506", - emailAddress: "esa@fsmpi.rwth-aachen.de", - website: "www.fsmpi.rwth-aachen.de", - stateAsOf: { - de: "Stand: %%", - en: "State as of: %%", - } satisfies {[lang in Locales]: string}, -}; -export const defaultDarkmodeConfig: Config = {...defaultConfig, textColor: "#ffffff", borderColor: "#ffffff"}; -export const exampleSchedule: Schedule = { - lang: "de", - title: "Erstsemestereinführung WS 23/24 - Informatik", - updated: "2023-09-26", - days: [ - { - date: "2023-10-02", - timeslots: [ - { - title: "Rektoreinführung", - location: "CARL", - start: 10 * 60 + 30, - end: 11 * 60 + 30, - }, - { - title: "Fach- und Fachschaftsvorstellung", - location: "Audimax Großer Hörsaal", - start: 12 * 60 + 0, - end: 13 * 60 + 0, - }, - { - title: "Tutorienzeit", - start: 13 * 60 + 0, - end: 15 * 60 + 30, - }, - { - title: "Profeninterviews", - location: "AH IV", - start: 15*60+30, - end: 17*60+30, - }, - { - title: "Erstiparty", - location: "Lessie", - start: 21 * 60 + 0, - end: 24 * 60 + 0, - }, - ], - }, - { - date: "2023-10-03", - timeslots: [ - { - title: "Tutorienzeit:\nGeocaching", - start: 10 * 60 + 0, - end: 18 * 60 + 0, - }, - { - title: "Spieleabend", - location: "Semi-Temp", - start: 18 * 60 + 0, - end: 22 * 60 + 0, - }, - ], - }, - { - date: "2023-10-04", - timeslots: [ - { - title: "Grillfest", - location: "Infozentrum", - start: 12 * 60 + 0, - end: 17 * 60 + 0, - }, - { - title: "Führungen: Infozentrum, Insitute, Lehrstühle", - start: 13 * 60 + 0, - end: 16 * 60 + 0, - }, - { - title: "Tutorienzeit", - start: 17 * 60 + 0, - end: 20 * 60 + 0, - }, - ], - }, - { - date: "2023-10-05", - timeslots: [ - { - title: "Rallye-Einführung", - start: 10 * 60 + 0, - end: 10 * 60 + 30, - }, - { - title: "Erstirallye", - start: 10 * 60 + 30, - end: 16*60+0, - }, - { - title: "Siegerehrung", - start: 18 * 60 + 0, - end: 18 * 60 + 30, - }, - ], - }, - { - date: "2023-10-06", - timeslots: [ - { - title: "Tutorienzeit: Frühstück", - start: 9 * 60 + 0, - end: 10 * 60 + 0, - }, - { - title: "Tutorienzeit:\nRWTHOnline-Einführung", - start: 10 * 60 + 0, - end: 16 * 60 + 0, - }, - { - title: "Gamingabend", - location: "Semi-Temp", - start: 18 * 60 + 0, - end: 22 * 60 + 0, - }, - ], - }, - ], -}; -exampleSchedule.days.forEach(day => day.timeslots = day.timeslots.sort((a, b) => a.start === b.start ? b.end - a.end : a.start - b.start)); -const scheduleInformatik = exampleSchedule; -const schedulePhysik: Schedule = { - title: "Erstsemestereinführung WS 23/24 - Physik", - lang: "de", - updated: "2023-09-26", - days: [ - { - date: "2023-10-02", - timeslots: [ - { - title: "Rektoreinführung", - location: "CARL", - start: 10*60+30, - end: 11*60+30, - }, - { - title: "Fach- und Fachschaftsvorstellung", - location: "Audimax Rot", - start: 12*60+0, - end: 13*60+0, - }, - { - title: "Tutorienzeit", - start: 13*60+0, - end: 15*60+30, - }, - { - title: "Profeninterviews", - location: "AH V", - start: 15*60+30, - end: 17*60+30, - }, - { - title: "Erstiparty", - location: "Lessie", - start: 21*60+0, - end: 24*60+0, - }, - ], - }, - { - date: "2023-10-03", - timeslots: [ - { - title: "Tutorienzeit:\nGeocaching", - start: 10*60+0, - end: 18*60+0, - }, - { - title: "Spieleabend", - location: "Semi-Temp", - start: 18*60+0, - end: 22*60+0, - }, - ], - }, - { - date: "2023-10-04", - timeslots: [ - { - title: "Grillfest", - location: "Infozentrum", - start: 12 * 60 + 0, - end: 17 * 60 + 0, - }, - { - title: "Führungen: Infozentrum, Insitute, Lehrstühle", - start: 13 * 60 + 0, - end: 16 * 60 + 0, - }, - { - title: "Tutorienzeit", - start: 17 * 60 + 0, - end: 20 * 60 + 0, - }, - ], - }, - { - date: "2023-10-05", - timeslots: [ - { - title: "Rallye-Einführung", - start: 10*60+0, - end: 10*60+30, - }, - { - title: "Erstirallye", - start: 10*60+30, - end: 16*60+0, - }, - { - title: "Siegerehrung", - start: 18*60+0, - end: 18*60+30, - }, - ], - }, - { - date: "2023-10-06", - timeslots: [ - { - title: "Tutorienzeit: Frühstück", - start: 9*60+0, - end: 10*60+0, - }, - { - title: "Tutorienzeit:\nRWTHOnline-Einführung", - start: 10*60+0, - end: 16*60+0, - }, - { - title: "Gamingabend", - location: "Semi-Temp", - start: 18*60+0, - end: 22*60+0, - }, - ], - }, - ], -}; -const scheduleMathematik: Schedule = { - title: "Erstsemestereinführung WS 23/24 - Mathematik", - lang: "de", - updated: "2023-09-26", - days: [ - { - date: "2023-10-02", - timeslots: [ - { - title: "Rektoreinführung", - location: "CARL", - start: 10*60+30, - end: 11*60+30, - }, - { - title: "Fach- und Fachschaftsvorstellung", - location: "Audimax Grün", - start: 12*60+0, - end: 13*60+0, - }, - { - title: "Tutorienzeit", - start: 13*60+0, - end: 15*60+30, - }, - { - title: "Profeninterviews", - location: "AH III", - start: 15*60+30, - end: 17*60+30, - }, - { - title: "Erstiparty", - location: "Lessie", - start: 21*60+0, - end: 24*60+0, - }, - ], - }, - { - date: "2023-10-03", - timeslots: [ - { - title: "Tutorienzeit:\nGeocaching", - start: 10 * 60 + 0, - end: 18 * 60 + 0, - }, - { - title: "Spieleabend", - location: "Semi-Temp", - start: 18 * 60 + 0, - end: 22 * 60 + 0, - }, - ], - }, - { - date: "2023-10-04", - timeslots: [ - { - title: "Anwendungsfachvorstellung", - location: "Audimax Grün", - start: 9*60+30, - end: 11*60+30, - }, - { - title: "Grillfest", - location: "Infozentrum", - start: 12 * 60 + 0, - end: 17 * 60 + 0, - }, - { - title: "Führungen: Infozentrum, Insitute, Lehrstühle", - start: 13 * 60 + 0, - end: 16 * 60 + 0, - }, - { - title: "Tutorienzeit", - start: 17 * 60 + 0, - end: 20 * 60 + 0, - }, - ], - }, - { - date: "2023-10-05", - timeslots: [ - { - title: "Rallye-Einführung", - start: 10 * 60 + 0, - end: 10 * 60 + 30, - }, - { - title: "Erstirallye", - start: 10 * 60 + 30, - end: 16*60+0, - }, - { - title: "Siegerehrung", - start: 18 * 60 + 0, - end: 18 * 60 + 30, - }, - ], - }, - { - date: "2023-10-06", - timeslots: [ - { - title: "Tutorienzeit: Frühstück", - start: 9 * 60 + 0, - end: 10 * 60 + 0, - }, - { - title: "Tutorienzeit:\nRWTHOnline-Einführung", - start: 10 * 60 + 0, - end: 16 * 60 + 0, - }, - { - title: "Gamingabend", - location: "Semi-Temp", - start: 18 * 60 + 0, - end: 22 * 60 + 0, - }, - ], - }, - ], -}; const scheduleWirtschaftsmathematik: Schedule = { - title: "Erstsemestereinführung WS 23/24 - Wirtschaftsmathematik", - lang: "de", - updated: "2023-09-26", - days: [ - { - date: "2023-10-02", - timeslots: [ - { - title: "Rektoreinführung", - location: "CARL", - start: 10*60+30, - end: 11*60+30, - }, - { - title: "Fach- und Fachschaftsvorstellung", - location: "Hauptgebäude IV", - start: 12*60+0, - end: 13*60+0, - }, - { - title: "Tutorienzeit", - start: 13*60+0, - end: 15*60+30, - }, - { - title: "Profeninterviews", - location: "AH III", - start: 15*60+30, - end: 17*60+30, - }, - { - title: "Grillen der WiWis", - location: "Platanenplatz", - start: 14*60+0, - end: 17*60+30, - }, - { - title: "Erstiparty", - location: "Lessie", - start: 21*60+0, - end: 24*60+0, - }, - ], - }, - { - date: "2023-10-03", - timeslots: [ - { - title: "Tutorienzeit:\nGeocaching", - start: 10 * 60 + 0, - end: 18 * 60 + 0, - }, - { - title: "Spieleabend", - location: "Semi-Temp", - start: 18 * 60 + 0, - end: 22 * 60 + 0, - }, - ], - }, - { - date: "2023-10-04", - timeslots: [ - { - title: "Grillfest", - location: "Infozentrum", - start: 12 * 60 + 0, - end: 17 * 60 + 0, - }, - { - title: "Führungen: Infozentrum, Insitute, Lehrstühle", - start: 13 * 60 + 0, - end: 16 * 60 + 0, - }, - { - title: "Tutorienzeit", - start: 17 * 60 + 0, - end: 20 * 60 + 0, - }, - ], - }, - { - date: "2023-10-05", - timeslots: [ - { - title: "Rallye-Einführung", - start: 10 * 60 + 0, - end: 10 * 60 + 30, - }, - { - title: "Erstirallye", - start: 10 * 60 + 30, - end: 16*60+0, - }, - { - title: "Siegerehrung", - start: 18 * 60 + 0, - end: 18 * 60 + 30, - }, - ], - }, - { - date: "2023-10-06", - timeslots: [ - { - title: "Tutorienzeit: Frühstück", - start: 9 * 60 + 0, - end: 10 * 60 + 0, - }, - { - title: "Tutorienzeit:\nRWTHOnline-Einführung", - start: 10 * 60 + 0, - end: 16 * 60 + 0, - }, - { - title: "RedBull-Event der WiWis", - location: "Platanenplatz", - start: 14*60+0, - end: 18*60+0, - }, - { - title: "Gamingabend", - location: "Semi-Temp", - start: 18 * 60 + 0, - end: 22 * 60 + 0, - }, - ], - }, - ], -}; -const scheduleLehramt: Schedule = { - title: "Erstsemestereinführung WS 23/24 - Lehramt MPI", - lang: "de", - updated: "2023-09-26", - days: [ - { - date: "2023-10-02", - timeslots: [ - { - title: "Rektoreinführung", - location: "CARL", - start: 9*60+0, - end: 10*60+0, - }, - { - title: "Fach- und Fachschaftsvorstellung", - location: "HKW 4", - start: 10*60+0, - end: 11*60+30, - }, - { - title: "Tutorienzeit", - start: 11*60+30, - end: 15*60+30, - }, - { - title: "Profeninterviews", - location: "Informatik: AH IV\nPhysik: AH V\nMathematik: AH III", - start: 15*60+30, - end: 17*60+30, - }, - { - title: "Erstiparty", - location: "Lessie", - start: 21*60+0, - end: 24*60+0, - }, - ], - }, - { - date: "2023-10-03", - timeslots: [ - { - title: "Tutorienzeit:\nGeocaching", - start: 10 * 60 + 0, - end: 18 * 60 + 0, - }, - { - title: "Spieleabend", - location: "Semi-Temp", - start: 18 * 60 + 0, - end: 22 * 60 + 0, - }, - ], - }, - { - date: "2023-10-04", - timeslots: [ - { - title: "Grillfest und Führungen: Infozentrum, Insitute, Lehrstühle", - location: "Infozentrum", - start: 12 * 60 + 0, - end: 17 * 60 + 0, - }, - { - title: "Facheinführung BiWi", - location: "H05", - start: 10 * 60 + 0, - end: 11 * 60 + 30, - }, - { - title: "Facheinführung Physik (H05), Mathe (Temp1) und BiWi (H05)", - start: 13*60+0, - end: 14*60+30, - }, - { - title: "Facheinführung Mathe (Temp1) und BiWi (H05)", - start: 14*60+30, - end: 16*60+0, - }, - { - title: "Facheinführung Informatik", - location: "AH V", - start: 16*60+0, - end: 17*60+30, - }, - { - title: "Tutorienzeit", - start: 18 * 60 + 0, - end: 20 * 60 + 0, - }, - ], - }, - { - date: "2023-10-05", - timeslots: [ - { - title: "Rallye-Einführung", - start: 10 * 60 + 0, - end: 10 * 60 + 30, - }, - { - title: "Erstirallye", - start: 10 * 60 + 30, - end: 16*60+0, - }, - { - title: "Siegerehrung", - start: 18 * 60 + 0, - end: 18 * 60 + 30, - }, - ], - }, - { - date: "2023-10-06", - timeslots: [ - { - title: "Tutorienzeit: Frühstück", - start: 9 * 60 + 0, - end: 10 * 60 + 0, - }, - { - title: "Tutorienzeit:\nRWTHOnline-Einführung", - start: 10 * 60 + 0, - end: 16 * 60 + 0, - }, - { - title: "Gamingabend", - location: "Semi-Temp", - start: 18 * 60 + 0, - end: 22 * 60 + 0, - }, - ], - }, - ], -}; -const scheduleMaster: Schedule = { - title: "Erstsemestereinführung WS 23/24 - Master", - lang: "de", - updated: "2023-09-26", - days: [ - { - date: "2023-10-02", - timeslots: [ - { - title: "Tutorienzeit", - location: "E3 9222", - start: 14*60+0, - end: 17*60+0, - }, - { - title: "Erstiparty", - location: "Lessie", - start: 21*60+0, - end: 24*60+0, - }, - ], - }, - { - date: "2023-10-03", - timeslots: [ - { - title: "Tutorienzeit:\nGeocaching", - start: 10 * 60 + 0, - end: 18 * 60 + 0, - }, - { - title: "Spieleabend", - location: "Semi-Temp", - start: 18 * 60 + 0, - end: 22 * 60 + 0, - }, - ], - }, - { - date: "2023-10-04", - timeslots: [ - { - title: "Grillfest", - location: "Infozentrum", - start: 12 * 60 + 0, - end: 17 * 60 + 0, - }, - { - title: "Führungen: Infozentrum, Insitute, Lehrstühle", - start: 13 * 60 + 0, - end: 16 * 60 + 0, - }, - { - title: "Master-Rockt", - start: 17 * 60 + 0, - end: 23 * 60 + 0, - }, - ], - }, - { - date: "2023-10-05", - timeslots: [ - { - title: "Rallye-Einführung", - start: 10 * 60 + 0, - end: 10 * 60 + 30, - }, - { - title: "Erstirallye", - start: 10 * 60 + 30, - end: 16*60+0, - }, - { - title: "Siegerehrung", - start: 18 * 60 + 0, - end: 18 * 60 + 30, - }, - ], - }, - { - date: "2023-10-06", - timeslots: [ - { - title: "Mastereinführung", - location: "Mathematik: E3 9222\nPhysik: 28D001", - start: 10*60+0, - end: 13*60+0, - }, - { - title: "Mastercafé", - location: "E3 9222", - start: 14*60+0, - end: 16*60+0, - }, - { - title: "Gamingabend", - location: "Semi-Temp", - start: 18*60+0, - end: 22*60+0, - }, - ], - }, - ], -}; -const scheduleMasterEn: Schedule = { - title: "Freshers' Orientation WS 23/24 - Master", - lang: "en", - updated: "2023-09-26", - days: [ - { - date: "2023-10-02", - timeslots: [ - { - title: "Group Time", - location: "E3 9222", - start: 14 * 60 + 0, - end: 17 * 60 + 0, - }, - { - title: "Freshers' Party", - location: "Lessie", - start: 21 * 60 + 0, - end: 24 * 60 + 0, - }, - ], - }, - { - date: "2023-10-03", - timeslots: [ - { - title: "Group Time:\nGeocaching", - start: 10 * 60 + 0, - end: 18 * 60 + 0, - }, - { - title: "Boardgame Night", - location: "Semi-Temp", - start: 18 * 60 + 0, - end: 22 * 60 + 0, - }, - ], - }, - { - date: "2023-10-04", - timeslots: [ - { - title: "Barbecue", - location: "Computer Science Center", - start: 12 * 60 + 0, - end: 17 * 60 + 0, - }, - { - title: "Tours: Computer Science Center, Insitutes, Departments", - start: 13 * 60 + 0, - end: 16 * 60 + 0, - }, - { - title: "Master-Rockt\n(progressive dinner)", - start: 17 * 60 + 0, - end: 23 * 60 + 0, - }, - ], - }, - { - date: "2023-10-05", - timeslots: [ - { - title: "Rally Introduction", - start: 10 * 60 + 0, - end: 10 * 60 + 30, - }, - { - title: "Freshers' Rally", - start: 10 * 60 + 30, - end: 16*60+0, - }, - { - title: "Award Ceremony", - start: 18 * 60 + 0, - end: 18 * 60 + 30, - }, - ], - }, - { - date: "2023-10-06", - timeslots: [ - { - title: "Master Introduction", - location: "Mathematics: E3 9222\nPhysics: 28D001", - start: 10 * 60 + 0, - end: 13 * 60 + 0, - }, - { - title: "Master Café", - location: "E3 9222", - start: 14 * 60 + 0, - end: 16 * 60 + 0, - }, - { - title: "Gaming Night", - location: "Semi-Temp", - start: 18 * 60 + 0, - end: 22 * 60 + 0, - }, - ], - }, - ], -}; - -export const schedules = [scheduleMathematik, schedulePhysik, scheduleInformatik, scheduleWirtschaftsmathematik, scheduleLehramt, scheduleMaster, scheduleMasterEn]; - - -type DrawingConfig = Config & {timeSlots: number, tableWidth: number, tableHeight: number, fullWidth: number, fullHeight: number, marginX: number, marginTop: number, marginBottom: number, brandingImageScale: number}; -export function renderSchedule(config: Config, data: Schedule, brandingImage: Image|undefined, brandingImageScale: number, type: CanvasType){ - registerFont("src/lib/HelveticaNeueLTStd45Light.otf", { - family: "Helvetica Neue", - }); - const dayWidth = Math.ceil(config.dayWidth * config.fontSize); - const timeSlotHeight = Math.ceil(config.timeSlotHeight * config.fontSize); - const timeColWidth = Math.ceil(config.timeColWidth * config.fontSize); - const timeSlots = Math.round((config.endTime - config.startTime) / 30); - const tableWidth = data.days.length * dayWidth + 2 * timeColWidth; - const tableHeight = (timeSlots + 1) * timeSlotHeight; - const halfBorder = Math.ceil(config.lineWidth / 2); - const marginX = halfBorder + config.padding; - const marginTop = (((brandingImage?.height || 0) * brandingImageScale / 100) || -config.brandingImageDistance) + config.brandingImageDistance + halfBorder + config.padding; - const marginBottom = Math.ceil(30 + 1.5 * config.titleFontSize + 3.55 * config.fontSize) + halfBorder + config.padding; - const fullWidth = tableWidth + 2 * marginX; - const fullHeight = tableHeight + marginTop + marginBottom; - const canvas = createCanvas(fullWidth, fullHeight, type==="image"?undefined:type); - const ctx = canvas.getContext("2d"); - if(type!=="image") ctx.textDrawingMode = "glyph"; - const configCopy: DrawingConfig = { - ...config, - dayWidth, - timeSlotHeight, - timeColWidth, - timeSlots, - tableWidth, - tableHeight, - fullWidth, - fullHeight, - marginX, - marginTop, - marginBottom, - brandingImageScale, - }; - draw(ctx, configCopy, data, brandingImage); - return {context: ctx, canvas}; -} - -function collect(set: Set<TimeslotWithConcurrent>, element: TimeslotWithConcurrent) { - if(set.has(element)) return set; - set.add(element); - for(const ts of element.concurrent) collect(set, ts); - return set; -} -function getFirstFreeSlot(timeslot: TimeslotWithConcurrent) { - const used: boolean[] = Array(timeslot.maxConcurrent).fill(false); - for(const ts of timeslot.concurrent) { - for(let i = 0; i < ts.slots; i++) used[ts.startSlot + i] = true; - } - return used.findIndex(x => !x); -} -function getConcurrentCount(timeslot: TimeslotWithConcurrent){ - const actions: {time: number, change: number}[] = []; - for(const ts of [...timeslot.concurrent, timeslot]){ - actions.push({time: ts.start, change: 1}, {time: ts.end, change: -1}); - } - return actions - .sort((a, b)=>a.time===b.time?a.change-b.change:a.time-b.time) - .reduce(({current, max}, action)=>({max: Math.max(max, current+action.change), current: current+action.change}), {current: 0, max: 0}) - .max; -} -function drawText(ctx: CanvasRenderingContext2D, color: string, fontSize: number, text: string, x: number, y: number, maxWidth: number) { - fontSize *= 1.25; - ctx.fillStyle = color; - const lines = text.split("\n").flatMap(line => { - if(ctx.measureText(line).width <= maxWidth) return [line]; - const parts: string[] = []; - let i = 0; - for(let j = 0; j < line.length; j++) { - if([" ", "-"].includes(line[j])) { - parts.push(line.slice(i, j + 1)); - i = j + 1; - } - } - parts.push(line.slice(i)); - const lines: string[] = []; - while(parts.length > 0) { - let i = 1; - let line = parts[0]; - while(i < parts.length && ctx.measureText((line + parts[i]).trim()).width <= maxWidth) { - line += parts[i]; - i++; - } - if(line.trim()) lines.push(line.trim()); - parts.splice(0, i); - } - return lines; - }); - const baseY = y - (lines.length - 1) / 2 * fontSize; - for(let i = 0; i < lines.length; i++) { - ctx.fillText(lines[i], x, baseY + i * fontSize, maxWidth); - } -} -function drawOutline(ctx: CanvasRenderingContext2D, x: number, y: number, w: number, h: number, borderColor: string, lineWidth: number){ - ctx.strokeStyle = borderColor; - ctx.lineWidth = lineWidth; - ctx.strokeRect(x, y, w, h); -} -function drawBox(ctx: CanvasRenderingContext2D, x: number, y: number, w: number, h: number, backgroundColor: string|null, borderColor: string, lineWidth: number, textColor: string, fontSize: number, caption: string){ - if(backgroundColor){ - ctx.fillStyle = backgroundColor; - ctx.fillRect(x, y, w, h); - } - drawOutline(ctx, x, y, w, h, borderColor, lineWidth); - if(caption) drawText(ctx, textColor, fontSize, caption, x + w/2, y + h/2, w - lineWidth); -} -function draw(ctx: CanvasRenderingContext2D, config: DrawingConfig, data: Schedule, brandingImage: Image|undefined){ - const timeFormatter = new Intl.DateTimeFormat(data.lang, { hour: "2-digit", minute: "2-digit" }); - const dateFormatter = new Intl.DateTimeFormat(data.lang, { day: "2-digit", month: "short", year: "numeric", weekday: "long" }); - const standardFont = `${config.fontWeight} ${config.fontSize}px ${config.fontFamily}`; - const titleFont = `${config.fontWeight} ${config.titleFontSize}px ${config.fontFamily}`; - ctx.font = standardFont; - ctx.textAlign = "center"; - ctx.textBaseline = "middle"; - const dateObj = new Date(); - ctx.translate(config.marginX, config.marginTop); - drawOutline(ctx, 0, 0, config.tableWidth, config.tableHeight, config.borderColor, config.lineWidth); - ctx.translate(0, config.timeSlotHeight); - for(let y = -1; y < config.timeSlots; y++) { - for(const baseX of [0, config.tableWidth - config.timeColWidth]) { - drawBox(ctx, baseX, y * config.timeSlotHeight, config.timeColWidth, config.timeSlotHeight, null, config.borderColor, config.lineWidth, config.textColor, config.fontSize, y < 0 ? "" : timeFormatter.format(dateObj.setHours(0, 30 * y + config.startTime))); - } - } - ctx.translate(config.timeColWidth, 0); - for(let x = 0; x < data.days.length; x++) { - drawOutline(ctx, 0, 0, config.dayWidth, config.timeSlotHeight * config.timeSlots, config.borderColor, config.lineWidth); - drawBox(ctx, 0, 0, config.dayWidth, -config.timeSlotHeight, null, config.borderColor, config.lineWidth, config.textColor, config.fontSize, dateFormatter.format(new Date(data.days[x].date))); - const timeslots: TimeslotWithConcurrent[] = data.days[x].timeslots.map(timeslot => { - const concurrent: TimeslotWithConcurrent[] = []; - return { ...timeslot, concurrent, maxConcurrent: 0, maxNeighborConcurrent: 0, startSlot: -1, slots: 0 }; - }); - for(const timeslot of timeslots){ - timeslot.concurrent = timeslots.filter(ts => ts !== timeslot && !(ts.end <= timeslot.start || ts.start >= timeslot.end)); - timeslot.maxConcurrent = getConcurrentCount(timeslot); - } - for(const timeslot of timeslots){ - timeslot.maxNeighborConcurrent = Math.max(1, ...timeslot.concurrent.map(ts => ts.maxConcurrent)); - } - const alreadyChecked: Set<TimeslotWithConcurrent> = new Set(); - for(const timeslot of timeslots){ - if(alreadyChecked.has(timeslot)) continue; - const set = collect(new Set(), timeslot); - const max = Math.max(...[...set].map(ts => ts.maxConcurrent)); - for(const ts of set) { - ts.maxConcurrent = max; - alreadyChecked.add(ts); - } - } - for(const timeslot of timeslots) { - timeslot.startSlot = getFirstFreeSlot(timeslot); - timeslot.slots = 1; - } - for(const timeslot of timeslots) { - const nthW = config.dayWidth / timeslot.maxConcurrent; - const w = timeslot.slots * nthW; - const x = timeslot.startSlot * nthW; - const y = Math.max(0, (timeslot.start - config.startTime) / 30 * config.timeSlotHeight); - const h = Math.min(config.tableHeight - config.timeSlotHeight - y, (timeslot.end - timeslot.start) / 30 * config.timeSlotHeight); - let text = timeslot.title; - if(timeslot.location) text += `\n(${timeslot.location})`; - drawBox(ctx, x, y, w, h, config.activityBackgroundColor, config.borderColor, config.lineWidth, config.activityTextColor, config.fontSize, text); - } - ctx.translate(config.dayWidth, 0); - } - ctx.resetTransform(); - ctx.translate(config.marginX, config.marginTop); - if(brandingImage){ - const w = brandingImage.width * config.brandingImageScale / 100; - const h = brandingImage.height * config.brandingImageScale / 100; - ctx.drawImage(brandingImage, config.tableWidth - w, -h - config.brandingImageDistance, w, h); - } - ctx.translate(0, config.tableHeight + 30 + config.titleFontSize / 2 + Math.ceil(config.lineWidth / 2)); - ctx.font = titleFont; - drawText(ctx, config.textColor, config.titleFontSize, data.title, config.tableWidth / 2, 0, config.tableWidth); - ctx.translate(0, config.titleFontSize + 1.8 * config.fontSize); - ctx.font = standardFont; - const updateDateFormatter = new Intl.DateTimeFormat(data.lang, {day: "2-digit", month: "short", year: "numeric"}); - drawText(ctx, config.textColor, config.fontSize, config.branding[data.lang], (config.timeColWidth + config.dayWidth) / 2, 0, config.timeColWidth + config.dayWidth); - drawText(ctx, config.textColor, config.fontSize, `${config.emailAddress}\nTel: ${config.phoneNumber}`, config.tableWidth / 2, 0, config.tableWidth); - drawText(ctx, config.textColor, config.fontSize, `${config.website}\n${config.stateAsOf[data.lang].replace("%%", updateDateFormatter.format(new Date(data.updated)))}`, config.tableWidth - (config.timeColWidth + config.dayWidth) / 2, 0, config.timeColWidth + config.dayWidth); - ctx.stroke(); -} diff --git a/src/lib/server/config.js b/src/lib/server/config.js deleted file mode 100644 index 20d50969aace000cb321f4e9ec6c37f0bf18e379..0000000000000000000000000000000000000000 --- a/src/lib/server/config.js +++ /dev/null @@ -1,81 +0,0 @@ -/* eslint-disable require-await */ -// mock config.js while database is not set up yet -// TODO remove after database is set up -export const config = { - yearOfSemester: "2023/24", - fresherWeekStart: "2023-10-02", - fresherWeekEnd: "2023-10-06", - tutorRegistrationOpen: true, - shirtSizes: "S;M;L;XL;2L;3XL;4XL", - rallyeRegistrationOpen: false, - rallyeDate: "2023-10-05", - rallyePreMeetingDate: "2023-10-04", - tutorTrainings: [ - { - date: "2023-08-05", - place: "E3, Informatikzentrum", - maxParticipants: 30, - }, - ], - tutors: [ - { - id: 1, - firstname: "Max", - lastname: "Mustermann", - nickname: "Maxi", - birthday: "2000-01-01", - email: "max.mustermann@rwth-aachen.de", - phone: "0123456789", - shirtSize: "M", - address: "Musterstraße 1, 12345 Musterstadt", - gender: "männlich", - studyProgram: "Informatik", - degree: "Bachelor", - training: "2023-08-05", - mentor: true, - coTutorWish: "", - trained: false, - comment: "", - tutorialId: 1, - tutorial: undefined, - }, - { - id: 2, - firstname: "Maxine", - lastname: "Musterfrau", - nickname: "", - birthday: "2000-01-01", - email: "maxine.musterfrau@rwth-aachen.de", - phone: "0123456789", - shirtSize: "S", - address: "Musterstraße 1, 12345 Musterstadt", - gender: "weiblich", - studyProgram: "Informatik", - degree: "Bachelor", - training: "-", - mentor: false, - coTutorWish: "", - trained: false, - comment: "", - tutorialId: 1, - tutorial: undefined, - }, - ], - tutorials: [ - { - id: 1, - name: "C++", - studyProgram: "Informatik", - }, - ], - waitlist: ["Physik"], - rallyeStationSupervisors: [], -}; - -export const getCurrentYearOfSemester = async () => config.yearOfSemester; -export const getFresherWeekStart = async () => config.fresherWeekStart; -export const getFresherWeekEnd = async () => config.fresherWeekEnd; -export const isTutorRegistrationOpen = async () => config.tutorRegistrationOpen; -export const getShirtSizes = async () => config.shirtSizes.split(";"); -export const isRallyeRegistrationOpen = async () => config.rallyeRegistrationOpen; -export const getRallyeDate = async () => config.rallyeDate; diff --git a/src/lib/server/database/db.ts b/src/lib/server/database/db.ts new file mode 100644 index 0000000000000000000000000000000000000000..0f9f2e59f9f42e6751aea9438edc28e46499c461 --- /dev/null +++ b/src/lib/server/database/db.ts @@ -0,0 +1,66 @@ +/* + * About migrations: + * File path: src/lib/server/database/migrations + * File name: \d+_.*\.sql + * Naming: <order>_<description>.sql + * <order> - number that represents the order in which the migration should be run + * <description> - short description of the migration + * The order is interpreted as integer, leading zeros are ignored. + * File content: SQL code + * Important: after execution/committing neither filename nor SQL code should be changed. + * The filename is used as key, so *any* to it change will be treated as a new migration. + */ + +import postgres, { type Sql } from "postgres"; +import fs from "node:fs/promises"; +import { env } from "$env/dynamic/private"; +import { building } from "$app/environment"; + +// fix for ARRAY_AGG on LEFT JOIN returning {NULL} instead of {null} or empty array +(()=>{ + const original = JSON.parse; + JSON.parse = (text, reviver)=>{ + if(text === "NULL") return null; + else return original(text, reviver); + }; +})(); + +export const sql = building ? null as unknown as Sql : await init(postgres({ + host: env.POSTGRES_HOST, + database: env.POSTGRES_DB, + user: env.POSTGRES_USER, + password: env.POSTGRES_PASSWORD, + port: parseInt(env.POSTGRES_PORT || "5432"), + transform: postgres.camel, +})); + +async function init(sql: Sql) { + await sql` + CREATE TABLE IF NOT EXISTS migrations ( + id SERIAL PRIMARY KEY, + name TEXT, + dateMigrated TIMESTAMP NOT NULL DEFAULT NOW() + ); + `; + const basePath = "src/lib/server/database/migrations"; + const migrationFiles = (await fs.readdir(basePath)) + .filter(name => name.endsWith(".sql")) + .map(name => ({ + filePath: `${basePath}/${name}`, + name: name.substring(0, name.length - 4), + sortKey: parseInt(name.split("_")[0]), + })) + .sort((a, b) => a.sortKey - b.sortKey); + const migrations = (await sql`SELECT name FROM migrations`).map(row => row.name as string); + for(const migration of migrationFiles){ + if(migrations.includes(migration.name)) continue; + console.log(`Running migration: ${migration.name}`); + // migrations must run in order + // eslint-disable-next-line no-await-in-loop + await sql.begin(sql=>[ + sql.file(migration.filePath), + sql`INSERT INTO migrations (name) VALUES (${migration.name})`, + ]); + } + return sql; +} diff --git a/src/lib/server/database/entities/Config.entity.ts b/src/lib/server/database/entities/Config.entity.ts new file mode 100644 index 0000000000000000000000000000000000000000..33868f21751f0a830c7c96e476650606c54fcc44 --- /dev/null +++ b/src/lib/server/database/entities/Config.entity.ts @@ -0,0 +1,63 @@ +import { building } from "$app/environment"; +import type { MailTemplate } from "$lib/mail"; +import type { BaseConfig, ColorConfig, PaddingConfig } from "$lib/server/schedules"; +import { deepAssign, pick, type DeepPartial, type DeepReadonly, type Localized } from "$lib/utils"; +import { sql } from "../db"; + +export class Config { + id!: number; + currentSemester!: string; + fresherWeekStart!: string; + fresherWeekEnd!: string; + tutorRegistrationOpen!: boolean; + shirtSizes!: string[]; + trainingsStart!: string; + trainingsEnd!: string; + rallyRegistrationOpen!: boolean; + rallyDate!: string; + rallyBriefingDate!: string; + rallyBriefingTime!: string|null; + esweLink!: string; + esweRegistrationStart!: Date|null; + esweStart!: string|null; + esweEnd!: string|null; + eswePrice!: number; + defaultPath!: string; + headerLinks!: string[]; + defaultPermissions!: number; + scheduleConfig!: { + config: BaseConfig; + lightmodeConfig: ColorConfig; + darkmodeConfig: ColorConfig; + imagePadding: PaddingConfig; + pdfPadding: PaddingConfig; + }; + mailTemplates!: { + tutorRegistered: MailTemplate; + trainingInformation: MailTemplate; + }; + trainingMailReminderDays!: number; + + static parse(config: any){ + if(!config) return undefined; + const c: Config = Object.assign(new Config(), config); + c.esweRegistrationStart = config.esweRegistrationStart ? new Date(config.esweRegistrationStart) : null; + return c; + } + + static get(){ + return config; + } + static async getById(id: number){ + return Config.parse((await sql`SELECT * FROM config WHERE id=${id}`)[0]); + } + static async update(updatedConfig: DeepPartial<Config>){ + const newConfig = pick(deepAssign({...config}, updatedConfig), Object.keys(updatedConfig) as (keyof Config)[]); + config = Config.parse((await sql`UPDATE config SET ${sql(newConfig)} WHERE id=${configId} RETURNING *`)[0])!; + } + static async create(cfg: Omit<Config, "id">){ + config = Config.parse((await sql`INSERT INTO config ${sql(Object.assign(cfg, {id: configId}))} RETURNING *`)[0])!; + } +} +const configId = 1; +let config: DeepReadonly<Config> = building ? {} as DeepReadonly<Config> : (await Config.getById(configId))!; diff --git a/src/lib/server/database/entities/Discount.entity.ts b/src/lib/server/database/entities/Discount.entity.ts new file mode 100644 index 0000000000000000000000000000000000000000..af453295d1e01962daf589594e4d317372a641a0 --- /dev/null +++ b/src/lib/server/database/entities/Discount.entity.ts @@ -0,0 +1,81 @@ +import type { DateString, Localized } from "$lib/utils"; +import { sql } from "../db"; + +export class Discount { + id!: number; + title!: string; + description!: Localized; + address!: string; + location!: [number, number]; + openingHours!: { [key in 1|2|3|4|5|6|7|"holiday"|DateString]?: [[number, number], [number, number]][] }; + startDate!: string; + endDate!: string; + tags!: Tag[]; + + static parse(discount: any){ + if(!discount) return undefined; + const d: Discount = Object.assign(new Discount(), discount); + d.tags = (discount.tags as any[]) + ?.filter(tag => tag !== null) + .map((tag: any) => Tag.parse(tag)); + return d; + } + + static async getById(id: number){ + const row = (await sql`select row_to_json(discounts.*) as discount, array_agg(row_to_json(tags.*)) as tags from discounts left join discount_tags on discounts.id=discount_tags.discount left join tags on discount_tags.tag=tags.id WHERE discounts.id=${id} group by discounts.id`)[0]; + if(!row) return undefined; + const discount = Discount.parse({...row.discount, tags: row.tags})!; + return discount; + } + static async getAll(){ + const rows = await sql`select row_to_json(discounts.*) as discount, array_agg(row_to_json(tags.*)) as tags from discounts left join discount_tags on discounts.id=discount_tags.discount left join tags on discount_tags.tag=tags.id group by discounts.id`; + return rows.map(row => Discount.parse({...row.discount, tags: row.tags})!); + } + static async create(discount: Omit<Discount, "id"|"tags">){ + return (await sql<{id: number}[]>`INSERT INTO discounts ${sql(discount)} RETURNING id`)[0].id; + } + static async update(id: number, discount: Omit<Discount, "id"|"tags">, tagIds: number[]){ + await sql.begin(async sql=>{ + await sql`UPDATE discounts SET ${sql(discount)} WHERE id=${id}`; + await sql`DELETE FROM discount_tags WHERE discount=${id}`; + if(tagIds.length){ + await sql`INSERT INTO discount_tags ${sql(tagIds.map(tag=>({tag, discount: id})))}`; + } + }) + } + static async delete(id: number){ + await sql.begin(async sql=>{ + await sql`DELETE FROM discount_tags WHERE discount=${id}`; + await sql`DELETE FROM discounts WHERE id=${id}`; + }); + } +} + +export class Tag { + id!: number; + name!: Localized; + + static parse(tag: any){ + if(!tag) return undefined; + const t = Object.assign(new Tag(), tag); + return t; + } + + static async getById(id: number){ + const row = await sql`select * from tags where id=${id}`; + return Tag.parse(row); + } + static async getAll(): Promise<Tag[]>{ + const rows = await sql`select * from tags`; + return rows.map(row => Tag.parse(row)!); + } + static async create(tag: Omit<Tag, "id">){ + return (await sql<{id: number}[]>`INSERT INTO tags ${sql(tag)} RETURNING id`)[0].id; + } + static async update(id: number, tag: Omit<Tag, "id">){ + await sql`UPDATE tags SET ${sql(tag)} WHERE id=${id}`; + } + static async delete(id: number){ + await sql`DELETE FROM tags WHERE id=${id}`; + } +} diff --git a/src/lib/server/database/entities/FormerTutor.entity.ts b/src/lib/server/database/entities/FormerTutor.entity.ts new file mode 100644 index 0000000000000000000000000000000000000000..1e5159f69144df5c75fc307d3ada2463675e4b8b --- /dev/null +++ b/src/lib/server/database/entities/FormerTutor.entity.ts @@ -0,0 +1,38 @@ +import { sql } from "../db"; + +export class FormerTutor { + id!: number; + email!: string; + firstname!: string; + lastname!: string; + trainedDate!: string; + years!: number; + lastYear!: string; + notes!: string; + lists!: string[]; + + static parse(tutor: any): FormerTutor|undefined { + if(!tutor) return; + const t = Object.assign(new FormerTutor(), tutor); + return t; + } + + static async getById(id: number): Promise<FormerTutor|undefined> { + return FormerTutor.parse((await sql`SELECT * FROM former_tutors WHERE id=${id}`)[0]); + } + static async getByEmail(email: string): Promise<FormerTutor|undefined> { + return FormerTutor.parse((await sql`SELECT * FROM former_tutors WHERE email=${email}`)[0]); + } + static async getAll(): Promise<FormerTutor[]> { + return (await sql`SELECT * FROM former_tutors`).map(t=>FormerTutor.parse(t)!); + } + static async create(tutor: Omit<FormerTutor, "id">): Promise<number> { + return (await sql`INSERT INTO former_tutors ${sql(tutor)} RETURNING id`)[0].id; + } + static async update(id: number, tutor: Partial<Omit<FormerTutor, "id">>){ + await sql`UPDATE former_tutors SET ${sql(tutor)} WHERE id=${id}`; + } + static async createOrUpdate(tutor: Omit<FormerTutor, "id"|"years">): Promise<number> { + return (await sql`INSERT INTO former_tutors ${sql({...tutor, years: 1})} ON CONFLICT (email) DO UPDATE SET last_year=${tutor.lastYear}, years=years+1 RETURNING id`)[0].id; + } +} diff --git a/src/lib/server/database/entities/RallyPoints.entity.ts b/src/lib/server/database/entities/RallyPoints.entity.ts new file mode 100644 index 0000000000000000000000000000000000000000..08649636aa11bb0e39c68ef1b41c44d304b6638a --- /dev/null +++ b/src/lib/server/database/entities/RallyPoints.entity.ts @@ -0,0 +1,33 @@ +import { sql } from "../db"; +import { RallyStation } from "./RallyStation.entity"; + +export class RallyPoints { + tutorial!: number; + rallyStation!: RallyStation; + points!: number; + bribe!: number; + + static parse(rallyPoints: any): RallyPoints|undefined { + if(!rallyPoints) return undefined; + return Object.assign(new RallyPoints(), rallyPoints); + } + + static async getByTutorial(tutorial: number): Promise<RallyPoints[]> { + const rows = await sql`SELECT row_to_json(rally_points.*) AS rally_points, row_to_json(rally_stations.*) AS rally_station FROM rally_points LEFT JOIN rally_stations ON rally_points.rally_station=rally_stations.id WHERE tutorial=${tutorial}`; + return rows.map(row => RallyPoints.parse(Object.assign(row.rallyPoints, { rallyStation: RallyStation.parse(row.rallyStation) }))!); + } + static async getByRallyStation(rallyStation: string): Promise<RallyPoints[]> { + const rows = await sql`SELECT row_to_json(rally_points.*) AS rally_points, row_to_json(rally_stations.*) AS rally_station FROM rally_points LEFT JOIN rally_stations ON rally_points.rally_station=rally_stations.id WHERE rally_station=${rallyStation}`; + return rows.map(row => RallyPoints.parse(Object.assign(row.rallyPoints, { rallyStation: RallyStation.parse(row.rallyStation) }))!); + } + static async getAll(): Promise<RallyPoints[]> { + const rows = await sql`SELECT row_to_json(rally_points.*) AS rally_points, row_to_json(rally_stations.*) AS rally_station FROM rally_points LEFT JOIN rally_stations ON rally_points.rally_station=rally_stations.id`; + return rows.map(row => RallyPoints.parse(Object.assign(row.rallyPoints, { rallyStation: RallyStation.parse(row.rallyStation) }))!); + } + static async create(rallyPoints: Omit<RallyPoints, "rallyStation"> & {rallyStation: string}): Promise<void> { + await sql`INSERT INTO rally_points ${sql(rallyPoints)}`; + } + static async update(rallyPoints: Omit<RallyPoints, "rallyStation"> & {rallyStation: string}): Promise<void> { + await sql`UPDATE rally_points SET ${sql(rallyPoints, ["points", "bribe"])} WHERE tutorial=${rallyPoints.tutorial} AND rally_station=${rallyPoints.rallyStation}`; + } +} diff --git a/src/lib/server/database/entities/RallyStation.entity.ts b/src/lib/server/database/entities/RallyStation.entity.ts new file mode 100644 index 0000000000000000000000000000000000000000..13d473bfc801d20d4d676f33e946fa144915e757 --- /dev/null +++ b/src/lib/server/database/entities/RallyStation.entity.ts @@ -0,0 +1,28 @@ +import { sql } from "../db"; + +export class RallyStation { + id!: string; + name!: string; + description!: string; + + static parse(rallyStation: any): RallyStation|undefined { + if(!rallyStation) return undefined; + return Object.assign(new RallyStation(), rallyStation); + } + + static async getById(id: string): Promise<RallyStation|undefined> { + return RallyStation.parse((await sql`SELECT * FROM rally_stations WHERE id=${id}`)[0]); + } + static async getAll(): Promise<RallyStation[]> { + return (await sql`SELECT * FROM rally_stations`).map(row => RallyStation.parse(row)!); + } + static async update(rallyStation: RallyStation): Promise<void> { + await sql`UPDATE rally_stations SET ${sql(rallyStation)} WHERE id=${rallyStation.id}`; + } + static async create(rallyStation: Omit<RallyStation, "id">): Promise<string> { + return (await sql`INSERT INTO rally_stations ${sql(rallyStation)} RETURNING id`)[0].id; + } + static async delete(id: string): Promise<void> { + await sql`DELETE FROM rally_stations WHERE id=${id}`; + } +} diff --git a/src/lib/server/database/entities/RallyStationSupervisor.entity.ts b/src/lib/server/database/entities/RallyStationSupervisor.entity.ts new file mode 100644 index 0000000000000000000000000000000000000000..0f24f650a208ef398d78ddee4cb2fed34620b50c --- /dev/null +++ b/src/lib/server/database/entities/RallyStationSupervisor.entity.ts @@ -0,0 +1,34 @@ +import { sql } from "../db"; + +export class RallyStationSupervisor { + id!: number; + firstname!: string; + lastname!: string; + nickname?: string; + birthday!: string; + email!: string; + phone!: string; + morning!: boolean; + afternoon!: boolean; + coSupervisorWish!: string; + notes!: string; + + static parse(supervisor: any){ + if(!supervisor) return undefined; + const s: RallyStationSupervisor = Object.assign(new RallyStationSupervisor(), supervisor); + return s; + } + + static async getById(id: number){ + return RallyStationSupervisor.parse((await sql`SELECT * FROM rally_station_supervisors WHERE id=${id}`)[0]); + } + static async getAll(){ + return (await sql`SELECT * FROM rally_station_supervisors`).map(RallyStationSupervisor.parse); + } + static async create(supervisor: Omit<RallyStationSupervisor, "id">): Promise<number> { + return (await sql`INSERT INTO rally_station_supervisors ${sql(supervisor)} RETURNING id`)[0].id; + } + static async update(id: number, supervisor: Partial<Omit<RallyStationSupervisor, "id">>){ + await sql`UPDATE rally_station_supervisors SET ${sql(supervisor)} WHERE id=${id}`; + } +} diff --git a/src/lib/server/database/entities/Schedule.entity.ts b/src/lib/server/database/entities/Schedule.entity.ts new file mode 100644 index 0000000000000000000000000000000000000000..0fc665231d3b57a008de81887a321b60a2e3ab97 --- /dev/null +++ b/src/lib/server/database/entities/Schedule.entity.ts @@ -0,0 +1,104 @@ +import { renderSchedule } from "$lib/server/schedules"; +import type { Localized, PartialExcept } from "$lib/utils"; +import { sql } from "../db"; +import { StudyProgram } from "./StudyProgram.entity"; +import fs from "node:fs/promises"; + +export class Schedule { + id!: number; + title!: Localized; + updated!: string; + studyProgram!: StudyProgram; + timeslots!: Timeslot[]; + + static parse(schedule: any){ + if(!schedule) return undefined; + const s: Schedule = Object.assign(new Schedule(), schedule); + s.timeslots = (schedule.timeslots as any[]) + ?.filter(timeslot => timeslot !== null) + .map(timeslot => Timeslot.parse(timeslot)!) + .sort((a, b) => a.start === b.start ? b.end - a.end : a.start - b.start); + s.studyProgram = StudyProgram.parse(schedule.studyProgram)!; + s.updated = schedule.updated && new Date(schedule.updated).toISOString().split("T")[0]; + return s; + } + + static async getById(id: number){ + const row = (await sql`select row_to_json(schedules.*) as schedule, array_agg(row_to_json(schedule_entries.*)) as timeslots, (array_agg(row_to_json(study_programs.*)))[1] as study_program from schedules left join schedule_entries on schedules.id=schedule_entries.schedule left join study_programs on schedules.study_program=study_programs.id WHERE schedules.id=${id} group by schedules.id`)[0]; + if(!row) return undefined; + const schedule = Schedule.parse({...row.schedule, timeslots: row.timeslots, studyProgram: row.studyProgram})!; + return schedule; + } + static async getAll(): Promise<Schedule[]>{ + const rows = await sql`select row_to_json(schedules.*) as schedule, array_agg(row_to_json(schedule_entries.*)) as timeslots, (array_agg(row_to_json(study_programs.*)))[1] as study_program from schedules left join schedule_entries on schedules.id=schedule_entries.schedule left join study_programs on schedules.study_program=study_programs.id group by schedules.id`; + const schedules = rows.map(row => Schedule.parse({...row.schedule, timeslots: row.timeslots, studyProgram: row.studyProgram})!); + return schedules; + } + static async getByStudyProgram(studyProgram: number){ + const row = (await sql`select row_to_json(schedules.*) as schedule, array_agg(row_to_json(schedule_entries.*)) as timeslots, (array_agg(row_to_json(study_programs.*)))[1] as study_program from schedules left join schedule_entries on schedules.id=schedule_entries.schedule left join study_programs on schedules.study_program=study_programs.id WHERE schedules.study_program=${studyProgram} group by schedules.id`)[0]; + if(!row) return undefined; + const schedule = Schedule.parse({...row.schedule, timeslots: row.timeslots, studyProgram: row.studyProgram})!; + return schedule; + } + static async addTimeslot(timeslot: Omit<Timeslot, "id">){ + const [t] = await sql.begin(sql=>[ + sql`INSERT INTO schedule_entries ${sql(timeslot)} RETURNING *`, + sql`UPDATE schedules SET updated=NOW() WHERE id=${timeslot.schedule}`, + ]); + await renderSchedule(timeslot.schedule); + return Timeslot.parse(t)!; + } + static async updateTimeslot(timeslot: PartialExcept<Timeslot, "id"|"schedule">){ + const [t] = await sql.begin(sql=>[ + sql`UPDATE schedule_entries SET ${sql(timeslot)} WHERE id=${timeslot.id} RETURNING *`, + sql`UPDATE schedules SET updated=NOW() WHERE id=${timeslot.schedule}`, + ]); + await renderSchedule(timeslot.schedule); + return Timeslot.parse(t)!; + } + static async deleteTimeslot(scheduleId: number, timeslotId: number){ + await sql.begin(sql=>[ + sql`DELETE FROM schedule_entries WHERE id=${timeslotId}`, + sql`UPDATE schedules SET updated=NOW() WHERE id=${scheduleId}`, + ]); + await renderSchedule(scheduleId); + } + static async create(title: Localized, studyProgram: number){ + const id = (await sql`INSERT INTO schedules ${sql({title, studyProgram})} RETURNING id`)[0].id; + await renderSchedule(id); + } + static async update(id: number, title: Localized){ + await sql`UPDATE schedules SET ${sql({title})}, updated=NOW() WHERE id=${id}`; + await renderSchedule(id); + } + static async delete(id: number){ + await sql`DELETE FROM schedules WHERE id=${id}`; + await fs.rm(`static/stundenplaene/${id}`, {recursive: true}); + } +} + +export class Timeslot { + id!: number; + schedule!: number; + date!: string; + title!: Localized; + location?: Localized; + startUncertainty!: number; + start!: number; + end!: number; + endUncertainty!: number; + + static parse(timeslot: any){ + if(!timeslot) return undefined; + if(typeof timeslot.start_uncertainty === "number"){ + timeslot.startUncertainty = timeslot.start_uncertainty; + delete timeslot.start_uncertainty; + } + if(typeof timeslot.end_uncertainty === "number"){ + timeslot.endUncertainty = timeslot.end_uncertainty; + delete timeslot.end_uncertainty; + } + const t: Timeslot = Object.assign(new Timeslot(), timeslot); + return t; + } +} diff --git a/src/lib/server/database/entities/StudyProgram.entity.ts b/src/lib/server/database/entities/StudyProgram.entity.ts new file mode 100644 index 0000000000000000000000000000000000000000..ea0051fe52a356175d36ce6971a2561998dd78bc --- /dev/null +++ b/src/lib/server/database/entities/StudyProgram.entity.ts @@ -0,0 +1,36 @@ +import type { Localized } from "$lib/utils"; +import { sql } from "../db"; + +export class StudyProgram { + id!: number; + name!: Localized; + tutorsWanted!: number; + tutorialNames!: string[]; + + static parse(program: any){ + if(!program) return undefined; + const p: StudyProgram = Object.assign(new StudyProgram(), program); + return p; + } + + static async getById(id: number): Promise<StudyProgram|undefined> { + return StudyProgram.parse((await sql`SELECT * FROM study_programs WHERE id=${id}`)[0]); + } + static async getAll(){ + return (await sql`SELECT * FROM study_programs ORDER BY id ASC`).map(program=>StudyProgram.parse(program)!); + } + static async update(updatedProgram: StudyProgram){ + await sql`UPDATE study_programs SET ${sql(updatedProgram)} WHERE id=${updatedProgram.id}`; + } + static async create(program: Omit<StudyProgram, "id">){ + await sql`INSERT INTO study_programs ${sql(program)}`; + } + static async delete(id: number){ + // TODO handle foreign key constraints + await sql`DELETE FROM study_programs WHERE id=${id}`; + } + static async getMissingTutors(): Promise<{[id: number]: number|null}>{ + const rows = await sql`SELECT study_programs.id, study_programs.tutors_wanted, COUNT(tutors.id) AS tutors FROM study_programs LEFT JOIN tutors ON study_programs.id=tutors.study_program GROUP BY study_programs.id`; + return rows.reduce((acc, row)=>({...acc, [row.id]: row.tutorsWanted ? row.tutorsWanted - row.tutors : null}), {}); + } +} diff --git a/src/lib/server/database/entities/Tutor.entity.ts b/src/lib/server/database/entities/Tutor.entity.ts new file mode 100644 index 0000000000000000000000000000000000000000..6723be0edaae326cdfd1c6a41a8f1246babdb5ad --- /dev/null +++ b/src/lib/server/database/entities/Tutor.entity.ts @@ -0,0 +1,59 @@ +import type { Degree } from "$lib/degrees"; +import type { Gender } from "$lib/genders"; +import type { PartialExcept } from "$lib/utils"; +import { sql } from "../db"; +import { FormerTutor } from "./FormerTutor.entity"; +import { StudyProgram } from "./StudyProgram.entity"; +import { Tutorial } from "./Tutorial.entity"; +import { TutorTraining } from "./TutorTraining.entity"; + +export class Tutor { + id!: number; + firstname!: string; + lastname!: string; + nickname?: string; + birthday!: string; + email!: string; + phone!: string; + address!: string; + gender!: keyof typeof Gender; + shirtSize!: string; + degree!: keyof typeof Degree; + dietaryRestriction?: string; + studyProgram!: StudyProgram; + training?: TutorTraining|null; + sentTrainingMail!: boolean; + trained!: boolean; + coTutorWish!: string; + mentor!: boolean; + tutorial?: Tutorial; + notes!: string; + former?: FormerTutor; + + static parse(tutor: any){ + if(!tutor) return undefined; + const t: Tutor = Object.assign(new Tutor(), tutor); + if(tutor.training) t.training = TutorTraining.parse(tutor.training); + if(tutor.studyProgram) t.studyProgram = StudyProgram.parse(tutor.studyProgram)!; + if(tutor.tutorial) t.tutorial = Tutorial.parse(tutor.tutorial); + if(tutor.former) t.former = FormerTutor.parse(tutor.former); + return t; + } + + // TODO include tutorial in getById and getAll + static async getById(id: number){ + const row = (await sql`SELECT row_to_json(tutors.*) AS tutor, row_to_json(tutor_trainings.*) AS training, row_to_json(study_programs.*) AS study_program, row_to_json(former_tutors.*) AS former FROM tutors LEFT JOIN tutor_trainings ON tutors.training=tutor_trainings.id LEFT JOIN study_programs ON tutors.study_program=study_programs.id LEFT JOIN former_tutors ON tutors.email=former_tutors.email WHERE tutors.id=${id}`)[0]; + if(!row) return undefined; + return Tutor.parse(Object.assign(row.tutor, { training: row.training, studyProgram: row.studyProgram })); + } + static async getAll(){ + const rows = await sql`SELECT row_to_json(tutors.*) AS tutor, row_to_json(tutor_trainings.*) AS training, row_to_json(study_programs.*) AS study_program, row_to_json(former_tutors.*) AS former FROM tutors LEFT JOIN tutor_trainings ON tutors.training=tutor_trainings.id LEFT JOIN study_programs ON tutors.study_program=study_programs.id LEFT JOIN former_tutors ON tutors.email=former_tutors.email`; + return rows.map(row => Tutor.parse(Object.assign(row.tutor, { training: row.training, studyProgram: row.studyProgram }))!); + } + static async create(tutor: Omit<Tutor, "id"|"studyProgram"|"training"|"tutorial"|"former">&{studyProgram: number, training?: number|null}){ + return (await sql`INSERT INTO tutors ${sql(tutor)} RETURNING id`)[0].id as number; + } + static async update(tutor: PartialExcept<Omit<Tutor, "studyProgram"|"training"|"former"> & {studyProgram: number, training?: number}, "id">){ + return sql`UPDATE tutors SET ${sql({...tutor, tutorial: tutor.tutorial?.id})} WHERE id=${tutor.id}`; + } +} diff --git a/src/lib/server/database/entities/TutorTraining.entity.ts b/src/lib/server/database/entities/TutorTraining.entity.ts new file mode 100644 index 0000000000000000000000000000000000000000..45f05d805b0330a92c162772f392d976c731f42e --- /dev/null +++ b/src/lib/server/database/entities/TutorTraining.entity.ts @@ -0,0 +1,34 @@ +import { sql } from "../db"; +import { Tutor } from "./Tutor.entity"; + +export class TutorTraining { + id!: number; + date!: string; + location!: string; + maxParticipants!: number; + internal!: boolean; + notes!: string; + participants!: Tutor[]; // important: NOT to be saved to the database! + + static parse(training: any): TutorTraining|undefined{ + if(!training) return undefined; + const t: TutorTraining = Object.assign(new TutorTraining(), training); + t.participants = ((training.participants || []) as any[]) + .filter(tutor=>tutor) + .map(tutor=>Object.assign(Tutor.parse({...tutor.tutor, studyProgram: tutor.studyProgram})!, {training: t})); + return t; + } + + static async getById(id: number): Promise<TutorTraining|undefined> { + return TutorTraining.parse((await sql`SELECT tutor_trainings.*, array_agg(json_build_object('tutor', row_to_json(tutors.*), 'studyProgram', row_to_json(study_programs.*))) AS participants FROM tutor_trainings LEFT JOIN tutors ON tutor_trainings.id=tutors.training JOIN study_programs ON tutors.study_program=study_programs.id WHERE tutor_trainings.id=${id} GROUP BY tutor_trainings.id`)[0]); + } + static async getAll(): Promise<TutorTraining[]> { + return (await sql`SELECT tutor_trainings.*, array_agg(json_build_object('tutor', row_to_json(tutors.*), 'studyProgram', row_to_json(study_programs.*))) AS participants FROM tutor_trainings LEFT JOIN tutors ON tutor_trainings.id=tutors.training JOIN study_programs ON tutors.study_program=study_programs.id GROUP BY tutor_trainings.id`).map(row=>TutorTraining.parse(row)!); + } + static async create(training: Omit<TutorTraining, "id"|"participants">): Promise<number> { + return (await sql`INSERT INTO tutor_trainings ${sql(training)} RETURNING id`)[0].id; + } + static async update(id: number, training: Partial<Omit<TutorTraining, "id"|"participants">>){ + await sql`UPDATE tutor_trainings SET ${sql(training)} WHERE id=${id}`; + } +} diff --git a/src/lib/server/database/entities/Tutorial.entity.ts b/src/lib/server/database/entities/Tutorial.entity.ts new file mode 100644 index 0000000000000000000000000000000000000000..2e9d7ef1d0bde158bef0cf4f492540b5869d51eb --- /dev/null +++ b/src/lib/server/database/entities/Tutorial.entity.ts @@ -0,0 +1,36 @@ +import { sql } from "../db"; +import { StudyProgram } from "./StudyProgram.entity"; +import { Tutor } from "./Tutor.entity"; + +export class Tutorial { + id!: number; + name!: string; + studyProgram!: StudyProgram; + rallyQuestionnairePoints?: number; + tutors!: Tutor[]; + + static parse(tutorial: any): Tutorial|undefined { + if(!tutorial) return undefined; + const t: Tutorial = Object.assign(new Tutorial(), tutorial); + if(tutorial.studyProgram) t.studyProgram = StudyProgram.parse(tutorial.studyProgram)!; + if(tutorial.tutors) t.tutors = tutorial.tutors.map((t: any) => Tutor.parse(t)!); + return t; + } + + static async getById(id: number): Promise<Tutorial|undefined> { + const row = (await sql`SELECT row_to_json(tutorials.*) AS tutorial, row_to_json(study_programs.*) AS study_program, json_agg(row_to_json(tutors.*)) AS tutors FROM tutorials LEFT JOIN study_programs ON tutorials.study_program=study_programs.id LEFT JOIN tutors ON tutorials.id=tutors.tutorial WHERE tutorials.id=${id} GROUP BY tutorials.id, study_programs.id`)[0]; + if(!row) return; + return Tutorial.parse(Object.assign(row.tutorial, { studyProgram: row.studyProgram, tutors: row.tutors })); + } + static async getAll(): Promise<Tutorial[]> { + const rows = await sql`SELECT row_to_json(tutorials.*) AS tutorial, row_to_json(study_programs.*) AS study_program, json_agg(row_to_json(tutors.*)) AS tutors FROM tutorials LEFT JOIN study_programs ON tutorials.study_program=study_programs.id LEFT JOIN tutors ON tutorials.id=tutors.tutorial GROUP BY tutorials.id, study_programs.id`; + return rows.map(row => Tutorial.parse(Object.assign(row.tutorial, { studyProgram: row.studyProgram, tutors: row.tutors }))!); + } + static async create(tutorial: Omit<Tutorial, "id"|"studyProgram"|"tutors"> & {studyProgram: number}, tutors: number[] = []): Promise<number> { + return sql.begin(async sql=>{ + const id = (await sql<{id: number}[]>`INSERT INTO tutorials ${sql(tutorial)} RETURNING id`)[0].id; + await sql`UPDATE tutors SET tutorial=${id} WHERE id IN ${tutors}`; + return id; + }) + } +} diff --git a/src/lib/server/database/entities/User.entity.ts b/src/lib/server/database/entities/User.entity.ts new file mode 100644 index 0000000000000000000000000000000000000000..1f36ef6fd4ecbd7797dbaac953aeab330c464b87 --- /dev/null +++ b/src/lib/server/database/entities/User.entity.ts @@ -0,0 +1,40 @@ +import { PermissionSet } from "$lib/perms"; +import { sql } from "../db"; +import { Config } from "./Config.entity"; + +export class User { + id!: string; + username!: string; + permissions!: PermissionSet; + + static parse(user: any){ + if(!user) return undefined; + const u: User = Object.assign(new User(), user); + u.permissions = new PermissionSet(user.permissions); // stored as number + return u; + } + + static async getById(id: string){ + return User.parse((await sql`SELECT * FROM users WHERE id=${id}`)[0]); + } + static async getOrCreate(id: string, username: string){ + let permissions = Config.get()?.defaultPermissions; + // no config exists yet, so we can't know the default permissions -> give all permissions + if(permissions === undefined) permissions = PermissionSet.all().raw(); + // username probably never changes but update is required for RETURNING to return anything, because it only returns changes + return User.parse((await sql`INSERT INTO users ${sql({id, username, permissions})} ON CONFLICT (id) DO UPDATE SET username=${username} RETURNING *`)[0])!; + } + static async create(id: string, username: string, permissions: PermissionSet){ + return User.parse((await sql`INSERT INTO users ${sql({id, username, permissions: permissions.raw()})} RETURNING *`)[0]); + } + static async update(id: string, permissions: PermissionSet|number){ + const perms = typeof permissions === "number" ? permissions : permissions.raw(); + return sql`UPDATE users SET permissions=${perms} WHERE id=${id}`; + } + async update(permissions: PermissionSet|number){ + return User.update(this.id, permissions); + } + static async getAll(){ + return (await sql`SELECT * FROM users`).map(u=>User.parse(u)!); + } +} diff --git a/src/lib/server/database/migrations/0_init.sql b/src/lib/server/database/migrations/0_init.sql new file mode 100644 index 0000000000000000000000000000000000000000..fbaee01f3d4fc16b58c112144f4a16e614ba9000 --- /dev/null +++ b/src/lib/server/database/migrations/0_init.sql @@ -0,0 +1,164 @@ +CREATE TABLE IF NOT EXISTS config ( + "id" SERIAL PRIMARY KEY, + "current_semester" TEXT NOT NULL, + "fresher_week_start" TEXT NOT NULL, + "fresher_week_end" TEXT NOT NULL, + "tutor_registration_open" BOOLEAN NOT NULL DEFAULT FALSE, + "shirt_sizes" TEXT[] NOT NULL DEFAULT '{}', + "trainings_start" TEXT NOT NULL, + "trainings_end" TEXT NOT NULL, + "rally_registration_open" BOOLEAN NOT NULL DEFAULT FALSE, + "rally_date" TEXT NOT NULL, + "rally_briefing_date" TEXT NOT NULL, + "rally_briefing_time" TIME DEFAULT NULL, + "eswe_link" TEXT NOT NULL DEFAULT '', + "eswe_registration_start" TIMESTAMP, + "eswe_start" TEXT, + "eswe_end" TEXT, + "eswe_price" INT NOT NULL DEFAULT 0, + "default_path" TEXT NOT NULL DEFAULT '/information', + "header_links" TEXT[] NOT NULL DEFAULT '{}', + "default_permissions" INT NOT NULL DEFAULT 0, + "schedule_config" JSONB NOT NULL DEFAULT '{}', + "mail_templates" JSONB NOT NULL DEFAULT '{}', + "training_mail_reminder_days" INT NOT NULL DEFAULT 7, +); + +CREATE TABLE IF NOT EXISTS study_programs ( + "id" SERIAL PRIMARY KEY, + "name" JSONB NOT NULL, + "tutors_wanted" INT NOT NULL DEFAULT 0, + "tutorial_names" TEXT[] NOT NULL DEFAULT '{}' +); + +CREATE TABLE IF NOT EXISTS users ( + "id" TEXT PRIMARY KEY, + "username" TEXT NOT NULL, + "permissions" INT NOT NULL DEFAULT 0 +); + +CREATE TABLE IF NOT EXISTS tutorials ( + "id" SERIAL PRIMARY KEY, + "name" TEXT NOT NULL, + "study_program" INT NOT NULL REFERENCES study_programs(id), + "rally_questionnaire_points" INT +); + +CREATE TABLE IF NOT EXISTS tutor_trainings ( + "id" SERIAL PRIMARY KEY, + "date" DATE NOT NULL, + "location" TEXT NOT NULL, + "max_participants" INT NOT NULL DEFAULT 0, + "internal" BOOLEAN NOT NULL DEFAULT FALSE, + "notes" TEXT NOT NULL DEFAULT '' +); + +CREATE TABLE IF NOT EXISTS tutors ( + "id" SERIAL PRIMARY KEY, + "firstname" TEXT NOT NULL, + "lastname" TEXT NOT NULL, + "nickname" TEXT, + "birthday" DATE NOT NULL, + "email" TEXT NOT NULL, + "phone" TEXT NOT NULL, + "address" TEXT NOT NULL, + "degree" TEXT NOT NULL, + "gender" TEXT NOT NULL, + "shirt_size" TEXT NOT NULL, + "dietary_restriction" TEXT NOT NULL DEFAULT '', + "study_program" INT NOT NULL REFERENCES study_programs(id), + "training" INT DEFAULT NULL REFERENCES tutor_trainings(id), + "sent_training_mail" BOOLEAN NOT NULL DEFAULT FALSE, + "trained" BOOLEAN NOT NULL DEFAULT FALSE, + "co_tutor_wish" TEXT NOT NULL DEFAULT '', + "mentor" BOOLEAN NOT NULL DEFAULT FALSE, + "tutorial" INT DEFAULT NULL REFERENCES tutorials(id), + "notes" TEXT NOT NULL DEFAULT '' +); + +CREATE TABLE IF NOT EXISTS former_tutors ( + "id" SERIAL PRIMARY KEY, + "email" TEXT NOT NULL UNIQUE, + "firstname" TEXT NOT NULL, + "lastname" TEXT NOT NULL, + "trained_date" TEXT NOT NULL DEFAULT '', + "years" INT NOT NULL, + "last_year" TEXT NOT NULL, + "notes" TEXT, + "lists" TEXT[] NOT NULL DEFAULT '{}' +); + +CREATE TABLE IF NOT EXISTS rally_station_supervisors ( + "id" SERIAL PRIMARY KEY, + "firstname" TEXT NOT NULL, + "lastname" TEXT NOT NULL, + "nickname" TEXT, + "birthday" DATE NOT NULL, + "email" TEXT NOT NULL, + "phone" TEXT NOT NULL, + "morning" BOOLEAN NOT NULL DEFAULT FALSE, + "afternoon" BOOLEAN NOT NULL DEFAULT FALSE, + "co_supervisor_wish" TEXT NOT NULL DEFAULT '', + "notes" TEXT NOT NULL DEFAULT '' +); + +CREATE TABLE IF NOT EXISTS rally_stations ( + "id" UUID PRIMARY KEY DEFAULT gen_random_uuid(), + "name" TEXT NOT NULL, + "description" TEXT NOT NULL +); + +CREATE TABLE IF NOT EXISTS rally_station_assignments ( + "station" UUID NOT NULL REFERENCES rally_stations(id), + "supervisor" INT NOT NULL REFERENCES rally_station_supervisors(id), + PRIMARY KEY (station, supervisor) +); + +CREATE TABLE IF NOT EXISTS rally_points ( + "rally_station" UUID NOT NULL REFERENCES rally_stations(id), + "tutorial" INT NOT NULL REFERENCES tutorials(id), + "points" INT NOT NULL, + "bribe" INT NOT NULL, + PRIMARY KEY (rally_station, tutorial) +); + +CREATE TABLE IF NOT EXISTS discounts ( + "id" SERIAL PRIMARY KEY, + "title" TEXT NOT NULL, + "description" JSONB NOT NULL, + "address" TEXT NOT NULL, + "location" FLOAT[] NOT NULL, + "opening_hours" JSONB NOT NULL, + "start_date" DATE NOT NULL, + "end_date" DATE NOT NULL +); + +CREATE TABLE IF NOT EXISTS tags ( + "id" SERIAL PRIMARY KEY, + "name" JSONB NOT NULL +); + +CREATE TABLE IF NOT EXISTS discount_tags ( + "discount" INT NOT NULL REFERENCES discounts(id), + "tag" INT NOT NULL REFERENCES tags(id), + PRIMARY KEY (discount, tag) +); + +CREATE TABLE IF NOT EXISTS schedules ( + "id" SERIAL PRIMARY KEY, + "title" JSONB NOT NULL, + "study_program" INT NOT NULL UNIQUE REFERENCES study_programs(id), + "updated" TIMESTAMP NOT NULL DEFAULT NOW() +); + +CREATE TABLE IF NOT EXISTS schedule_entries ( + "id" SERIAL PRIMARY KEY, + "schedule" INT NOT NULL REFERENCES schedules(id), + "title" JSONB NOT NULL, + "location" JSONB, + "start_uncertainty" INT NOT NULL DEFAULT 0, + "start" INT NOT NULL, + "end" INT NOT NULL, + "end_uncertainty" INT NOT NULL DEFAULT 0, + "date" DATE NOT NULL +); diff --git a/src/lib/server/db.js b/src/lib/server/db.js deleted file mode 100644 index 1f0d70cca9b88016c5a85480d06d0954ab7f901f..0000000000000000000000000000000000000000 --- a/src/lib/server/db.js +++ /dev/null @@ -1,108 +0,0 @@ -/* eslint-disable require-await */ -import { config } from "./config"; -import discounts, { Tags, holidays } from "./discounts"; - -/** - * @typedef {Object} Tutor - * @property {string} firstname - * @property {string} lastname - * @property {string} nickname - * @property {string} birthday - * @property {string} email - * @property {string} phone - * @property {string} shirtSize - * @property {string} address - * @property {string} gender - * @property {string} studyProgram - * @property {string} degree - * @property {string} training - * @property {boolean} trained - * @property {boolean} mentor - * @property {string} coTutorWish - * @property {string} comment - * @property {number|undefined} tutorialId - * @property {Tutorial|undefined} tutorial - */ -/** - * @typedef {Object} HasId - * @property {number} id - */ -/** - * @typedef {Tutor & HasId} TutorWithId - */ -/** - * @typedef {Object} Tutorial - * @property {number} id - * @property {string} name - * @property {string} studyProgram - */ - -export async function getTutorTrainings(){ - return Promise.all(config.tutorTrainings.map(async t=>Object.assign({isFull: (await getTutorsForTraining(t.date)).length >= t.maxParticipants}, t))); -} - -/** @returns {Promise<TutorWithId[]>} */ -export async function getTutors(){ - return Promise.all(config.tutors.map(async tutor => Object.assign({}, tutor, {tutorial: await getTutorial(tutor.tutorialId)}))); -} - -/** @param {string} trainingDate */ -export async function getTutorsForTraining(trainingDate){ - return config.tutors.filter(t=>t.training===trainingDate).map(tutor => Object.assign({}, tutor)); -} - -/** @param {number} id @returns {Promise<TutorWithId|undefined>} */ -export async function getTutor(id){ - /** @type TutorWithId|undefined */ - let tutor = config.tutors.find(tutor => tutor.id === id); - if(tutor) tutor = Object.assign({}, tutor); - if(tutor && tutor.tutorialId){ - tutor.tutorial = await getTutorial(tutor.tutorialId); - } - return tutor; -} - -/** @returns {Promise<Tutorial[]>} */ -export async function getTutorials(){ - return config.tutorials.map(tutorial => Object.assign({}, tutorial)); -} - -/** @param {number} id @returns {Promise<Tutorial|undefined>} */ -export async function getTutorial(id){ - const tutorial = config.tutorials.find(tutorial => tutorial.id === id); - if(tutorial) return Object.assign({}, tutorial); - return tutorial; -} - -export async function getRallyeStationSupervisors(){ - return config.rallyeStationSupervisors.map(supervisor => Object.assign({}, supervisor)); -} - -/** @param {Tutor} tutor @returns {Promise<TutorWithId>} */ -export async function createTutor(tutor){ - // TODO - return Object.assign({}, tutor, {id: 0}); -} - -/** @param {TutorWithId} tutor @returns {Promise<TutorWithId>} */ -export async function updateTutor(tutor){ - // TODO - return Object.assign({}, tutor); -} - -/** @returns {Promise<import("./discounts").Location[]>} */ -export async function getDiscounts(){ - return discounts.map(discount => Object.assign({}, discount)); -} - -export async function getDiscountTags(){ - return Tags; -} - -export async function getHolidays(){ - return [...holidays]; -} - -export async function getStudyProgramsWithWaitlist(){ - return [...config.waitlist]; -} diff --git a/src/lib/server/discounts.ts b/src/lib/server/discounts.ts deleted file mode 100644 index 61dda12d46fb86b9dca9adfd9d2efc8e535ef137..0000000000000000000000000000000000000000 --- a/src/lib/server/discounts.ts +++ /dev/null @@ -1,1409 +0,0 @@ -/* eslint-disable @typescript-eslint/comma-spacing */ -import type { Locales } from "$lib/i18n/i18n-types"; - -export type Description = {[key in Locales]: string}; -export type Location = { - name: string, - description: Description, - tags: Tag[], - address: string, - location: [number, number], - open: {[key: string]: [[number, number], [number, number]][]}, - start: string, - end: string -}; - -export const holidays = [ - "2023-10-03", -]; - -export type Tag = {[locale in Locales]: string}; -export const Tags = { - ASIAN: { - de: "astiatisch", - en: "asian", - }, - BOWLS: { - de: "Bowls", - en: "Bowls", - }, - BUBBLE_TEA: { - de: "Bubble Tea", - en: "Bubble Tea", - }, - BURGER: { - de: "Burger", - en: "Burger", - }, - CAFE: { - de: "Café", - en: "Café", - }, - CHINESE: { - de: "chinesisch", - en: "chinese", - }, - INDIAN: { - de: "indisch", - en: "indian", - }, - ITALIAN: { - de: "italienisch", - en: "italian", - }, - KEBAB: { - de: "Kebab", - en: "Kebab", - }, - LIBANESE: { - de: "libanesisch", - en: "libanese", - }, - MEDITERRANIAN: { - de: "mediterran", - en: "mediterranian", - }, - MEXICAN: { - de: "mexikanisch", - en: "mexican", - }, - ORIENTAL: { - de: "orientalisch", - en: "oriental", - }, - PASTA: { - de: "Pasta", - en: "Pasta", - }, - PIZZA: { - de: "Pizza", - en: "Pizza", - }, - PUB: { - de: "Kneipe", - en: "Pub", - }, - RESTAURANT: { - de: "Restaurant", - en: "Restaurant", - }, - TURKISH: { - de: "türkisch", - en: "turkish", - }, -} as const satisfies {[key: string]: Tag}; - -export const discounts: Location[] = [ - { - name: "Café Beste Freunde", - description: { - de: "20% auf Eisspeisen und Waffeln\n20% auf heiße Getränke und Kaffee", - en: "20% off ice creams and waffles\n20% off hot drinks and coffee", - }, - tags: [Tags.CAFE], - address: "Adalbertstraße 100", - location: [50.77478, 6.09327], - open: { - 1: [[[10,0],[20,0]]], - 2: [[[10,0],[20,0]]], - 3: [[[10,0],[20,0]]], - 4: [[[10,0],[20,0]]], - 5: [[[10,0],[20,0]]], - 6: [[[10,0],[20,0]]], - 7: [], - }, - start: "2023-10-02", - end: "2023-10-06", - }, - { - name: "MO's Restaurant", - description: { - de: "10% auf alles", - en: "10% off everything", - }, - tags: [Tags.BURGER], - address: "Adalbertstraße 100", - location: [50.7749885, 6.0919482], - open: { - 1: [[[11,0],[20,0]]], - 2: [[[11,0],[20,0]]], - 3: [[[11,0],[20,0]]], - 4: [[[11,0],[20,0]]], - 5: [[[11,0],[20,0]]], - 6: [[[11,0],[20,0]]], - 7: [], - }, - start: "2023-10-02", - end: "2023-10-10", - }, - { - name: "Frankenberger Hof", - description: { - de: "10% auf alles außer Fisch, Gambas und Steak", - en: "10% off everything except fish, prawns and steak", - }, - tags: [Tags.MEDITERRANIAN], - address: "Bismarckstraße 34", - location: [50.7680818, 6.1010771], - open: { - 1: [[[12,0],[14,30]], [[17,0],[23,0]]], - 2: [[[12,0],[14,30]], [[17,0],[23,0]]], - 3: [[[12,0],[14,30]], [[17,0],[23,0]]], - 4: [[[12,0],[14,30]], [[17,0],[23,0]]], - 5: [[[12,0],[14,30]], [[17,0],[23,0]]], - 6: [[[12,0],[23,0]]], - 7: [[[12,0],[23,0]]], - }, - start: "2023-10-02", - end: "2023-10-05", - }, - { - name: "Café Middelberg", - description: { - de: "Gratis Heißgetränk zum Frühstücksbuffet (11,50€)\n1 Stück Kuchen + Tasse Kaffee/Tee für 6€", - en: "Free hot drink with breakfast buffet (11,50€)\n1 piece of cake + cup of coffee/tea for 6€", - }, - tags: [Tags.CAFE], - address: "Rethelstraße 6", - location: [50.77609, 6.08488], - open: { - 1: [], - 2: [[[9,30],[18,0]]], - 3: [[[9,30],[18,0]]], - 4: [[[9,30],[18,0]]], - 5: [[[9,30],[18,0]]], - 6: [[[9,30],[18,0]]], - 7: [[[9,30],[17,30]]], - }, - start: "2023-10-02", - end: "2023-10-06", - }, - { - name: "Hexenhof", - description: { - de: "10% auf alles", - en: "10% off everything", - }, - tags: [Tags.RESTAURANT, Tags.BOWLS], - address: "Krämerstraße 7", - location: [50.77576, 6.08444], - open: { - 1: [[[10,0],[24+1,0]]], - 2: [[[10,0],[24+1,0]]], - 3: [[[10,0],[24+1,0]]], - 4: [[[10,0],[24+1,0]]], - 5: [[[10,0],[24+1,0]]], - 6: [[[10,0],[24+1,0]]], - 7: [[[10,0],[24+1,0]]], - }, - start: "2023-10-02", - end: "2023-10-10", - }, - { - name: "AKL", - description: { - de: "Falafel Sandwich: 3,50€\nSchawarma: 4,50€", - en: "Falafel sandwich: 3,50€\nSchawarma: 4,50€", - }, - tags: [Tags.LIBANESE], - address: "Pontstraße 1-3", - location: [50.7768852, 6.0832023], - open: { - 1: [[[11,30],[23,0]]], - 2: [[[11,30],[23,0]]], - 3: [[[11,30],[23,0]]], - 4: [[[11,30],[23,0]]], - 5: [[[11,30],[23,0]]], - 6: [[[11,30],[23,0]]], - 7: [], - holiday: [], - }, - start: "2023-10-02", - end: "2023-10-06", - }, - { - name: "essBar", - description: { - de: "Pizza zu Mittagskartenpreisen ab 5,50€\nCocktail 0,6L zu Preis von 0,3L", - en: "Pizza at lunch menu prices from 5,50€\nCocktail 0,6L at price of 0,3L", - }, - tags: [Tags.ITALIAN, Tags.PIZZA], - address: "Pontstraße 122-126", - location: [50.7796271, 6.0805498], - open: { - 1: [[[10,30],[24+3,0]]], - 2: [[[10,30],[24+3,0]]], - 3: [[[10,30],[24+3,0]]], - 4: [[[10,30],[24+3,0]]], - 5: [[[10,30],[24+5,0]]], - 6: [[[10,30],[24+5,0]]], - 7: [[[11,0],[24+3,0]]], - }, - start: "2023-10-02", - end: "2023-10-05", - }, - { - name: "Honigtopf im Bärenhof", - description: { - de: "Eintopf/Suppe des Tages + Brot für 4€\nFlaschenbier (Bolten, Tannenzäpfle, Gaffel Wiess) für 2,80€\nBerliner Luft, Mexikaner, Averna, Ouzo je 1€", - en: "Stew/soup of the day + bread for 4€\nBottle beer (Bolten, Tannenzäpfle, Gaffel Wiess) for 2,80€\nBerliner Luft, Mexikaner, Averna, Ouzo 1€ each", - }, - tags: [Tags.PUB], - address: "Templergraben 1-3", - location: [50.7755537, 6.0754905], - open: { - 1: [[[14,0],[24,0]]], - 2: [[[14,0],[24,0]]], - 3: [], - 4: [[[14,0],[24,0]]], - 5: [[[14,0],[24,0]]], - 6: [[[14,0],[24,0]]], - 7: [[[14,0],[24,0]]], - }, - start: "2023-10-02", - end: "2023-10-08", - }, - { - name: "Goldener Schwan", - description: { - de: "20% auf alles", - en: "20% off everything", - }, - tags: [Tags.PUB], - address: "Markt 37", - location: [50.77661, 6.08334], - open: { - 1: [], - 2: [[[11,0],[23,0]]], - 3: [[[11,0],[23,0]]], - 4: [[[11,0],[23,0]]], - 5: [[[11,0],[23,0]]], - 6: [[[11,0],[23,0]]], - 7: [[[11,0],[23,0]]], - holiday: [[[11,0],[23,0]]], - }, - start: "2023-10-02", - end: "2023-10-06", - }, - { - name: "Jogi Indian", - description: { - de: "10% Rabatt auf alle Hauptspeisen (nur abends, wann auch immer das sein soll)", - en: "10% discount on all main courses (only in the evening, whenever that may be)", - }, - tags: [Tags.INDIAN], - address: "Pontstraße 100", - location: [50.7792355, 6.0808554], - open: { - 1: [[[12,0],[22,0]]], - 2: [], - 3: [[[12,0],[22,0]]], - 4: [[[12,0],[22,0]]], - 5: [[[12,0],[22,0]]], - 6: [[[12,0],[22,0]]], - 7: [[[12,0],[22,0]]], - }, - start: "2023-10-02", - end: "2023-10-06", - }, - { - name: "White House Aachen", - description: { - de: "alle Cocktails 0,5L für 5€\nPils und Kölsch 0,5L für 2€\nWhite House Shot und Sweet Fire für 1€\nBuffet (all you can eat) für 7,90€", - en: "all cocktails 0,5L for 5€\nPils and Kölsch 0,5L for 2€\nWhite House Shot and Sweet Fire for 1€\nBuffet (all you can eat) for 7,90€", - }, - tags: [Tags.RESTAURANT, Tags.PUB], - address: "Pontstraße 141-149", - location: [50.7804656, 6.0787077], - open: { - 1: [[[10,0],[24+1,0]]], - 2: [[[10,0],[24+1,0]]], - 3: [[[10,0],[24+1,0]]], - 4: [[[10,0],[24+1,0]]], - 5: [[[10,0],[24+3,0]]], - 6: [[[10,0],[24+3,0]]], - 7: [[[10,0],[24+1,0]]], - }, - start: "2023-10-02", - end: "2023-10-08", - }, - { - name: "Gold of Naples", - description: { - de: "30% auf alle Pizzen", - en: "30% off all pizzas", - }, - tags: [Tags.ITALIAN, Tags.PIZZA], - address: "Pontstraße 125", - location: [50.7802473, 6.0800455], - open: { - 1: [[[17,0],[23,0]]], - 2: [[[17,0],[23,0]]], - 3: [[[17,0],[23,0]]], - 4: [[[12,0],[23,0]]], - 5: [[[12,0],[24,0]]], - 6: [[[12,0],[24,0]]], - 7: [[[12,0],[22,0]]], - holiday: [[[17,0],[23,0]]], - }, - start: "2023-10-02", - end: "2023-10-13", - }, - { - name: "Tangente", - description: { - de: "Studentenkarte gilt jeden Tag", - en: "Student menu valid every day", - }, - tags: [Tags.PUB], - address: "Pontstraße 141", - location: [50.7805865, 6.0793565], - open: { - 1: [], - 2: [[[18,0],[24,0]]], - 3: [[[18,0],[24,0]]], - 4: [[[18,0],[24,0]]], - 5: [[[17,0],[24+2,0]]], - 6: [[[17,0],[24+2,0]]], - 7: [[[12,0],[22,0]]], - }, - start: "2023-10-02", - end: "2023-10-13", - }, - { - name: "Pont Grill", - description: { - de: "Döner für 5€\nDürüm für 5€\nDönerteller mit Pommes für 8€\nFalafeltasche für 4€", - en: "Kebab for 5€\n Dürüm for 5€\n Kebab plate with fries for 8€\n Falafel bag for 4€", - }, - tags: [Tags.KEBAB, Tags.PIZZA], - address: "Pontstraße 119", - location: [50.7800846, 6.0801859], - open: { - 1: [[[11,0],[24,0]]], - 2: [], - 3: [[[11,0],[24,0]]], - 4: [[[11,0],[24,0]]], - 5: [[[11,0],[24+1,0]]], - 6: [[[11,0],[24+1,0]]], - 7: [[[11,0],[24,0]]], - }, - start: "2023-10-02", - end: "2023-10-31", - }, - { - name: "Trattoria Sole Mio", - description: { - de: "10% auf alles", - en: "10% off everything", - }, - tags: [Tags.PIZZA, Tags.ITALIAN], - address: "Pontstraße 140-142", - location: [50.78002, 6.08044], - open: { - 1: [[[17,0],[23,0]]], - 2: [], - 3: [[[17,0],[23,0]]], - 4: [[[17,0],[23,0]]], - 5: [[[17,0],[23,0]]], - 6: [[[12,0],[23,0]]], - 7: [[[12,0],[23,0]]], - }, - start: "2023-10-02", - end: "2023-10-31", - }, - { - name: "Lava Grill", - description: { - de: "1€ Rabatt auf Döner\n0,50€ Rabatt auf Dürüm\n1€ Rabatt auf Pommdöner", - en: "1€ off kebab\n0,50€ off dürüm\n1€ off Pommdöner", - }, - tags: [Tags.KEBAB, Tags.BURGER, Tags.PIZZA, Tags.PASTA], - address: "Pontstraße 128", - location: [50.7797010, 6.0805268], - open: { - 1: [[[11,0],[24+5,0]]], - 2: [[[11,0],[24+5,0]]], - 3: [[[11,0],[24+5,0]]], - 4: [[[11,0],[24+5,0]]], - 5: [[[11,0],[24+5,0]]], - 6: [[[11,0],[24+5,0]]], - 7: [[[11,0],[24+5,0]]], - }, - start: "2023-10-02", - end: "2023-10-15", - }, - { - name: "Pont Pascha", - description: { - de: "alle Studentenpizzen für 5€", - en: "all student pizzas for 5€", - }, - tags: [Tags.PIZZA, Tags.PASTA, Tags.KEBAB, Tags.TURKISH], - address: "Pontstraße 114-116", - location: [50.7794703, 6.0806303], - open: { - 1: [], - 2: [[[11,0],[23,0]]], - 3: [[[11,0],[23,0]]], - 4: [[[11,0],[23,0]]], - 5: [[[11,0],[24+1,0]]], - 6: [[[11,0],[24+1,0]]], - 7: [[[11,0],[23,0]]], - }, - start: "2023-10-02", - end: "2023-10-10", - }, - { - name: "TB taste the best", - description: { - de: "10% auf alles", - en: "10% off everything", - }, - tags: [Tags.ASIAN, Tags.BOWLS, Tags.BUBBLE_TEA], - address: "Pontstraße 89", - location: [50.7792939, 6.0805180], - open: { - 1: [], - 2: [[[12,0],[20,30]]], - 3: [[[12,0],[20,30]]], - 4: [[[12,0],[20,30]]], - 5: [[[12,0],[20,30]]], - 6: [[[12,0],[20,30]]], - 7: [[[13,0],[20,30]]], - }, - start: "2023-10-04", - end: "2023-10-06", - }, - { - name: "Burgeria", - description: { - de: "gratis Softdrink zum Mittagstisch (11-16 Uhr)\n20% auf Burgermenu\ngratis Shot zu jedem Cocktail ab 18 Uhr\n6 für 4 auf Bierkarte", - en: "free soft drink during lunch (11am to 4pm)\n20% off burger menu\nfree shot with any cocktail from 6pm\n6 for 4 on beer menu", - }, - tags: [Tags.BURGER], - address: "Templergraben 20", - location: [50.77622, 6.07645], - open: { - 1: [[[11,0],[23,0]]], - 2: [[[11,0],[23,0]]], - 3: [[[11,0],[23,0]]], - 4: [[[11,0],[23,0]]], - 5: [[[11,0],[23,0]]], - 6: [[[12,0],[23,0]]], - 7: [[[12,0],[22,0]]], - }, - start: "2023-10-01", - end: "2023-10-31", - }, - { - name: "Kaya's", - description: { - de: "gratis Getränk zu jeder Speise", - en: "free drink with every meal", - }, - tags: [Tags.KEBAB, Tags.TURKISH], - address: "Karlsgraben 7", - location: [50.7728627, 6.0766779], - open: { - 1: [[[11,0],[23,0]]], - 2: [[[11,0],[23,0]]], - 3: [[[11,0],[23,0]]], - 4: [[[11,0],[23,0]]], - 5: [[[11,0],[23,0]]], - 6: [[[11,0],[24+2,0]]], - 7: [[[13,0],[24+2,0]]], - }, - start: "2023-10-02", - end: "2023-10-31", - }, - { - name: "Mr Kebab", - description: { - de: "Döner, Dürüm, Pommdöner, Falafeltasche und Falafeldürüm für je 4€", - en: "Kebab, Dürüm, Pommdöner, Falafeltasche and Falafeldürüm for 4€ each", - }, - tags: [Tags.KEBAB, Tags.TURKISH], - address: "Annuntiatenbach 22", - location: [50.7758556, 6.0794007], - open: { - 1: [[[11,30],[23,0]]], - 2: [[[11,30],[23,0]]], - 3: [[[11,30],[23,0]]], - 4: [[[11,30],[23,0]]], - 5: [[[11,30],[23,0]]], - 6: [], - 7: [[[13,0],[22,0]]], - }, - start: "2023-10-02", - end: "2023-10-06", - }, - { - name: "Sowiso / Ocean", - description: { - de: "ausgewählte Shots für 1,10€ (Apfelkorn, Bärbelchen, Berliner Luft, Bessen, Dirty Harry, Els, Flimm, Korn, Mexikaner, Peppino Peach, Xuxu, Zack Zack)", - en: "selected shots for 1,10€ (Apfelkorn, Bärbelchen, Berliner Luft, Bessen, Dirty Harry, Els, Flimm, Korn, Mexikaner, Peppino Peach, Xuxu, Zack Zack)", - }, - tags: [Tags.PUB], - address: "Pontstraße 164-166", - location: [50.7809, 6.07932], - open: { - 1: [[[10,0],[24+2,0]]], - 2: [[[10,0],[24+2,0]]], - 3: [[[10,0],[24+2,0]]], - 4: [[[10,0],[24+2,0]]], - 5: [[[10,0],[24+2,0]]], - 6: [[[10,0],[24+2,0]]], - 7: [[[10,0],[24+2,0]]], - }, - start: "2023-10-03", - end: "2023-10-05", - }, - { - name: "Dat Frittebüdche", - description: { - de: "15% auf alles außer Mittagsangebot und Getränke", - en: "15% off everything except lunch specials and drinks", - }, - tags: [Tags.LIBANESE], - address: "Pontstraße 127", - location: [50.7802629, 6.0800023], - open: { - 1: [[[11,0],[24+1,0]]], - 2: [[[11,0],[24+1,0]]], - 3: [[[11,0],[24+1,0]]], - 4: [[[11,0],[24+1,0]]], - 5: [[[11,0],[24+2,0]]], - 6: [[[11,0],[24+2,0]]], - 7: [[[11,0],[24+2,0]]], - }, - start: "2023-10-02", - end: "2023-10-16", - }, - { - name: "Café Einstein", - description: { - de: "Stauder Pils 0,5L für 3,80€\nBerliner Luft, Persico und Mexikaner für 1€\nbelgische Biere für 4,20€", - en: "Stauder Pils 0,5L for 3,80€\nBerliner Luft, Persico and Mexikaner for 1€\nBelgian beers for 4,20€", - }, - tags: [Tags.PUB], - address: "Lindenplatz 17", - location: [50.77556, 6.07826], - open: { - 1: [[[16,0],[24,0]]], - 2: [[[16,0],[24,0]]], - 3: [[[16,0],[24,0]]], - 4: [[[16,0],[24,0]]], - 5: [[[16,0],[24+1,0]]], - 6: [[[16,0],[24+1,0]]], - 7: [[[16,0],[24,0]]], - holiday: [], - }, - start: "2023-10-01", - end: "2023-10-31", - }, - { - name: "Pont Asia", - description: { - de: "verschiedene Boxen für 4€ (nur 11-17 Uhr)\n1€ Rabatt bei Menüs", - en: "various boxes for 4€ (only 11am to 5pm)\n1€ discount on menus", - }, - tags: [Tags.CHINESE], - address: "Pontstraße 109", - location: [50.7798402, 6.0802550], - open: { - 1: [[[11,30],[22,0]]], - 2: [[[11,30],[22,0]]], - 3: [[[11,30],[22,0]]], - 4: [[[11,30],[22,0]]], - 5: [[[11,30],[15,0]]], - 6: [[[11,30],[22,0]]], - 7: [[[11,30],[22,0]]], - }, - start: "2023-10-02", - end: "2023-10-06", - }, - { - name: "Zaatar & Zait", - description: { - de: "10% auf orientalische Gerichte", - en: "10% off oriental dishes", - }, - tags: [Tags.ORIENTAL, Tags.LIBANESE], - address: "Alexianergraben 8", - location: [50.7722031, 6.0840352], - open: { - 1: [[[8,0],[19,0]]], - 2: [[[8,0],[19,0]]], - 3: [], - 4: [[[8,0],[19,0]]], - 5: [[[8,0],[19,0]]], - 6: [[[8,0],[19,0]]], - 7: [[[8,0],[18,0]]], - }, - start: "2023-10-01", - end: "2023-10-15", - }, - { - name: "Vis-a-vis", - description: { - de: "Spontaner Mystery-Rabatt auf Nachfrage", - en: "Spontaneous mystery discount if you ask", - }, - tags: [Tags.PUB], - address: "Kleinmarschierstraße 43", - location: [50.7731, 6.08456], - open: { - 1: [], - 2: [[[17,0],[24+1,0]]], - 3: [[[17,0],[24+1,0]]], - 4: [[[17,0],[24+1,0]]], - 5: [[[17,0],[24+1,0]]], - 6: [[[15,0],[24+3,0]]], - 7: [[[17,0],[24+1,0]]], - }, - start: "2023-10-02", - end: "2023-10-31", - }, - { - name: "Iskender Kebap", - description: { - de: "10% auf alles", - en: "10% off everything", - }, - tags: [Tags.KEBAB], - address: "Lothringer Straße 109", - location: [50.7696018, 6.1001838], - open: { - 1: [[[11,30],[22,0]]], - 2: [[[11,30],[22,0]]], - 3: [[[11,30],[22,0]]], - 4: [[[11,30],[22,0]]], - 5: [[[11,30],[22,0]]], - 6: [[[11,30],[22,0]]], - 7: [[[11,30],[22,0]]], - }, - start: "2023-10-01", - end: "2023-10-31", - }, - { - name: "Tugusto", - description: { - de: "10% auf alles", - en: "10% off everything", - }, - tags: [Tags.MEXICAN, Tags.BURGER], - address: "Bachstraße 30", - location: [50.7680605, 6.0980570], - open: { - 1: [], - 2: [[[12,0],[22,0]]], - 3: [[[12,0],[22,0]]], - 4: [[[12,0],[22,0]]], - 5: [[[12,0],[22,0]]], - 6: [[[12,0],[22,0]]], - 7: [[[12,0],[22,0]]], - }, - start: "2023-10-02", - end: "2023-10-15", - }, - { - name: "Boho Bowls", - description: { - de: "10% auf Bowls", - en: "10% off of bowls", - }, - tags: [Tags.BOWLS], - address: "Bismarckstraße 77", - location: [50.7687659, 6.1041665], - open: { - 1: [], - 2: [[[11,30],[22,0]]], - 3: [[[11,30],[22,0]]], - 4: [[[11,30],[22,0]]], - 5: [[[11,30],[22,0]]], - 6: [[[11,30],[22,0]]], - 7: [[[11,30],[22,0]]], - }, - start: "2023-10-02", - end: "2023-10-12", - }, - { - name: "Aposto", - description: { - de: "Folgende jeweils 2 für 1 von 12 bis 24 Uhr:\nEifelbräu Landbier 0,3L (3,40€)\nBarcello Rum 4cl (6€)\nAperol Spritz 0,2L (7,50€)\nPunk im Glas Hauswein 0,2L, Chardonnay oder Montepulcian (5,50€)", - en: "2 for 1 between 12pm and 12am for the following:\nEifelbräu Landbier 0.3L (3.40€)\nBarcello Rum 4cl (6€)\nAperol Spritz 0.2L (7.50€)\nPunk im Glas house wine 0.2L, Chardonnay or Montepulcian (5.50€)", - }, - tags: [Tags.ITALIAN, Tags.PIZZA, Tags.PASTA], - address: "Kapuzinergraben 19", - location: [50.7719680, 6.0858208], - open: { - 1: [[[12,0],[23,0]]], - 2: [[[12,0],[23,0]]], - 3: [[[12,0],[23,0]]], - 4: [[[12,0],[23,0]]], - 5: [[[12,0],[24+1,0]]], - 6: [[[12,0],[24+1,0]]], - 7: [[[12,0],[23,0]]], - }, - start: "2023-10-02", - end: "2023-10-06", - }, - { - name: "BissMarkt", - description: { - de: "gratis Portion Nachos bei Bestellung von drei Cocktails oder drei großen Bieren", - en: "free serving of nachos when ordering three cocktails or three large beers", - }, - tags: [Tags.RESTAURANT, Tags.BURGER, Tags.PASTA], - address: "Bismarckstraße 89", - location: [50.7688879, 6.1051043], - open: { - 1: [[[16,30],[22,30]]], - 2: [[[16,30],[22,30]]], - 3: [[[16,30],[22,30]]], - 4: [[[16,30],[22,30]]], - 5: [[[16,30],[23,0]]], - 6: [[[16,30],[23,0]]], - 7: [[[11,30],[22,0]]], - holiday: [[[16,30],[22,30]]], - }, - start: "2023-10-02", - end: "2023-10-06", - }, - { - name: "Vertical die Weinbar", - description: { - de: "15% auf Bouvet Crémant Rosé 0,1L\n15% auf Büchin Grauburgunder 0,1L\n15% auf Badala Primitivo 0,1L", - en: "15% off Bouvet Crémant Rosé 0.1L\n15% off Büchin Grauburgunder 0.1L\n15% off Badala Primitivo 0.1L", - }, - tags: [Tags.RESTAURANT, Tags.PUB], - address: "Kockerellstraße 13", - location: [50.7763206, 6.0814308], - open: { - 1: [[[17,0],[24,0]]], - 2: [[[17,0],[24,0]]], - 3: [[[17,0],[24,0]]], - 4: [[[17,0],[24,0]]], - 5: [[[17,0],[24+1,0]]], - 6: [[[15,0],[24+1,0]]], - 7: [], - }, - start: "2023-10-02", - end: "2023-10-07", - }, - { - name: "Chico Mendes", - description: { - de: "Cuba Libre für 5€", - en: "Cuba Libre for 5€", - }, - tags: [Tags.CAFE, Tags.PUB], - address: "Pontstraße 74-76", - location: [50.7785147, 6.0819152], - open: { - 1: [[[9,0],[20,0]]], - 2: [[[9,0],[20,0]]], - 3: [[[9,0],[20,0]]], - 4: [[[9,0],[20,0]]], - 5: [[[9,0],[20,0]]], - 6: [[[9,0],[20,0]]], - 7: [], - }, - start: "2023-10-02", - end: "2023-10-07", - }, - { - name: "Café Stopover", - description: { - de: "20€ Gutschein für Erstis", - en: "20€ voucher for freshers", - }, - tags: [Tags.CAFE], - address: "Marienbongard 24-28", - location: [50.7802003, 6.0786301], - open: { - 1: [[[8,0],[18,0]]], - 2: [[[8,0],[18,0]]], - 3: [[[8,0],[18,0]]], - 4: [[[8,0],[17,0]]], - 5: [[[8,0],[17,0]]], - 6: [[[10,30],[16,30]]], - 7: [[[12,0],[17,0]]], - holiday: [[[12,0],[17,0]]], - }, - start: "2023-10-02", - end: "2023-12-15", - }, - { - name: "Greedy and Thirsty", - description: { - de: "Shots für 1,90€\nCocktails für 4,90€", - en: "Shots for 1.90€\nCocktails for 4.90€", - }, - tags: [Tags.PUB], - address: "Pontstraße 110/112", - location: [50.7793931, 6.0806784], - open: { - 1: [], - 2: [], - 3: [[[16,0],[24+3,0]]], - 4: [], - 5: [[[16,0],[24+5,0]]], - 6: [[[16,0],[24+5,0]]], - 7: [], - }, - start: "2023-10-02", - end: "2023-10-08", - }, - - - /* - { - name: "", - description: { - de: "", - en: "", - }, - tags: [], - address: "", - location: [0, 0], - open: { - 1: [], - 2: [], - 3: [], - 4: [], - 5: [], - 6: [], - 7: [], - }, - start: "", - end: "", - }, - */ - - /* - { - "name": "Dreame Bowls", - "description": "Basis Bowl für 9€ anstatt 11€", - "tags": ["Bowls", "Keine Kekse"], - "address": "Alexianergraben 9", - "location": [50.771906, 6.0841712], - "open": { - "1": [[[11, 0], [20, 0]]], - "2": [[[11, 0], [20, 0]]], - "3": [[[11, 0], [20, 0]]], - "4": [[[11, 0], [20, 0]]], - "5": [[[11, 0], [20, 0]]], - "6": [[[11, 0], [20, 0]]], - "7": [[[11, 0], [20, 0]]], - "2022-10-03": [[[11, 0], [20, 0]]], - "2022-10-31": [[[11, 0], [20, 0]]] - }, - start: "", - end: "" - }, - { - "name": "Vapiano", - "description": "Montag 20% \nDienstag 20% auf vegane und vegetarische Pasta", - "tags": ["italienisch", "Keine Kekse"], - "address": "Franzstraße 51", - "location": [50.77057, 6.08556], - "open": { - "1": [[[11, 0], [20, 0]]], - "2": [[[15, 0], [20, 0]]], - "3": [[[11, 0], [20, 0]]], - "4": [[[11, 0], [20, 0]]], - "5": [[[11, 0], [20, 0]]], - "6": [[[11, 0], [20, 0]]], - "7": [[[11, 0], [20, 0]]], - "2022-10-03": [[[11, 0], [20, 0]]], - "2022-10-31": [[[11, 0], [20, 0]]] - }, - start: "", - end: "" - }, - { - "name": "Aposto", - "description": "Domkölsch 50% \nHomemade Limonade 50%", - "tags": ["italienisch", "Keine Kekse"], - "address": "Kapuzinergraben 19", - "location": [50.77196, 6.08582], - "open": { - "1": [[[11, 0], [20, 0]]], - "2": [[[11, 0], [15, 0]]], - "3": [[[11, 0], [20, 0]]], - "4": [[[11, 0], [20, 0]]], - "5": [[[11, 0], [20, 0]]], - "6": [[[11, 0], [20, 0]]], - "7": [[[11, 0], [20, 0]]], - "2022-10-03": [[[11, 0], [20, 0]]], - "2022-10-31": [[[11, 0], [20, 0]]] - }, - start: "", - end: "" - }, - { - "name": "Pausenbrot", - "description": "Zimtschnecke to go (außer Haus): 3,00€ statt 4,00€, nur 10-17 Uhr und einmal pro Person", - "tags": ["Café", "Keine Kekse"], - "address": "Pontstraße 5", - "location": [50.77701803234534, 6.083094840386871], - "open": { - "1": [[[11, 0], [20, 0]]], - "2": [[[11, 0], [14, 0]], [[17, 0], [20, 0]]], - "3": [[[11, 0], [20, 0]]], - "4": [[[11, 0], [20, 0]]], - "5": [[[11, 0], [20, 0]]], - "6": [[[11, 0], [20, 0]]], - "7": [[[11, 0], [20, 0]]], - "2022-10-03": [[[11, 0], [20, 0]]], - "2022-10-31": [[[11, 0], [20, 0]]] - }, - start: "", - end: "" - }, - { - "name": "Molkerei", - "description": "Shots (2cl): 1,50€\nAlle Cocktails: 5,90€", - "tags": ["Drinks", "Keine Kekse"], - "address": "Pontstraße 141-149", - "location": [50.78054978737415, 6.079428545927381], - "open": { - "1": [[[11, 0], [20, 0]]], - "2": [[[11, 0], [20, 0]]], - "3": [[[11, 0], [20, 0]]], - "4": [[[11, 0], [20, 0]]], - "5": [[[11, 0], [20, 0]]], - "6": [[[11, 0], [20, 0]]], - "7": [[[11, 0], [20, 0]]], - "2022-10-03": [[[11, 0], [20, 0]]], - "2022-10-31": [[[11, 0], [20, 0]]] - }, - start: "", - end: "" - }, - { - "name": "Oishii", - "description": "15% Rabatt auf alles", - "tags": ["Sushi", "Keine Kekse"], - "address": "Pontstraße 83", - "location": [50.77917729556271, 6.080703255727973], - "open": { - "1": [[[11, 0], [20, 0]]], - "2": [[[11, 0], [20, 0]]], - "3": [[[11, 0], [20, 0]]], - "4": [[[11, 0], [20, 0]]], - "5": [[[11, 0], [20, 0]]], - "6": [[[11, 0], [20, 0]]], - "7": [[[11, 0], [20, 0]]], - "2022-10-03": [[[11, 0], [20, 0]]], - "2022-10-31": [[[11, 0], [20, 0]]] - }, - start: "", - end: "" - }, - { - "name": "Pont Asia", - "description": "0,50€ Rabatt auf alle Hauptgerichte und Mittagskarte (nicht Boxen)", - "tags": ["asiatisch", "Keine Kekse"], - "address": "Pontstraße 109", - "location": [50.77981624725831, 6.080245569222246], - "open": { - "1": [[[11, 0], [20, 0]]], - "2": [[[11, 0], [20, 0]]], - "3": [[[11, 0], [20, 0]]], - "4": [[[11, 0], [20, 0]]], - "5": [[[11, 0], [20, 0]]], - "6": [[[11, 0], [20, 0]]], - "7": [[[11, 0], [20, 0]]], - "2022-10-03": [[[11, 0], [20, 0]]], - "2022-10-31": [[[11, 0], [20, 0]]] - }, - start: "", - end: "" - }, - { - "name": "Pontgrill", - "description": "Dönertasche: 3,50€ statt 6,00€\nDürüm: 4,00€ statt 6,50€\nDönerteller (Pommes+Salat): 6,50€ statt 11,50€", - "tags": ["Fast Food", "Keine Kekse"], - "address": "Pontstraße 119", - "location": [50.780045279522234, 6.080211469222247], - "open": { - "1": [[[11, 0], [20, 0]]], - "2": [[[11, 0], [20, 0]]], - "3": [[[11, 0], [20, 0]]], - "4": [[[11, 0], [20, 0]]], - "5": [[[11, 0], [20, 0]]], - "6": [[[11, 0], [20, 0]]], - "7": [[[11, 0], [20, 0]]], - "2022-10-03": [[[11, 0], [20, 0]]], - "2022-10-31": [[[11, 0], [20, 0]]] - }, - start: "", - end: "" - }, - { - "name": "Pontgarten", - "description": "Pils (0,5l): 2,40€\nShots: 1,00€\nLongdrinks: 2,90€\nWeizen (0,5l): 2,40€", - "tags": ["Drinks", "Keine Kekse"], - "address": "Pontstraße 154", - "location": [50.780658547014056, 6.079809098057529], - "open": { - "1": [[[11, 0], [20, 0]]], - "2": [[[11, 0], [20, 0]]], - "3": [[[11, 0], [20, 0]]], - "4": [[[11, 0], [20, 0]]], - "5": [[[11, 0], [20, 0]]], - "6": [[[11, 0], [20, 0]]], - "7": [[[11, 0], [20, 0]]], - "2022-10-03": [[[11, 0], [20, 0]]], - "2022-10-31": [[[11, 0], [20, 0]]] - }, - start: "", - end: "" - }, - { - "name": "Tangente", - "description": "Lillet, Aperol, Hugo, Gin Tonic, Rum Cola: 2 für 1", - "tags": ["Drinks", "Keine Kekse"], - "address": "Pontstraße 141", - "location": [50.780569663280545, 6.07943159805753], - "open": { - "1": [[[11, 0], [20, 0]]], - "2": [[[11, 0], [20, 0]]], - "3": [[[11, 0], [20, 0]]], - "4": [[[11, 0], [20, 0]]], - "5": [[[11, 0], [20, 0]]], - "6": [[[11, 0], [20, 0]]], - "7": [[[11, 0], [20, 0]]], - "2022-10-03": [[[11, 0], [20, 0]]], - "2022-10-31": [[[11, 0], [20, 0]]] - }, - start: "", - end: "" - }, - { - "name": "Vis a Vis", - "description": "Ouzo: 3 für 2\n2 Jägermeister: 3,50€\nBerliner Luft: 1,50€\nMexikaner: 1,50€", - "tags": ["Drinks", "Keine Kekse"], - "address": "Kleinmarschierstraße 43", - "location": [50.773044579521155, 6.084583198057205], - "open": { - "1": [[[11, 0], [20, 0]]], - "2": [[[11, 0], [20, 0]]], - "3": [[[11, 0], [20, 0]]], - "4": [[[11, 0], [20, 0]]], - "5": [[[11, 0], [20, 0]]], - "6": [[[11, 0], [20, 0]]], - "7": [[[11, 0], [20, 0]]], - "2022-10-03": [[[11, 0], [20, 0]]], - "2022-10-31": [[[11, 0], [20, 0]]] - }, - start: "", - end: "" - }, - { - "name": "Way of Ink", - "description": "Di-Do 17-19 Uhr: gespritzte Getränke, Longdrinks, Cocktails, Shots: 2 für 1\nFr ab 18 Uhr: Aperol: 5 für 3\njedes Bier (0,33l): 2,50€", - "tags": ["Drinks", "Keine Kekse"], - "address": "Theaterpl. 1A", - "location": [50.77302026437466, 6.087254726892482], - "open": { - "1": [[[11, 0], [20, 0]]], - "2": [[[11, 0], [20, 0]]], - "3": [[[11, 0], [20, 0]]], - "4": [[[11, 0], [20, 0]]], - "5": [[[11, 0], [20, 0]]], - "6": [[[11, 0], [20, 0]]], - "7": [[[11, 0], [20, 0]]], - "2022-10-03": [[[11, 0], [20, 0]]], - "2022-10-31": [[[11, 0], [20, 0]]] - }, - start: "", - end: "" - }, - { - "name": "Fill Full", - "description": "10% Rabatt auf alles", - "tags": ["Street Food", "Keine Kekse"], - "address": "Adalbertstraße 100", - "location": [50.774928266841634, 6.092385071641983], - "open": { - "1": [[[11, 0], [20, 0]]], - "2": [[[11, 0], [20, 0]]], - "3": [[[11, 0], [20, 0]]], - "4": [[[11, 0], [20, 0]]], - "5": [[[11, 0], [20, 0]]], - "6": [[[11, 0], [20, 0]]], - "7": [[[11, 0], [20, 0]]], - "2022-10-03": [[[11, 0], [20, 0]]], - "2022-10-31": [[[11, 0], [20, 0]]] - }, - start: "", - end: "" - }, - { - "name": "YAMEN'S", - "description": "10% Rabatt auf alles", - "tags": ["Fast Food", "Keine Kekse"], - "address": "Adalbertstraße 100", - "location": [50.77482215683048, 6.091867016748221], - "open": { - "1": [[[11, 0], [20, 0]]], - "2": [[[11, 0], [20, 0]]], - "3": [[[11, 0], [20, 0]]], - "4": [[[11, 0], [20, 0]]], - "5": [[[11, 0], [20, 0]]], - "6": [[[11, 0], [20, 0]]], - "7": [[[11, 0], [20, 0]]], - "2022-10-03": [[[11, 0], [20, 0]]], - "2022-10-31": [[[11, 0], [20, 0]]] - }, - start: "", - end: "" - }, - { - "name": "Eiscafé Del Mondo", - "description": "Alles: 2 für 1", - "tags": ["Eis", "Keine Kekse"], - "address": "Peterstraße 13", - "location": [50.77603121719418, 6.088954128739407], - "open": { - "1": [[[11, 0], [20, 0]]], - "2": [[[11, 0], [20, 0]]], - "3": [[[11, 0], [20, 0]]], - "4": [[[11, 0], [20, 0]]], - "5": [[[11, 0], [20, 0]]], - "6": [[[11, 0], [20, 0]]], - "7": [[[11, 0], [20, 0]]], - "2022-10-03": [[[11, 0], [20, 0]]], - "2022-10-31": [[[11, 0], [20, 0]]] - }, - start: "", - end: "" - }, - { - "name": "Side Kepab", - "description": "Hähnchendöner mit 0,3l Softgetränk für 5€", - "tags": ["Fast Food", "Keine Kekse"], - "address": "Blondelstraße 12", - "location": [50.77587494177401, 6.090325511551569], - "open": { - "1": [[[11, 0], [20, 0]]], - "2": [[[11, 0], [20, 0]]], - "3": [[[11, 0], [20, 0]]], - "4": [[[11, 0], [20, 0]]], - "5": [[[11, 0], [20, 0]]], - "6": [[[11, 0], [20, 0]]], - "7": [[[11, 0], [20, 0]]], - "2022-10-03": [[[11, 0], [20, 0]]], - "2022-10-31": [[[11, 0], [20, 0]]] - }, - start: "", - end: "" - }, - { - "name": "Saray", - "description": "Dienstags Weizen für 2,50€\nMittwochs Eifeler Landbier und Pils für 2,50€", - "tags": ["Drinks", "Keine Kekse"], - "address": "Pontstraße 118-120", - "location": [50.77952463498542, 6.080632499866584], - "open": { - "1": [[[11, 0], [20, 0]]], - "2": [[[11, 0], [20, 0]]], - "3": [[[11, 0], [20, 0]]], - "4": [[[11, 0], [20, 0]]], - "5": [[[11, 0], [20, 0]]], - "6": [[[11, 0], [20, 0]]], - "7": [[[11, 0], [20, 0]]], - "2022-10-03": [[[11, 0], [20, 0]]], - "2022-10-31": [[[11, 0], [20, 0]]] - }, - start: "", - end: "" - }, - { - "name": "Chico Mendes", - "description": "Bitburger Pils 0,4l: 2,50€\nGin Tonic 0,2l: 2,50€\nWodka Lemon/Cola/Orange 0,2l: 2,50€ Rum/Cola: 2,50€ 5 Shots(Apfelkorn oder Berliner Luft) 2cl: 6,00€", - "tags": ["Drinks", "Keine Kekse"], - "address": "Pontstraße 74-76", - "location": [50.778512984382, 6.081884546163372], - "open": { - "1": [[[11, 0], [20, 0]]], - "2": [[[11, 0], [20, 0]]], - "3": [[[11, 0], [20, 0]]], - "4": [[[11, 0], [20, 0]]], - "5": [[[11, 0], [20, 0]]], - "6": [[[11, 0], [20, 0]]], - "7": [[[11, 0], [20, 0]]], - "2022-10-03": [[[11, 0], [20, 0]]], - "2022-10-31": [[[11, 0], [20, 0]]] - }, - start: "", - end: "" - }, - { - "name": "White House Aachen", - "description": "All you can eat Buffet für 7,90€ statt 9,90€\nWodka Energy für 2,50€\nRum Cola für 2,50€\nGin Tonic für 2,50€\nzu jedem Cocktail ein Wodka/Rum/Gin Shot\nHefe Weizen für 2,50€", - "tags": ["europäisch", "Keine Kekse"], - "address": "Pontstraße 141-149", - "location": [50.78060261551528, 6.078871442245423], - "open": { - "1": [[[11, 0], [20, 0]]], - "2": [[[11, 0], [20, 0]]], - "3": [[[11, 0], [20, 0]]], - "4": [[[11, 0], [20, 0]]], - "5": [[[11, 0], [20, 0]]], - "6": [[[11, 0], [20, 0]]], - "7": [[[11, 0], [20, 0]]], - "2022-10-03": [[[11, 0], [20, 0]]], - "2022-10-31": [[[11, 0], [20, 0]]] - }, - start: "", - end: "" - }, - { - "name": "Sowiso / Ocean", - "description": "Apfelkorn, Bärbelden, Bessen, Dirty Harry, Cls, Flimm, Korn, Mexikaner, Peppino peadi, xoxo, zack zack für 1,10€\nAusgewählte Longdrinks für 4,50€", - "tags": ["Drinks", "Keine Kekse"], - "address": "Pontsraße 164-166", - "location": [50.78103221823567, 6.079380413147282], - "open": { - "1": [[[11, 0], [20, 0]]], - "2": [[[11, 0], [20, 0]]], - "3": [[[11, 0], [20, 0]]], - "4": [[[11, 0], [20, 0]]], - "5": [[[11, 0], [20, 0]]], - "6": [[[11, 0], [20, 0]]], - "7": [[[11, 0], [20, 0]]], - "2022-10-03": [[[11, 0], [20, 0]]], - "2022-10-31": [[[11, 0], [20, 0]]] - }, - start: "", - end: "" - }, - { - "name": "Black LaserTag Aachen", - "description": "Pro Spiel, pro Person 7€ statt 9€ (gültig vom 04.10.2022-31.03.2023)", - "tags": ["Aktivität", "Keine Kekse"], - "address": "Jülicher Straße 392", - "location": [50.79157470512543, 6.119794913637211], - "open": { - "1": [[[11, 0], [20, 0]]], - "2": [[[11, 0], [20, 0]]], - "3": [[[11, 0], [20, 0]]], - "4": [[[11, 0], [20, 0]]], - "5": [[[11, 0], [20, 0]]], - "6": [[[11, 0], [20, 0]]], - "7": [[[11, 0], [20, 0]]], - "2022-10-03": [[[11, 0], [20, 0]]], - "2022-10-31": [[[11, 0], [20, 0]]] - }, - start: "", - end: "" - }, - { - "name": "Holocafe Aachen", - "description": "Pro Holosesion, pro Person 12,50€ statt 16€ (Gültig vom 04.10.2022-31.03.2023)", - "tags": ["Aktivität", "Keine Kekse"], - "address": "Theaterstraße 30", - "location": [50.771466036584286, 6.089812913636517], - "open": { - "1": [[[11, 0], [20, 0]]], - "2": [[[11, 0], [20, 0]]], - "3": [[[11, 0], [20, 0]]], - "4": [[[11, 0], [20, 0]]], - "5": [[[11, 0], [20, 0]]], - "6": [[[11, 0], [20, 0]]], - "7": [[[11, 0], [20, 0]]], - "2022-10-03": [[[11, 0], [20, 0]]], - "2022-10-31": [[[11, 0], [20, 0]]] - }, - start: "", - end: "" - }, - { - "name": "Cineplex", - "description": "Grundpreis 6,90€ statt 9,00€ Mo-Do aufs reguläre Programm (Gültig vom 04.10.2022-17.10.2022)", - "tags": ["Aktivität", "Keine Kekse"], - "address": "Borngasse 30", - "location": [50.77195756254811, 6.086610947161357], - "open": { - "1": [[[11, 0], [20, 0]]], - "2": [[[11, 0], [20, 0]]], - "3": [[[11, 0], [20, 0]]], - "4": [[[11, 0], [20, 0]]], - "5": [[[11, 0], [20, 0]]], - "6": [[[11, 0], [20, 0]]], - "7": [[[11, 0], [20, 0]]], - "2022-10-03": [[[11, 0], [20, 0]]], - "2022-10-31": [[[11, 0], [20, 0]]] - }, - start: "", - end: "" - }, - { - "name": "Frankys Farm", - "description": "Frankys Sandwich + Cappuccino 3,50€", - "tags": ["Café", "Keine Kekse"], - "address": "Pontstraße 137", - "location": [50.78051780775668, 6.079474904169743], - "open": { - "1": [[[11, 0], [20, 0]]], - "2": [[[11, 0], [20, 0]]], - "3": [[[11, 0], [20, 0]]], - "4": [[[11, 0], [20, 0]]], - "5": [[[11, 0], [20, 0]]], - "6": [[[11, 0], [20, 0]]], - "7": [[[11, 0], [20, 0]]], - "2022-10-03": [[[11, 0], [20, 0]]], - "2022-10-31": [[[11, 0], [20, 0]]] - }, - start: "", - end: "" - }, - { - "name": "Food Brother", - "description": "2€ Rabatt auf einen Burger je Person", - "tags": ["Fast Food", "Keine Kekse"], - "address": "Pontstraße 113", - "location": [50.77991634430484, 6.08028044670684], - "open": { - "1": [[[11, 0], [20, 0]]], - "2": [[[11, 0], [20, 0]]], - "3": [[[11, 0], [20, 0]]], - "4": [[[11, 0], [20, 0]]], - "5": [[[11, 0], [20, 0]]], - "6": [[[11, 0], [20, 0]]], - "7": [[[11, 0], [20, 0]]], - "2022-10-03": [[[11, 0], [20, 0]]], - "2022-10-31": [[[11, 0], [20, 0]]] - }, - start: "", - end: "" - }, - { - "name": "Pizzaria La Finesta", - "description": "10% Rabatt auf alles", - "tags": ["italienisch", "Keine Kekse"], - "address": "Pontstraße 123", - "location": [50.78026091474326, 6.080116255737473], - "open": { - "1": [[[11, 0], [20, 0]]], - "2": [[[11, 0], [20, 0]]], - "3": [[[11, 0], [20, 0]]], - "4": [[[11, 0], [20, 0]]], - "5": [[[11, 0], [20, 0]]], - "6": [[[11, 0], [20, 0]]], - "7": [[[11, 0], [20, 0]]], - "2022-10-03": [[[11, 0], [20, 0]]], - "2022-10-31": [[[11, 0], [20, 0]]] - }, - start: "", - end: "" - } - */ -]; - -export default discounts; diff --git a/src/lib/server/eswe.ts b/src/lib/server/eswe.ts new file mode 100644 index 0000000000000000000000000000000000000000..d65b097a4690bb32679eb7f7a189d34b8fd11a97 --- /dev/null +++ b/src/lib/server/eswe.ts @@ -0,0 +1,3 @@ +export const IMAGE_DIR = "static/eswe/"; +export const SUPPORTED_EXTENSIONS = ["jpg", "png", "jpeg", "webp"]; +export const INVALID_CHARACTERS = /[^a-z0-9\-_ .]/gi; diff --git a/src/lib/server/images.ts b/src/lib/server/images.ts new file mode 100644 index 0000000000000000000000000000000000000000..4f292aba79ac5d8e1582f2b8889eda525617dab8 --- /dev/null +++ b/src/lib/server/images.ts @@ -0,0 +1,134 @@ +import path from "path"; +import fs from "fs/promises"; +import sharp, { type AvailableFormatInfo, type AvifOptions, type FormatEnum, type GifOptions, type HeifOptions, type Jp2Options, type JpegOptions, type JxlOptions, type OutputOptions, type PngOptions, type TiffOptions, type WebpOptions } from "sharp"; +import type { Localized } from "$lib/utils"; + +type ImageBuffer = Buffer | ArrayBuffer | Uint8Array | Uint8ClampedArray | Int8Array | Uint16Array | Int16Array | Uint32Array | Int32Array | Float32Array | Float64Array; +export type ImageMetadata<T=never> = { + identifier: string; + description: Localized; + width: number; + height: number; + sizes: Array<{ + filename: string; + width: number; + url: string; + type: string; + }>; + custom?: T; +}; + +const categoryImages: Record<string, ImageMetadata<unknown>[]> = {}; +async function getCategoryImages<T>(category: string): Promise<ImageMetadata<T>[]> { + if(categoryImages[category]) return categoryImages[category] as ImageMetadata<T>[]; + const metadataFile = getMetadataFile(category); + const metadata = await fs.readFile(metadataFile, "utf-8").then(JSON.parse).catch(() => []) as ImageMetadata<T>[]; + categoryImages[category] = metadata; + return metadata; +} +async function setCategoryImages<T>(category: string, images: ImageMetadata<T>[]){ + categoryImages[category] = images; + const metadataFile = getMetadataFile(category); + await fs.writeFile(metadataFile, JSON.stringify(images), "utf-8"); +} +async function addCategoryImages<T>(category: string, images: ImageMetadata<T>[]){ + const existing = await getCategoryImages<T>(category); + await setCategoryImages(category, existing.concat(images)); +} + +const wantedWidths = [320, 640, 1280, 1920, 2560]; +const wantedTypes = [ + //{ type: "image/jxl", extension: "jxl", options: { quality: 80 } as JxlOptions }, + { type: "image/avif", extension: "avif", options: { quality: 60 } as AvifOptions }, + { type: "image/webp", extension: "webp", options: { quality: 80 } as WebpOptions }, + { type: "image/jpeg", extension: "jpg", options: { quality: 80 } as JpegOptions }, +] satisfies Array<{ extension: keyof FormatEnum | AvailableFormatInfo, type: string, options: OutputOptions | JpegOptions | PngOptions | WebpOptions | AvifOptions | HeifOptions | JxlOptions | GifOptions | Jp2Options | TiffOptions }>; + +function ensureDirectoryExists(directory: string){ + return fs.mkdir(directory, { recursive: true }); +} +function getDirectory(category: string){ + return path.join("static", "images", category); +} +function getMetadataFile(category: string){ + return path.join(getDirectory(category), "metadata.json"); +} +function getUrl(category: string, filename: string){ + return `/images/${category}/${filename}`; +} + +async function uploadSingleImage<T>(imageBuffer: ImageBuffer, description: Localized, category: string, custom?: T, forcedId?: string): Promise<ImageMetadata<T>> { + const transformer = sharp(imageBuffer).rotate(); + const imageMetadata = await transformer.metadata(); + const sizes = wantedWidths.filter(width => imageMetadata.width ? width <= imageMetadata.width : true); + const identifier = forcedId || Math.random().toString(36).slice(2); + const metadata: ImageMetadata<T> = { + identifier, + description, + sizes: [], + width: imageMetadata.width ?? 0, + height: imageMetadata.height ?? 0, + custom, + }; + const promises = []; + for(const type of wantedTypes){ + for(const size of sizes){ + const transformed = transformer.clone().resize(size).toFormat(type.extension, type.options); + const filename = `${identifier}-${size}.${type.extension}`; + promises.push(transformed.toFile(path.join(getDirectory(category), filename))); + metadata.sizes.push({ + filename, + width: size, + url: getUrl(category, filename), + type: type.type, + }); + } + const filename = `${identifier}.${type.extension}`; + promises.push(transformer.clone().toFormat(type.extension, type.options).toFile(path.join(getDirectory(category), filename))); + metadata.sizes.push({ + filename, + width: imageMetadata.width ?? 0, + url: getUrl(category, `${identifier}.${type.extension}`), + type: type.type, + }); + } + await Promise.all(promises); + return metadata; +} + +export async function uploadImages<T>(category: string, images: Array<{ buffer: ImageBuffer, description: Localized, custom?: T, forcedId?: string }>): Promise<ImageMetadata<T>[]> { + const directory = getDirectory(category); + await ensureDirectoryExists(directory); + const metadata = await Promise.all(images.map(image => uploadSingleImage(image.buffer, image.description, category, image.custom, image.forcedId))); + await addCategoryImages(category, metadata); + return metadata; +} +export async function uploadImage<T>(buffer: ImageBuffer, description: Localized, category: string, custom?: T, forcedId?: string): Promise<ImageMetadata<T>> { + return (await uploadImages(category, [{ buffer, description, custom, forcedId }]))[0]; +} + +export async function getImages<T>(category: string): Promise<ImageMetadata<T>[]> { + return getCategoryImages<T>(category); +} + +export async function editImage<T>(category: string, identifier: string, {description, custom}: { description?: Localized, custom?: T }){ + const images = await getCategoryImages<T>(category); + const image = images.find(image => image.identifier === identifier); + if(!image) return false; + if(description !== undefined) image.description = description; + if(custom !== undefined) image.custom = custom; + await setCategoryImages(category, images); + return true; +} + +export async function deleteImage(category: string, identifier: string): Promise<boolean> { + const metadataFile = getMetadataFile(category); + const metadata = await fs.readFile(metadataFile, "utf-8").then(JSON.parse).catch(() => []) as ImageMetadata[]; + const image = metadata.find(image => image.identifier === identifier); + if(!image) return false; + const directory = getDirectory(category); + const promises = image.sizes.map(size => fs.rm(path.join(directory, size.filename))); + promises.push(setCategoryImages(category, metadata.filter(image => image.identifier !== identifier))); + await Promise.all(promises); + return true; +} diff --git a/src/lib/server/mail.ts b/src/lib/server/mail.ts new file mode 100644 index 0000000000000000000000000000000000000000..6c6ceb8ba00ccd35d52d73a6e0bfc5bdce8b9a93 --- /dev/null +++ b/src/lib/server/mail.ts @@ -0,0 +1,110 @@ +import { env } from "$env/dynamic/private"; +import nodemailer from "nodemailer"; +import { Tutor } from "./database/entities/Tutor.entity"; +import type Mail from "nodemailer/lib/mailer"; +import { compileTemplate, parseTemplate, renderTemplate, type MailTemplate } from "$lib/mail"; +import { Config } from "./database/entities/Config.entity"; +import ical from "ical-generator"; +import type SMTPTransport from "nodemailer/lib/smtp-transport"; + +const transporter = nodemailer.createTransport({ + host: env.MAIL_HOST, + port: Number(env.MAIL_PORT), + secure: false, + tls: { + rejectUnauthorized: false + }, + from: env.MAIL_FROM, +}); + +export async function sendTrainingMails(){ + const { trainingMailReminderDays, trainingsStart, trainingsEnd, mailTemplates: { trainingInformation: template } } = Config.get(); + const now = Date.now(); + const [startHour, startMinute] = trainingsStart.split(":").map(t=>parseInt(t)); + const [endHour, endMinute] = trainingsEnd.split(":").map(t=>parseInt(t)); + const allTutors = Object.groupBy((await Tutor.getAll()).filter(tutor => { + if(tutor.sentTrainingMail || !tutor.training) return false; + const trainingDate = Date.parse(tutor.training.date); + if(trainingDate < now) return false; + const daysUntilTraining = (trainingDate - now) / 1000 / 60 / 60 / 24; + return daysUntilTraining <= trainingMailReminderDays; + }), tutor=>tutor.training!.id); + try { + const results = await Promise.all(Object.values(allTutors).map(async tutors=>{ + if(!tutors) return; // won't happen, but typescript doesn't know that + const start = new Date(tutors[0].training!.date); + start.setHours(startHour, startMinute, 0, 0); + const end = new Date(start); + end.setHours(endHour, endMinute, 0, 0); + return await sendMail({ + template, + tutors, + from: env.MAIL_FROM, + icalEvent: { + filename: "training.ics", + method: "request", + content: ical({ + events: [ + { + start, + end, + summary: "Tutschulung / Tutor training", + description: "Verpflichtende Schulung für alle Ersti-Tuts / Mandatory training for all fresher tutors", + location: tutors[0].training!.location, + url: "https://esa.fsmpi.rwth-aachen.de/", + } + ], + }).toString(), + }, + }) + })).then(results=>({errors: results.flatMap(r=>r!.errors), successes: results.flatMap(r=>r!.successes)})); + if(results.errors.length > 0) console.error("Error sending training mails", results.errors); + await Promise.allSettled(results.successes.map(({tutorId: id})=>Tutor.update({id, sentTrainingMail: true}))); + }catch(error){ + console.error(error); + } +} + +export async function sendTutorRegisteredMail(tutor: Tutor){ + const { mailTemplates: { tutorRegistered: template } } = Config.get(); + return sendMail({ + template, + tutors: [tutor], + from: env.MAIL_FROM, + }); +} + +type SuccessType = {status: "success", response: SMTPTransport.SentMessageInfo, tutorId: number}; +type RejectedType = {status: "rejected", response: SMTPTransport.SentMessageInfo, tutorId: number}; +type ErrorType = {status: "error", error: Error, tutorId: number}; +export function sendMail({ template, tutors, attachments = [], from, icalEvent }: { + template: MailTemplate, + tutors: Tutor[], + attachments?: Mail.Attachment[], + from?: string | Mail.Address, + icalEvent?: Mail.IcalAttachment, +}): Promise<{successes: SuccessType[], rejected: RejectedType[], errors: ErrorType[]}> { + const replyTo = template.replyTo === "-" ? undefined : template.replyTo; + const textParts = parseTemplate(template.subject); + const subjectParts = parseTemplate(template.subject); + return Promise.all(tutors.map(tutor => { + const compiled = compileTemplate(textParts, tutor); + const rendered = renderTemplate(compiled); + const subject = compileTemplate(subjectParts, tutor, ({de, en})=>`${de} / ${en}`); + return transporter.sendMail({ + to: tutor.email, + from: from || env.MAIL_FROM, + text: compiled, + html: rendered, + subject, + attachments, + replyTo, + icalEvent, + }).then(response=>({status: response.accepted.includes(tutor.email) ? "success" : "rejected", response, tutorId: tutor.id} as SuccessType|RejectedType)).catch(error=>({status: "error", error, tutorId: tutor.id} satisfies ErrorType)); + })).then(results=>{ + const errors = results.filter(r=>r.status === "error"); + const rejected = results.filter(r=>r.status === "rejected"); + const successes = results.filter(r=>r.status === "success"); + return {errors, rejected, successes}; + }); +} diff --git a/src/lib/server/renderFlyerImages.js b/src/lib/server/renderFlyerImages.js deleted file mode 100644 index 5a7b01e967c937e8e55ebfddbea11073322e5f8b..0000000000000000000000000000000000000000 --- a/src/lib/server/renderFlyerImages.js +++ /dev/null @@ -1,42 +0,0 @@ -(async () => { - const fs = await import("node:fs/promises"); - const pdfjs = (await import("pdfjs-dist")).default; - const { createCanvas } = await import("canvas"); - - /** @param {import("pdfjs-dist").PDFPageProxy} page @param {number} minWidth @param {number} minHeight */ - async function renderPage(page, minWidth, minHeight){ - const unscaledViewport = page.getViewport({ scale: 1 }); - const scale = Math.max(minWidth / unscaledViewport.width, minHeight / unscaledViewport.height); - const viewport = page.getViewport({ scale }); - const canvas = createCanvas(viewport.width, viewport.height); - const ctx = canvas.getContext("2d"); - await page.render({ - // @ts-ignore - canvasContext: ctx, - viewport, - }).promise; - return Buffer.from(canvas.toDataURL("image/jpeg").split(",")[1], "base64"); - } - - const path = "static/flyer/"; - // clean up old images - const oldImages = (await fs.readdir(path)).filter(name => name.endsWith(".jpg")); - await Promise.all(oldImages.map(filename => fs.unlink(path + filename))); - // generate new images - const files = (await fs.readdir(path)).filter(name => name.toLowerCase().endsWith(".pdf")); - await Promise.all(files.map(async filename => { - console.debug(`Rendering ${filename}`); - const file = await pdfjs.getDocument({ - url: path + filename, - standardFontDataUrl: "node_modules/pdfjs-dist/standard_fonts/", - }).promise; - const page = await file.getPage(1); - const jpgName = filename.slice(0, -4); - return Promise.all([renderPage(page, 800, 400), renderPage(page, 30, 80)]) - .then(([hqBuffer, lqBuffer])=>Promise.all([ - fs.writeFile(`${path + jpgName}_hq.jpg`, hqBuffer), - fs.writeFile(`${path + jpgName}_lq.jpg`, lqBuffer), - ])); - })); - await fs.writeFile(`${path}data.json`, JSON.stringify(files.map(filename=>filename.slice(0, -4)))); -})(); diff --git a/src/lib/server/schedules.ts b/src/lib/server/schedules.ts new file mode 100644 index 0000000000000000000000000000000000000000..33902fe4780df65f35feaafe41b52068d00b3c43 --- /dev/null +++ b/src/lib/server/schedules.ts @@ -0,0 +1,379 @@ +import { type CanvasRenderingContext2D, createCanvas, Image, registerFont, loadImage } from "canvas"; +import { locales, type Locale } from "$lib/i18n/i18n"; +import { daysBetween, type Localized } from "$lib/utils"; +import { Schedule } from "./database/entities/Schedule.entity"; +import { Config } from "./database/entities/Config.entity"; +import fs from "node:fs/promises"; + +type Timeslot = { start: number, end: number, startUncertainty: number, endUncertainty: number, title: Localized, location?: Localized }; +type TimeslotWithConcurrent = Timeslot & { concurrent: TimeslotWithConcurrent[], maxConcurrent: number, maxNeighborConcurrent: number, startSlot: number, slots: number }; +type Day = { date: string, timeslots: Timeslot[] }; +export type ScheduleType = { lang: Locale, title: string, updated: string, days: Day[] }; +export type CanvasType = "image" | "svg" | "pdf"; +export type BaseConfig = { + fontWeight: "100"|"200"|"300"|"400"|"500"|"600"|"700"|"800"|"900", + fontSize: number, + fontFamily: string, + titleFontWeight: "100"|"200"|"300"|"400"|"500"|"600"|"700"|"800"|"900", + titleFontFamily: string, + titleFontSize: number, + startTime: number, + endTime: number, + dayWidth: number, + timeSlotHeight: number, + timeColWidth: number, + lineWidth: number, + brandingImageDistance: number, + branding: Localized, + phoneNumber: string, + emailAddress: string, + website: string, + stateAsOf: Localized, + brandingImageScale: number, +}; +export type PaddingConfig = { + padding: number, +}; +export type ColorConfig = { + activityBackgroundColor: string, + activityTextColor: string, + borderColor: string, + textColor: string, +}; +export type CompleteConfig = BaseConfig & ColorConfig & PaddingConfig; + +export type RenderParams = { + pdf: boolean, + darkmodeImage: boolean, + lightmodeImage: boolean, +}; + +export const FONTS_FOLDER = "src/lib/server/fonts"; +export const SUPPORTED_EXTENSIONS = ["ttf", "otf"]; +async function registerFonts(){ + const fontFiles = (await fs.readdir(FONTS_FOLDER)).filter(filename => SUPPORTED_EXTENSIONS.includes(filename.split(".").pop()!)); + for(const fontFile of fontFiles){ + const fontName = fontFile.split(".").slice(0, -1).join("."); + registerFont(`${FONTS_FOLDER}/${fontFile}`, { family: fontName }); + } +} + +export async function renderAllSchedules(params?: Partial<RenderParams>){ + const schedules = await Schedule.getAll(); + return Promise.all(schedules.map(schedule=>renderSchedule(schedule, params))); +} +export async function renderSchedule(scheduleId: Schedule|number, params?: Partial<RenderParams>){ + const { pdf=false, darkmodeImage=false, lightmodeImage=false } = params || { pdf: true, darkmodeImage: true, lightmodeImage: true }; + const schedule = typeof scheduleId === "number" ? (await Schedule.getById(scheduleId))! : scheduleId; + if(!schedule) throw new Error("Schedule not found"); + await registerFonts(); + const config = Config.get(); + const transformedDays: {[k: string]: Day} = {}; + for(const timeslot of schedule.timeslots){ + transformedDays[timeslot.date] ||= {date: timeslot.date, timeslots: []}; + transformedDays[timeslot.date].timeslots.push({start: timeslot.start, end: timeslot.end, startUncertainty: timeslot.startUncertainty, endUncertainty: timeslot.endUncertainty, title: timeslot.title, location: timeslot.location}); + } + const days: Day[] = daysBetween(config.fresherWeekStart, config.fresherWeekEnd) + .map(date => transformedDays[date] || {date, timeslots: []}); + const brandingImage = await (async ()=>{ + try{ + return await loadImage("static/stundenplaene/branding.png"); + }catch(e){ + return undefined; + } + })(); + const promises = []; + for(const locale of locales){ + const s: ScheduleType = { + lang: locale, + title: schedule.title[locale], + updated: schedule.updated, + days, + }; + /* + static/stundenplaene/ + branding.png + [id]/ + [lang].pdf + [lang].dark.svg + [lang].light.svg + */ + promises.push(fs.mkdir(`static/stundenplaene/${schedule.id}`, {recursive: true}).then(()=>{ + const promises = []; + if(pdf){ + const renderedPdf = render( + {...config.scheduleConfig.config, ...config.scheduleConfig.pdfPadding, ...config.scheduleConfig.lightmodeConfig}, + s, + brandingImage, + "pdf", + ).canvas.createPDFStream({ + creationDate: new Date(), + title: schedule.title[locale], + author: "Fachschaft Mathematik/Physik/Informatik, Aaron Dötsch", + modDate: new Date(schedule.updated), + //subject: "", // TODO + keywords: "Stundenplan;Ablaufplan;Ablauf;RWTH Aachen;Erstsemester;Ersti;Erstiwoche;Einführungswoche;O-Phase;WiSe 2024/25;Wintersemester 2024/25;Informatik;Physik;Mathematik;Wirtschaftsmathematik;Lehramt", // TODO keywords based on schedule and language + creator: "Website der Erstsemesterarbeit der Fachschaft Mathematik/Physik/Informatik an der RWTH Aachen (https://esa.fsmpi.rwth-aachen.de)", + }); + promises.push(fs.writeFile(`static/stundenplaene/${schedule.id}/${locale}.pdf`, renderedPdf)); + } + if(darkmodeImage){ + const renderedDarkmodeSvg = render( + {...config.scheduleConfig.config, ...config.scheduleConfig.imagePadding, ...config.scheduleConfig.darkmodeConfig}, + s, + brandingImage, + "svg", + ).canvas.toBuffer(); + promises.push(fs.writeFile(`static/stundenplaene/${schedule.id}/${locale}.dark.svg`, renderedDarkmodeSvg)); + } + if(lightmodeImage){ + const renderedLightmodeSvg = render( + {...config.scheduleConfig.config, ...config.scheduleConfig.imagePadding, ...config.scheduleConfig.lightmodeConfig}, + s, + brandingImage, + "svg", + ).canvas.toBuffer(); + promises.push(fs.writeFile(`static/stundenplaene/${schedule.id}/${locale}.light.svg`, renderedLightmodeSvg)); + } + return Promise.all(promises); + })); + } + await Promise.all(promises); +} + +type DrawingConfig = CompleteConfig & {timeSlots: number, tableWidth: number, tableHeight: number, fullWidth: number, fullHeight: number, marginX: number, marginTop: number, marginBottom: number}; +function render(config: CompleteConfig, data: ScheduleType, brandingImage: Image|undefined, type: CanvasType){ + const dayWidth = Math.ceil(config.dayWidth * config.fontSize); + const timeSlotHeight = Math.ceil(config.timeSlotHeight * config.fontSize); + const timeColWidth = Math.ceil(config.timeColWidth * config.fontSize); + const timeSlots = Math.round((config.endTime - config.startTime) / 30); + const tableWidth = data.days.length * dayWidth + 2 * timeColWidth; + const tableHeight = (timeSlots + 1) * timeSlotHeight; + const halfBorder = Math.ceil(config.lineWidth / 2); + const marginX = halfBorder + config.padding; + const marginTop = (((brandingImage?.height || 0) * config.brandingImageScale / 100) || -config.brandingImageDistance) + config.brandingImageDistance + halfBorder + config.padding; + const marginBottom = Math.ceil(30 + 1.5 * config.titleFontSize + 3.55 * config.fontSize) + halfBorder + config.padding; + const fullWidth = tableWidth + 2 * marginX; + const fullHeight = tableHeight + marginTop + marginBottom; + const canvas = createCanvas(fullWidth, fullHeight, type==="image"?undefined:type); + const ctx = canvas.getContext("2d"); + if(type!=="image") ctx.textDrawingMode = "glyph"; + const configCopy: DrawingConfig = { + ...config, + dayWidth, + timeSlotHeight, + timeColWidth, + timeSlots, + tableWidth, + tableHeight, + fullWidth, + fullHeight, + marginX, + marginTop, + marginBottom, + }; + draw(ctx, configCopy, data, brandingImage); + return {context: ctx, canvas}; +} + +function collect(set: Set<TimeslotWithConcurrent>, element: TimeslotWithConcurrent) { + if(set.has(element)) return set; + set.add(element); + for(const ts of element.concurrent) collect(set, ts); + return set; +} +function getFirstFreeSlot(timeslot: TimeslotWithConcurrent) { + const used: boolean[] = Array(timeslot.maxConcurrent).fill(false); + for(const ts of timeslot.concurrent) { + for(let i = 0; i < ts.slots; i++) used[ts.startSlot + i] = true; + } + return used.findIndex(x => !x); +} +function getConcurrentCount(timeslot: TimeslotWithConcurrent){ + const actions: {time: number, change: number}[] = []; + for(const ts of [...timeslot.concurrent, timeslot]){ + actions.push({time: ts.start-ts.startUncertainty, change: 1}, {time: ts.end+ts.endUncertainty, change: -1}); + } + return actions + .sort((a, b)=>a.time===b.time?a.change-b.change:a.time-b.time) + .reduce(({current, max}, action)=>({max: Math.max(max, current+action.change), current: current+action.change}), {current: 0, max: 0}) + .max; +} +function drawText(ctx: CanvasRenderingContext2D, color: string, fontSize: number, text: string, x: number, y: number, maxWidth: number) { + fontSize *= 1.25; + ctx.fillStyle = color; + const lines = text.split("\n").flatMap(line => { + if(ctx.measureText(line).width <= maxWidth) return [line]; + const parts: string[] = []; + let i = 0; + for(let j = 0; j < line.length; j++) { + if([" ", "-"].includes(line[j])) { + parts.push(line.slice(i, j + 1)); + i = j + 1; + } + } + parts.push(line.slice(i)); + const lines: string[] = []; + while(parts.length > 0) { + let i = 1; + let line = parts[0]; + while(i < parts.length && ctx.measureText((line + parts[i]).trim()).width <= maxWidth) { + line += parts[i]; + i++; + } + if(line.trim()) lines.push(line.trim()); + parts.splice(0, i); + } + return lines; + }); + const baseY = y - (lines.length - 1) / 2 * fontSize; + for(let i = 0; i < lines.length; i++) { + ctx.fillText(lines[i], x, baseY + i * fontSize, maxWidth); + } +} +function drawOutline(ctx: CanvasRenderingContext2D, x: number, y: number, w: number, h: number, borderColor: string, lineWidth: number){ + ctx.strokeStyle = borderColor; + ctx.lineWidth = lineWidth; + ctx.strokeRect(x, y, w, h); +} +function drawBox(ctx: CanvasRenderingContext2D, x: number, y: number, w: number, h: number, backgroundColor: string|null, borderColor: string, lineWidth: number, textColor: string, fontSize: number, caption: string, gradientTop = 0, gradientBottom = 0, minY = y, maxY = y+h){ + if(h <= 0 || w <= 0) return; + if(backgroundColor){ + const gradient = ctx.createLinearGradient(x, y-gradientTop, x, y+h+gradientBottom); + if(gradientTop) gradient.addColorStop(0, backgroundColor + "00"); + gradient.addColorStop(gradientTop / (gradientTop+h+gradientBottom), backgroundColor); + gradient.addColorStop(1 - gradientBottom / (gradientTop+h+gradientBottom), backgroundColor); + if(gradientBottom) gradient.addColorStop(1, backgroundColor + "00"); + ctx.fillStyle = gradient; + ctx.fillRect(x+lineWidth/2, Math.max(minY, y-gradientTop+lineWidth/2), w-lineWidth, gradientTop+h+gradientBottom-lineWidth); + } + if(backgroundColor && (gradientTop || gradientBottom)){ + const gradient = ctx.createLinearGradient(x, y-gradientTop, x, y+h+gradientBottom); + if(gradientTop) gradient.addColorStop(0, borderColor + "00"); + gradient.addColorStop(gradientTop / (gradientTop+h+gradientBottom), borderColor); + gradient.addColorStop(1 - gradientBottom / (gradientTop+h+gradientBottom), borderColor); + if(gradientBottom) gradient.addColorStop(1, borderColor + "00"); + ctx.strokeStyle = gradient; + ctx.lineWidth = lineWidth; + const lineY1 = Math.max(minY, y - gradientTop - lineWidth/2); + const lineY2 = Math.min(maxY, y + h + gradientBottom + lineWidth/2); + ctx.beginPath(); + ctx.moveTo(x, lineY1); + ctx.lineTo(x, lineY2); + ctx.closePath(); + ctx.stroke(); + ctx.beginPath(); + ctx.moveTo(x + w, lineY1); + ctx.lineTo(x + w, lineY2); + ctx.closePath(); + ctx.stroke(); + ctx.beginPath(); + if(!gradientTop || y <= minY){ + ctx.moveTo(x, y); + ctx.lineTo(x + w, y); + } + if(!gradientBottom || y + h >= maxY){ + ctx.moveTo(x, y + h); + ctx.lineTo(x + w, y + h); + } + ctx.closePath(); + ctx.stroke(); + }else drawOutline(ctx, x, y, w, h, borderColor, lineWidth); + if(caption) drawText(ctx, textColor, fontSize, caption, x + w/2, y + h/2, w - lineWidth); +} +function draw(ctx: CanvasRenderingContext2D, config: DrawingConfig, data: ScheduleType, brandingImage: Image|undefined){ + const timeFormatter = new Intl.DateTimeFormat(data.lang, { hour: "2-digit", minute: "2-digit" }); + const dateFormatter = new Intl.DateTimeFormat(data.lang, { day: "2-digit", month: "short", year: "numeric", weekday: "long" }); + const standardFont = `${config.fontWeight} ${config.fontSize}px ${config.fontFamily}`; + const titleFont = `${config.titleFontWeight} ${config.titleFontSize}px ${config.titleFontFamily}`; + ctx.textAlign = "center"; + ctx.textBaseline = "middle"; + const dateObj = new Date(); + // let table start at 0,0 + ctx.translate(config.marginX, config.marginTop); + // draw title + ctx.font = titleFont; + ctx.font = titleFont; + drawText(ctx, config.textColor, config.titleFontSize, data.title, config.tableWidth / 2, config.tableHeight + 30 + config.titleFontSize / 2 + Math.ceil(config.lineWidth / 2), config.tableWidth); + // for some reason setting it only once doesn't work + ctx.font = standardFont; + ctx.font = standardFont; + // draw table outline + drawOutline(ctx, 0, 0, config.tableWidth, config.tableHeight, config.borderColor, config.lineWidth); + // let table body start at 0,0 + ctx.translate(0, config.timeSlotHeight); + // draw time columns + for(let y = -1; y < config.timeSlots; y++) { + for(const baseX of [0, config.tableWidth - config.timeColWidth]) { + drawBox(ctx, baseX, y * config.timeSlotHeight, config.timeColWidth, config.timeSlotHeight, null, config.borderColor, config.lineWidth, config.textColor, config.fontSize, y < 0 ? "" : timeFormatter.format(dateObj.setHours(0, 30 * y + config.startTime))); + } + } + // make first day start at 0,0 + ctx.translate(config.timeColWidth, 0); + // draw day columns + for(let x = 0; x < data.days.length; x++) { + // draw day outline + drawOutline(ctx, 0, 0, config.dayWidth, config.timeSlotHeight * config.timeSlots, config.borderColor, config.lineWidth); + // draw day header + drawBox(ctx, 0, -config.timeSlotHeight, config.dayWidth, config.timeSlotHeight, null, config.borderColor, config.lineWidth, config.textColor, config.fontSize, dateFormatter.format(new Date(data.days[x].date))); + // process timeslots to correctly display concurrent events + const timeslots: TimeslotWithConcurrent[] = data.days[x].timeslots.map(timeslot => { + const concurrent: TimeslotWithConcurrent[] = []; + return { ...timeslot, concurrent, maxConcurrent: 0, maxNeighborConcurrent: 0, startSlot: -1, slots: 0 }; + }); + for(const timeslot of timeslots){ + timeslot.concurrent = timeslots.filter(ts => ts !== timeslot && !(ts.end+ts.endUncertainty <= timeslot.start-timeslot.startUncertainty || ts.start-ts.startUncertainty >= timeslot.end+timeslot.endUncertainty)); + timeslot.maxConcurrent = getConcurrentCount(timeslot); + } + for(const timeslot of timeslots){ + timeslot.maxNeighborConcurrent = Math.max(1, ...timeslot.concurrent.map(ts => ts.maxConcurrent)); + } + const alreadyChecked: Set<TimeslotWithConcurrent> = new Set(); + for(const timeslot of timeslots){ + if(alreadyChecked.has(timeslot)) continue; + const set = collect(new Set(), timeslot); + const max = Math.max(...[...set].map(ts => ts.maxConcurrent)); + for(const ts of set) { + ts.maxConcurrent = max; + alreadyChecked.add(ts); + } + } + for(const timeslot of timeslots) { + timeslot.startSlot = getFirstFreeSlot(timeslot); + timeslot.slots = 1; + } + // draw timeslots + for(const timeslot of timeslots) { + const nthW = config.dayWidth / timeslot.maxConcurrent; + const w = timeslot.slots * nthW; + const x = timeslot.startSlot * nthW; + const minY = 0; + const y = Math.max(minY, (timeslot.start - config.startTime) / 30 * config.timeSlotHeight); + const maxY = config.tableHeight - config.timeSlotHeight; + const h = Math.min(maxY - y, (timeslot.end - timeslot.start) / 30 * config.timeSlotHeight); + let text = timeslot.title[data.lang]; + if(timeslot.location?.[data.lang]) text += `\n(${timeslot.location[data.lang]})`; + drawBox(ctx, x, y, w, h, config.activityBackgroundColor, config.borderColor, config.lineWidth, config.activityTextColor, config.fontSize, text, timeslot.startUncertainty, timeslot.endUncertainty, minY + config.lineWidth / 2, maxY); + } + // make next day start at 0,0 + ctx.translate(config.dayWidth, 0); + } + // make table start at 0,0 + ctx.resetTransform(); + ctx.translate(config.marginX, config.marginTop); + // draw branding image + if(brandingImage){ + const w = brandingImage.width * config.brandingImageScale / 100; + const h = brandingImage.height * config.brandingImageScale / 100; + ctx.drawImage(brandingImage, config.tableWidth - w, -h - config.brandingImageDistance, w, h); + } + // make bottom of title + half the title + 1.8 * fontSize start at 0,0 + ctx.translate(0, config.tableHeight + 30 + 1.5 * config.titleFontSize + Math.ceil(config.lineWidth / 2) + 1.8 * config.fontSize); + // draw footer + ctx.font = standardFont; + const updateDateFormatter = new Intl.DateTimeFormat(data.lang, {day: "2-digit", month: "short", year: "numeric"}); + drawText(ctx, config.textColor, config.fontSize, config.branding[data.lang], (config.timeColWidth + config.dayWidth) / 2, 0, config.timeColWidth + config.dayWidth); + drawText(ctx, config.textColor, config.fontSize, `${config.emailAddress}\n${config.phoneNumber}`, config.tableWidth / 2, 0, config.tableWidth); + drawText(ctx, config.textColor, config.fontSize, `${config.website}\n${config.stateAsOf[data.lang].replace("%%", updateDateFormatter.format(new Date(data.updated)))}`, config.tableWidth - (config.timeColWidth + config.dayWidth) / 2, 0, config.timeColWidth + config.dayWidth); + ctx.stroke(); +} diff --git a/src/lib/server/tutorMatching.ts b/src/lib/server/tutorMatching.ts new file mode 100644 index 0000000000000000000000000000000000000000..455a63648e950e36140d9b650492896c7679f9c0 --- /dev/null +++ b/src/lib/server/tutorMatching.ts @@ -0,0 +1,38 @@ +import { removeDiacritics } from "modern-diacritics"; +import type { Tutor } from "./database/entities/Tutor.entity"; +import { shuffle } from "$lib/utils"; + +type Match = {tutorialName: string, tutors: number[]}; + +export function matchTutors(tutors: Tutor[], tutorialNames: string[]): Match[] { + if(tutorialNames.length === 0) return []; + tutorialNames = shuffle(tutorialNames); + const tuts = tutors.map(t=>({name: removeDiacritics(`${t.firstname} ${t.lastname}`), id: t.id, coTutorWish: removeDiacritics(t.coTutorWish), used: false})); + const matches: Match[] = []; + for(const tut of tuts){ + if(tut.used) continue; + const match = tuts.find(t=>t.name===tut.coTutorWish && t.coTutorWish===tut.name && !t.used); + if(!match) continue; + tut.used = true; + match.used = true; + matches.push({tutorialName: tutorialNames.pop()!, tutors: [tut.id, match.id]}); + if(tutorialNames.length===0) break; + } + return matches; +} + +export function matchRemainingTutors(tutors: Tutor[], tutorialNames: string[]): Match[] { + if(tutorialNames.length === 0) return []; + tutors = shuffle(tutors).sort((a,b)=>a.mentor===b.mentor?0:a.mentor?-1:1); + const matches: Match[] = []; + while(tutorialNames.length>0 && tutors.length>1){ + matches.push({ + tutorialName: tutorialNames.pop()!, + tutors: [tutors.shift()!.id, tutors.pop()!.id], + }); + } + for(let i = Math.min(tutors.length, matches.length)-1; i >= 0; i--){ + matches[i].tutors.push(tutors.pop()!.id); + } + return matches; +} diff --git a/src/lib/studyPrograms.ts b/src/lib/studyPrograms.ts deleted file mode 100644 index 6665c451bcd2024b701a50724cc7f8b6e1ed209f..0000000000000000000000000000000000000000 --- a/src/lib/studyPrograms.ts +++ /dev/null @@ -1,10 +0,0 @@ -// When changing, also edit $LL.StudyProgram for all locales! -export const studyPrograms = [ - "Informatik", - "Mathematik", - "Physik", - "Wirtschaftsmathematik", - "Lehramt", -] as const; -export default studyPrograms; -export type StudyProgram = typeof studyPrograms[number]; diff --git a/src/lib/utils.ts b/src/lib/utils.ts new file mode 100644 index 0000000000000000000000000000000000000000..909156822d1c205ea20630f00d8cb44e4a905111 --- /dev/null +++ b/src/lib/utils.ts @@ -0,0 +1,125 @@ +import type { Locale } from "$lib/i18n/i18n"; + +export type ResolvePropDeep<T, P> = + P extends "" ? T : + P extends `${infer Pre}.${infer Suf}` ? + Pre extends keyof T ? ResolvePropDeep<T[Pre], Suf> : never : + P extends keyof T ? T[P] : + never; + +export type ObjectToDotProp<T extends object, V=unknown> = ObjectToDotPropOfInternal<T, V>[keyof T]; +type ObjectToDotPropOfInternal<T extends object, V> = { + [Key in keyof T]: + Key extends string ? + T[Key] extends object|null|undefined ? + // @ts-expect-error instantiation excessively deep and possibly infinite - we dont care + ObjectToDotProp<T[Key], V> extends string ? `${Key}.${ObjectToDotProp<T[Key], V>}` : + never : + T[Key] extends V ? Key : + never : + never; +}; + +export type DeepReadonly<T> = + T extends (infer R)[] ? DeepReadonlyArray<R> : + // eslint-disable-next-line @typescript-eslint/ban-types + T extends Function ? T : + T extends object ? DeepReadonlyObject<T> : + T; +interface DeepReadonlyArray<T> extends ReadonlyArray<DeepReadonly<T>> { } +type DeepReadonlyObject<T> = { + readonly [P in keyof T]: DeepReadonly<T[P]>; +}; + +export type Localized<T=string> = {[locale in Locale]: T}; + +type Digit = 0|1|2|3|4|5|6|7|8|9; +type OneToNine = 1|2|3|4|5|6|7|8|9; +type Year = `${19|20}${Digit}${Digit}`; +type Month28 = "02"; +type Month30 = `0${4|6|9}`|11; +type Month31 = `0${1|3|5|7|8}`|"10"|"12"; +type Day28 = `0${OneToNine}`|`1${Digit}`|`2${0|1|2|3|4|5|6|7|8}`; +type Day30 = Day28|"29"|"30"; +type Day31 = Day30|"31"; +export type DateString = `${Year}-${`${Month28}-${Day28}`|`${Month30}-${Day30}`|`${Month31}-${Day31}`}`; + +export function uniqueByProp<T>(array: T[], propExtractor: (item: T) => any): T[]{ + return [...new Map(array.map(item => [propExtractor(item), item])).values()]; +} + +export type PartialExcept<T, K extends keyof T> = Partial<T> & Pick<T, K>; + +export type DeepPartial<T> = { + [P in keyof T]?: T[P] extends Array<infer U> ? Array<DeepPartial<U>> : DeepPartial<T[P]>; +}; + +/** + * @param target target object + * @param source source object + * @param fullArrayOverwrite if `true`: `deepAssign({a: [1, 2, 3]}, {a: [4, 5]}) => {a: [4, 5]}`, if `false`: `deepAssign({a: [1, 2, 3]}, {a: [4, 5]}) => {a: [4, 5, 3]}` + * @returns source object + */ +export function deepAssign<T>(target: T, source: DeepPartial<T>|undefined, fullArrayOverwrite=true): T{ + if(!target) return target; + if(typeof target !== "object" || typeof source !== "object") return target; + for(const key in source){ + if(typeof target[key] === "object" && typeof source[key] === "object"){ + if(Array.isArray(target[key]) && fullArrayOverwrite){ + target[key] = source[key]!; + }else{ + deepAssign(target[key], source[key]!); + } + }else{ + target[key] = source[key]; + } + } + return target; +} + +export function deepEquals(obj1: any, obj2: any){ + if(typeof obj1 !== "object" || typeof obj2 !== "object") return obj1 === obj2; + const keysChecked = new Set<string>(); + for(const key in obj1){ + if(!deepEquals(obj1[key], obj2[key])) return false; + keysChecked.add(key); + } + for(const key in obj2){ + if(keysChecked.has(key)) continue; + if(!deepEquals(obj1[key], obj2[key])) return false; + // no need to add key to keysChecked, not needed again afterwards + } + return true; +} + +export function daysBetween(start: string, end: string): string[]{ + const days: string[] = []; + const currentDate = new Date(start); + const endDate = new Date(end); + // setDate does indeed modify the date object + // eslint-disable-next-line no-unmodified-loop-condition + while(currentDate <= endDate){ + days.push(currentDate.toISOString().split("T")[0]); + currentDate.setDate(currentDate.getDate() + 1); + } + return days; +} + +export function pick<T extends object, K extends keyof T>(obj: T, keys: K[]): Pick<T, K>{ + const result = {} as Pick<T, K>; + for(const key of keys){ + result[key] = obj[key]; + } + return result; +} + +export function shuffle<T>(array: T[]): T[]{ + const shuffled = array.slice(); + for(let i = shuffled.length - 1; i > 0; i--){ + const j = Math.floor(Math.random() * (i + 1)); + const temp = shuffled[i]; + shuffled[i] = shuffled[j]; + shuffled[j] = temp; + } + return shuffled; +} diff --git a/src/params/uuid.ts b/src/params/uuid.ts new file mode 100644 index 0000000000000000000000000000000000000000..172d3813887427246b478c8a029ce9e53fcdb5c6 --- /dev/null +++ b/src/params/uuid.ts @@ -0,0 +1,3 @@ +export function match(param: string): boolean { + return /^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12}$/.test(param); +} diff --git a/src/routes/(non-admin)/+layout.server.ts b/src/routes/(non-admin)/+layout.server.ts new file mode 100644 index 0000000000000000000000000000000000000000..a93ad606ed236386d376c60dca5efcbbc3563708 --- /dev/null +++ b/src/routes/(non-admin)/+layout.server.ts @@ -0,0 +1,9 @@ +import { Config } from '$lib/server/database/entities/Config.entity'; +import type { LayoutServerLoad } from './$types'; + +export const load = (async () => { + const headerLinks = Config.get()?.headerLinks ?? []; // empty fallback in case instance is not set up yet + return { + headerLinks, + }; +}) satisfies LayoutServerLoad; diff --git a/src/routes/(non-admin)/+layout.svelte b/src/routes/(non-admin)/+layout.svelte index f6b3ebe6dd83a56bcdbe53e710b21eed59352be6..e1faa1ef6bc466ca84fa0a5fa24f74ccac9e010f 100644 --- a/src/routes/(non-admin)/+layout.svelte +++ b/src/routes/(non-admin)/+layout.svelte @@ -1,8 +1,9 @@ -<script> +<script lang="ts"> import MainLayout from "$lib/components/MainLayout.svelte"; - export let data; + + let { data, children } = $props(); </script> -<MainLayout bind:locale={data.locale}> - <slot /> +<MainLayout headerLinks={data.headerLinks as string[]}> + {@render children()} </MainLayout> diff --git a/src/routes/(non-admin)/+page.server.ts b/src/routes/(non-admin)/+page.server.ts index ed5867b12ff4ddf6c56a2418c3b993a1396dee59..425387d7cccdbf9f81ff43615b42a50e1d44b5ce 100644 --- a/src/routes/(non-admin)/+page.server.ts +++ b/src/routes/(non-admin)/+page.server.ts @@ -1,6 +1,7 @@ -import { redirect } from "@sveltejs/kit"; -import type { PageServerLoad } from "./$types"; +import { redirect } from '@sveltejs/kit'; +import type { PageServerLoad } from './$types'; +import { Config } from '$lib/server/database/entities/Config.entity'; -export const load: PageServerLoad = async ()=>{ - throw redirect(303, "/information"); -}; +export const load = (async () => { + redirect(302, Config.get()?.defaultPath || '/information'); +}) satisfies PageServerLoad; diff --git a/src/routes/(non-admin)/+page.svelte b/src/routes/(non-admin)/+page.svelte deleted file mode 100644 index 55dc280cb7f6d08487481785678f38a3b78e4e6e..0000000000000000000000000000000000000000 --- a/src/routes/(non-admin)/+page.svelte +++ /dev/null @@ -1,2 +0,0 @@ -<h1>Welcome to SvelteKit</h1> -<p>Visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to read the documentation</p> diff --git a/src/routes/(non-admin)/datenschutz/+page.svelte b/src/routes/(non-admin)/datenschutz/+page.svelte new file mode 100644 index 0000000000000000000000000000000000000000..b6a1bb6348baff0ead153a704189aa5c5eea8b1f --- /dev/null +++ b/src/routes/(non-admin)/datenschutz/+page.svelte @@ -0,0 +1,23 @@ +<script lang="ts"> + import { LL, type Translation, type TranslationFunction } from "$lib/i18n/i18n"; + import { Heading, P } from "flowbite-svelte"; + + $: paragraphs = Object.keys($LL.PrivacyPolicy.Paragraphs) as (keyof Translation["PrivacyPolicy"]["Paragraphs"])[]; + + type Paragraph = { Headline: TranslationFunction; Content: TranslationFunction; }; +</script> + +<Heading tag="h1" customSize="text-4xl font-bold" class="mb-6 text-center">{$LL.PrivacyPolicy.Headline()}</Heading> + +{#if $LL.PrivacyPolicy.Disclaimer()} +<P class="mb-6" color="text-red-600 dark:text-red-500" whitespace="preline" weight="extrabold">{$LL.PrivacyPolicy.Disclaimer()}</P> +{/if} + +{#each paragraphs as key} + <Heading tag="h3" class="mb-1">{($LL.PrivacyPolicy.Paragraphs[key] as Paragraph).Headline()}</Heading> + <P class="mb-6" whitespace="preline">{($LL.PrivacyPolicy.Paragraphs[key] as Paragraph).Content()}</P> +{/each} + +{#if $LL.PrivacyPolicy.Footer()} +<P class="mt-12 mb-6" whitespace="preline">{$LL.PrivacyPolicy.Footer()}</P> +{/if} diff --git a/src/routes/(non-admin)/eswe/+page.server.ts b/src/routes/(non-admin)/eswe/+page.server.ts new file mode 100644 index 0000000000000000000000000000000000000000..69bf7a598e035ce1b3d46ed22dc36af33934196f --- /dev/null +++ b/src/routes/(non-admin)/eswe/+page.server.ts @@ -0,0 +1,15 @@ +import { Config } from "$lib/server/database/entities/Config.entity"; +import { getImages } from "$lib/server/images"; + +export async function load(){ + const config = Config.get(); + const esweLink = Date.now() < (config?.esweRegistrationStart?.getTime() ?? Number.MAX_SAFE_INTEGER) ? {registrationOpen: false, esweLink: null} as const : {registrationOpen: true, esweLink: config.esweLink} as const; + const images = (await getImages<number>("eswe")).sort((a, b) => a.custom! - b.custom!); + return { + images, + ...esweLink, + price: config?.eswePrice ?? 0, + start: config?.esweStart, + end: config?.esweEnd, + }; +} diff --git a/src/routes/(non-admin)/eswe/+page.svelte b/src/routes/(non-admin)/eswe/+page.svelte index 3c50d8e2740a51ecf3ad2b313f3f853d8dcd99a1..645c160dbc2b2da2b7ee0e1110a9c014caf3e866 100644 --- a/src/routes/(non-admin)/eswe/+page.svelte +++ b/src/routes/(non-admin)/eswe/+page.svelte @@ -1,22 +1,15 @@ <script lang="ts"> import { A, Button, Carousel, Gallery, Heading, Li, List, Modal, P } from "flowbite-svelte"; - import { LL, locale } from "$lib/i18n/i18n-svelte"; + import { LL, locale } from "$lib/i18n/i18n"; + import Image from "$lib/components/Image.svelte"; - const images = [ - {src: "/eswe/0f5c2fa1-92a9-4c65-a4df-87fa7c63c6f4.JPG"}, - {src: "/eswe/22983672-610f-4460-b372-57f61ed97629.JPG"}, - {src: "/eswe/16777d3b-8e2b-4350-8a2a-404ae1e26301.JPG"}, - {src: "/eswe/9f08f05c-b6a0-4fa6-8049-976da3828376.JPG"}, - {src: "/eswe/c99c0398-9fe3-4f4b-855a-2612906bf0f6.JPG"}, - {src: "/eswe/44425363-690c-4974-8f27-0a8fcc96375e.JPG"}, - {src: "/eswe/cc9ad1ff-2ea8-4a1f-bd4d-09d8d042a3b3.JPG"}, - {src: "/eswe/0a8b0d3c-6b2d-4601-8ed8-145c0c689ef1.JPG"}, - {src: "/eswe/4ec6de7f-d575-40b9-a880-f30d394ba65d.JPG"}, - ]; + export let data; + + const images = data.images; let showModal = false; let index = 0; - function openModal(src: string){ - index = images.findIndex(i=>i.src===src); + function openModal(idx: number){ + index = idx; showModal = true; } function handleKey(e: KeyboardEvent){ @@ -24,24 +17,31 @@ else if(e.key === "ArrowLeft") index = (index-1+images.length)%images.length; else if(e.key === "ArrowRight") index = (index+1)%images.length; } + + const dateFormatter = new Intl.DateTimeFormat("de", { day: "numeric", month: "long" }); + const currencyFormatter = new Intl.NumberFormat("de", { style: "currency", currency: "EUR", minimumFractionDigits: data.price%100===0 ? 0 : 2, maximumFractionDigits: data.price%100===0 ? 0 : 2 }); + + const showInfo = data.start && data.end && Date.parse(data.start) > Date.now(); </script> <Heading tag="h1" customSize="text-4xl font-bold" class="mb-6 text-center">{$LL.ESWE.Headline()}</Heading> <svelte:body on:keydown={handleKey} /> {#if $locale === "de"} +{#if showInfo} <P class="mb-3">Hallo liebe Erstis,</P> <P class="mb-3"> auch dieses Jahr findet natürlich wieder das Erstsemesterwochenende (ESWE) statt! 🎊<br> - Wenn du vom <b>17.11. bis 19.11.</b> noch nichts zu tun hast und dann <b>volljährig</b> sein wirst, dann lies weiter und melde dich gerne unten an. + Wenn du vom <b>{dateFormatter.formatRange(new Date(data.start!), new Date(data.end!))}</b> noch nichts zu tun hast und dann <b>volljährig</b> sein wirst, dann lies weiter und melde dich gerne unten an. </P> -<P class="mb-6">Deine ESA</P> +<P class="mb-3">Deine ESA</P> +{/if} <Heading tag="h2" customSize="text-3xl font-bold" class="mb-3 mt-6">Was ist das ESWE?</Heading> -<P class="mb-6"> +<P class="mb-3"> Das ESWE ist ein jährliches Wochenende, an dem wir eine eigene Unterkunft mieten und dir damit die Möglichkeit bieten andere Erstis (und uns) besser kennen zu lernen.<br> Wir haben hierbei immer freiwilliges lustiges Programm geplant und viele Spiele zur Unterhaltung dabei. </P> @@ -59,10 +59,11 @@ <Heading tag="h2" customSize="text-3xl font-bold" class="mb-3 mt-6">ESWE dieses Jahr</Heading> +{#if showInfo} <P class="mb-3"> - Das ESWE wird vom <b>17.11. bis 19.11.</b> im Landhaus Hohenfried in der Eifel stattfinden. Die Hin- und Rückfahrt werden wir mit den Öffis bewältigen.<br> - Dein Beitrag liegt bei <b>35€</b> und damit wird alles bis auf alkoholische Getränke inklusive sein. Bier wirst du vor Ort kaufen können und alles andere müsstest du selbst mitnehmen.<br> - Außerdem musst du am 17.11. <b>volljährig</b> sein. + Das ESWE wird vom <b>{dateFormatter.formatRange(new Date(data.start!), new Date(data.end!))}</b> im Landhaus Hohenfried in der Eifel stattfinden. Die Hin- und Rückfahrt werden wir mit den Öffis bewältigen.<br> + Dein Beitrag liegt bei <b>{currencyFormatter.format(data.price/100)}</b> und damit wird alles bis auf alkoholische Getränke inklusive sein. Bier wirst du vor Ort kaufen können und alles andere müsstest du selbst mitnehmen.<br> + Außerdem musst du am {dateFormatter.format(new Date(data.start!))} <b>volljährig</b> sein. </P> <P class="mb-6"> @@ -70,33 +71,39 @@ </P> <div class="flex mb-6 justify-center"> - {#if Date.now() < new Date("2023-10-02T06:00:00+0200").getTime()} + {#if !data.registrationOpen} <Button disabled size="xl">Die Anmeldung öffnet bald</Button> {:else} - <Button href="https://tickets.fsmpi.rwth-aachen.de/fsmpi/eswe2023/" target="_blank" size="xl">Zur Anmeldung</Button> + <Button href={data.esweLink} target="_blank" size="xl">Zur Anmeldung</Button> {/if} </div> +{:else} +<!--eswe already running or in past or no start/end date set--> +<!--TODO--> +<P>Tut uns leid, aber das Erstsemesterwochenende ist leider schon vorbei.</P> +{/if} +<Heading tag="h2" customSize="text-3xl font-bold" class="mb-3 mt-6">Eindrücke aus Vorjahren</Heading> -<Heading tag="h2" customSize="text-3xl font-bold" class="mb-3 mt-6">Eindrücke aus 2022</Heading> - +<!-- TODO diesen Text anpassen, muss ja nicht immer Hohenfried sein --> <P class="mb-3">Voriges Jahr waren wir auch im Landhaus Hohenfried, hier ein paar Eindrücke:</P> -<Gallery items={images} class="gap-4 grid-cols-2 md:grid-cols-3" let:item> - <div on:click={()=>openModal(item.src)} aria-haspopup="dialog" role="button" tabindex={0} on:keydown={e=>e.key==="Enter"&&openModal(item.src)}> - <img src={item.src} alt="" class="h-auto max-w-full rounded-lg" /> - </div> +<!-- images array is just a dummy, item is not an image property object but the index within the gallery --> +<Gallery items={Array.from({length: images.length}, (_, i)=>i)} class="gap-4 grid-cols-2 md:grid-cols-3" let:item={index}> + <button on:click={()=>openModal(index)}> + <Image src={images[index]} class="h-auto max-w-full rounded-lg" loading="lazy" sizes="(min-width: 768px) 33vw, min(50vw, 363px)" autosize/> + </button> </Gallery> <Modal bind:open={showModal} outsideclose dismissable={false} size="lg"> <!-- not dismissable bc missing z-index --> - <Carousel {images} let:Controls let:Indicators bind:index class="h-auto sm:h-auto xl:h-auto 2xl:h-auto"> + <Carousel images={Array.from({length: images.length}, (_, i)=>i)} let:Controls let:Indicators bind:index class="h-auto sm:h-auto xl:h-auto 2xl:h-auto" transition={null}> <Controls /> <Indicators /> <svelte:fragment slot="slide"> - {#key images[index].src} - <img src={images[index].src} alt="" /> + {#key images[index].identifier} + <Image src={images[index]} sizes="min(100vw, 856px)" /> {/key} </svelte:fragment> </Carousel> </Modal> {:else} -<P>The Freshers' Weekend is an event for bachelor freshers only that will be held completely in German. For more information look at the German version.</P> +<P>{$LL.ESWE.OnlyAvailableInGerman()}</P> {/if} diff --git a/src/routes/(non-admin)/flyer/+page.server.ts b/src/routes/(non-admin)/flyer/+page.server.ts index 234a49d8ac3e5a16772bd18dd7684a3da65b10a1..53387befeaf56e4ca2e93f2a74e12168e3511ebb 100644 --- a/src/routes/(non-admin)/flyer/+page.server.ts +++ b/src/routes/(non-admin)/flyer/+page.server.ts @@ -1,6 +1,9 @@ -import flyer from "../../../../static/flyer/data.json"; +import { getImages } from "$lib/server/images"; import type { PageServerLoad } from "./$types"; export const load: PageServerLoad = async ()=>{ - return {flyer}; + const flyer = await getImages<string>("flyer"); + return { + flyer, + }; }; diff --git a/src/routes/(non-admin)/flyer/+page.svelte b/src/routes/(non-admin)/flyer/+page.svelte index 7d7cd91b8b22ed0374372cf02859784126747c87..3701fe7d2c169d583022374db476db24f1a976a2 100644 --- a/src/routes/(non-admin)/flyer/+page.svelte +++ b/src/routes/(non-admin)/flyer/+page.svelte @@ -1,30 +1,35 @@ -<script> - import { Carousel, Thumbnails } from 'flowbite-svelte'; - import { quintOut } from 'svelte/easing'; - import { fade } from 'svelte/transition'; - import LL from '$lib/i18n/i18n-svelte'; - - /** @type {import('./$types').PageData} */ - export let data; - const slideTransition = (x) => fade(x, { duration: 700, easing: quintOut }); - let index = 0; -</script> - -<div class="max-w-3xl mx-auto space-y-4"> - <Carousel images={data.flyer.map(flyer=>({src: "/flyer/"+flyer+"_hq.jpg", alt: flyer}))} let:Controls let:Indicators bind:index class="h-120 sm:h-120 xl:h-120 2xl:h-120"> - <Controls /> - <Indicators /> - <a slot="slide" href="/flyer/{data.flyer[index]}.pdf" target="_blank" let:Slide> - <!--<Slide image={images[index]} class="object-contain w-full h-full" />--> - {#key data.flyer[index]} - <img src={"/flyer/" + data.flyer[index] + "_hq.jpg"} alt={data.flyer[index]} class="absolute block w-full h-full object-contain" transition:slideTransition /> - {/key} - </a> - </Carousel> - <Thumbnails images={data.flyer.map(flyer=>({src: "/flyer/" + flyer+"_lq.jpg", alt: flyer}))} bind:index class="max-h-20 h-auto dark:bg-gray-800" let:Thumbnail let:image let:selected> - <Thumbnail {...image} {selected} class="max-h-20 h-auto" /> - </Thumbnails> - <div class="rounded bg-gray-300 dark:bg-gray-700 dark:text-white p-2 my-2 text-center"> - {$LL.Flyer.Notice()} - </div> -</div> +<script lang="ts"> + import { Carousel, P, Thumbnails } from 'flowbite-svelte'; + import { quintOut } from 'svelte/easing'; + import { fade } from 'svelte/transition'; + import LL from '$lib/i18n/i18n'; + import Image from '$lib/components/Image.svelte'; + + export let data; + + const slideTransition = (x: Element) => fade(x, { duration: 700, easing: quintOut }); + let index = 0; +</script> + +<div class="max-w-3xl mx-auto space-y-4"> + {#if data.flyer.length > 0} + <Carousel images={data.flyer} slideDuration={0} let:Controls let:Indicators bind:index class="h-120 sm:h-120 xl:h-120 2xl:h-120"> + <Controls /> + <Indicators /> + <a slot="slide" href="/flyer/{data.flyer[index].custom}.pdf" target="_blank" let:Slide> + <!--<Slide image={images[index]} class="object-contain w-full h-full" />--> + {#key index} + <Image src={data.flyer[index]} sizes="min(768px, {Math.ceil(480 / data.flyer[index].height * data.flyer[index].width)}px)" class="absolute block w-full h-full object-contain" /> <!-- TODO add slideTransition --> + {/key} + </a> + </Carousel> + <Thumbnails images={data.flyer} bind:index class="max-h-20 h-auto dark:bg-gray-800" let:image let:selected> + <Image src={image} sizes="{Math.ceil(80 / image.height * image.width)}px" class="{selected ? "opacity-100" : "opacity-60"} max-h-20 h-auto object-contain" /> + </Thumbnails> + <div class="rounded bg-gray-300 dark:bg-gray-700 dark:text-white p-2 my-2 text-center"> + {$LL.Flyer.Notice()} + </div> + {:else} + <P class="text-center">Aktuell gibt es keine Flyer</P> <!-- TODO i18n --> + {/if} +</div> diff --git a/src/routes/(non-admin)/impressum/+page.svelte b/src/routes/(non-admin)/impressum/+page.svelte index bf852ec7ecefdd21bfbd60d82b76814edff8a153..2474eb4e702a997e6ffc29e8eedabc69e2289f0d 100644 --- a/src/routes/(non-admin)/impressum/+page.svelte +++ b/src/routes/(non-admin)/impressum/+page.svelte @@ -1,25 +1,27 @@ -<script> - import { Blockquote, Heading, A, P } from "flowbite-svelte"; - import LL from "$lib/i18n/i18n-svelte"; -</script> - -<Heading tag="h1" customSize="text-4xl font-bold" class="mb-4 text-center">{$LL.Legal.Headline()}</Heading> - -<Heading tag="h2" customSize="text-lg">{$LL.Legal.Address()}</Heading> -<Blockquote class="mb-4 px-3 py-1" border size="base" italic={false}> - Fachschaft Mathematik/Physik/Informatik<br> - Templergraben 55<br> - 52056 Aachen -</Blockquote> - -<P class="mb-4"> - {$LL.Legal.Phone()}: <A href="tel:+492418094506">+49 241 80-94506</A> -</P> - -<P class="mb-4"> - {$LL.Legal.EmailGeneral()}: <A href="mailto:fs@fsmpi.rwth-aachen.de">fs@fsmpi.rwth-aachen.de</A><br> - {$LL.Legal.EmailESA()}: <A href="mailto:esa@fsmpi.rwth-aachen.de">esa@fsmpi.rwth-aachen.de</A><br> - {$LL.Legal.Internet()}: <A href="https://esa.fsmpi.rwth-aachen.de/">https://esa.fsmpi.rwth-aachen.de/</A> -</P> - -<P class="mb-4">{$LL.Legal.LiabilityNote()}</P> +<script lang="ts"> + import { LL } from "$lib/i18n/i18n"; + import { A, Blockquote, Heading, P } from "flowbite-svelte"; + + +</script> + +<Heading tag="h1" customSize="text-4xl font-bold" class="mb-4 text-center">{$LL.Legal.Headline()}</Heading> + +<Heading tag="h2" customSize="text-lg">{$LL.Legal.Address()}</Heading> +<Blockquote class="mb-4 px-3 py-1" border size="base" italic={false}> + Fachschaft Mathematik/Physik/Informatik<br> + Templergraben 55<br> + 52056 Aachen +</Blockquote> + +<P class="mb-4"> + {$LL.Legal.Phone()}: <A href="tel:+492418094506">+49 241 80-94506</A> +</P> + +<P class="mb-4"> + {$LL.Legal.EmailGeneral()}: <A href="mailto:fs@fsmpi.rwth-aachen.de">fs@fsmpi.rwth-aachen.de</A><br> + {$LL.Legal.EmailESA()}: <A href="mailto:esa@fsmpi.rwth-aachen.de">esa@fsmpi.rwth-aachen.de</A><br> + {$LL.Legal.Internet()}: <A href="https://esa.fsmpi.rwth-aachen.de/">https://esa.fsmpi.rwth-aachen.de/</A> +</P> + +<P class="mb-4">{$LL.Legal.LiabilityNote()}</P> diff --git a/src/routes/(non-admin)/information/+page.server.ts b/src/routes/(non-admin)/information/+page.server.ts new file mode 100644 index 0000000000000000000000000000000000000000..40860ea493baa6f35def3a78f14fe63809473f32 --- /dev/null +++ b/src/routes/(non-admin)/information/+page.server.ts @@ -0,0 +1,11 @@ +import { Schedule } from "$lib/server/database/entities/Schedule.entity"; + +export async function load(){ + const schedules = await Schedule.getAll(); + return { + schedules: Object.fromEntries(schedules.map(s=>[s.studyProgram.id, { + studyProgramName: s.studyProgram.name, + scheduleId: s.id, + }])), + }; +} diff --git a/src/routes/(non-admin)/information/+page.svelte b/src/routes/(non-admin)/information/+page.svelte index b949d1c6f2906dae960363188afc3255ba4e4007..30b1e847bfe308486c6be8d594bda6fe0536dd05 100644 --- a/src/routes/(non-admin)/information/+page.svelte +++ b/src/routes/(non-admin)/information/+page.svelte @@ -1,29 +1,31 @@ -<script lang="ts"> - import { A, Button, Heading, Label, Li, List, Select } from 'flowbite-svelte'; - import { LL, locale } from "$lib/i18n/i18n-svelte"; - import type { Translation } from '$lib/i18n/i18n-types.js'; - - export let data; - const studyPrograms = Object.keys($LL.Information.StudyPrograms) as (keyof Translation["Information"]["StudyPrograms"])[]; - $: studyProgram = $locale==="de"?studyPrograms[0]:studyPrograms[1]; -</script> - -<Heading tag="h2" customSize="text-3xl font-bold" class="mb-6">{$LL.Information.Schedule()}</Heading> -<!-- -<Label class="mb-4"> - {$LL.Information.StudyProgram()} - <Select items={studyPrograms.map(sp=>({name: $LL.Information.StudyPrograms[sp](), value: sp}))} bind:value={studyProgram} /> -</Label> ---> - -<div> - <img src="/stundenplaene/stundenplan-{studyProgram}.png" alt={$LL.Information.ScheduleAlt({semester: data.semester, studyProgram: $LL.Information.StudyPrograms[studyProgram]()})} class="w-full mb-2 block dark:hidden" /> - <img src="/stundenplaene/stundenplan-{studyProgram}-dark.png" alt={$LL.Information.ScheduleAlt({semester: data.semester, studyProgram: $LL.Information.StudyPrograms[studyProgram]()})} class="w-full mb-2 dark:block hidden" /> - <div class="flex justify-end mb-4"><Button href="/stundenplaene/stundenplan-{studyProgram}.pdf" download={$LL.Information.DownloadFilename({semester: data.semester, studyProgram: $LL.Information.StudyPrograms[studyProgram]()})}>{$LL.Information.Download()}</Button></div> -</div> - -<Heading tag="h2" customSize="text-3xl font-bold" class="mb-6">{$LL.Information.Information()}</Heading> -<List tag="ul" class="mb-2 text-gray-900 dark:text-gray-300"> - <Li><A href="/es-info/kurz.pdf" download={$LL.Information.ShortDownloadFilename({semester: data.semester})}>{$LL.Information.Short()}</A></Li> - <Li><A href="/es-info/lang.pdf" download={$LL.Information.LongDownloadFilename({semester: data.semester})}>{$LL.Information.Long()}</A></Li> -</List> +<script lang="ts"> + import { A, Button, Heading, Label, Li, List, P, Select } from 'flowbite-svelte'; + import { LL, locale } from "$lib/i18n/i18n"; + + export let data; + let selectedStudyProgram = Object.keys(data.schedules)[0]; + $: studyProgramName = data.schedules[selectedStudyProgram]?.studyProgramName[$locale]; +</script> + +<Heading tag="h2" customSize="text-3xl font-bold" class="mb-6">{$LL.Information.Schedule()}</Heading> + +<Label class="mb-4"> + {$LL.Information.StudyProgram()} + <Select items={Object.entries(data.schedules).map(([id, schedule])=>({name: schedule.studyProgramName[$locale], value: id}))} bind:value={selectedStudyProgram} /> +</Label> + +{#if selectedStudyProgram} +<div> + <img src="/stundenplaene/{data.schedules[selectedStudyProgram].scheduleId}/{$locale}.dark.svg" class="w-full mb-2 hidden dark:block" alt={$LL.Information.ScheduleAlt({semester: data.semester, studyProgram: studyProgramName})} /> + <img src="/stundenplaene/{data.schedules[selectedStudyProgram].scheduleId}/{$locale}.light.svg" class="w-full mb-2 block dark:hidden" alt={$LL.Information.ScheduleAlt({semester: data.semester, studyProgram: studyProgramName})} /> + <div class="flex justify-end mb-4"><Button href="/stundenplaene/{data.schedules[selectedStudyProgram].scheduleId}/{$locale}.pdf" download={$LL.Information.DownloadFilename({semester: data.semester, studyProgram: studyProgramName})}>{$LL.Information.Download()}</Button></div> +</div> +{:else} +<P class="mb-4">Aktuell existieren keine Stundenpläne.</P> <!-- TODO i18n --> +{/if} + +<Heading tag="h2" customSize="text-3xl font-bold" class="mb-6">{$LL.Information.Information()}</Heading> +<List tag="ul" class="mb-2 text-gray-900 dark:text-gray-300"> + <Li><A href="/es-info/kurz.pdf" download={$LL.Information.ShortDownloadFilename({semester: data.semester})}>{$LL.Information.Short()}</A></Li> + <Li><A href="/es-info/lang.pdf" download={$LL.Information.LongDownloadFilename({semester: data.semester})}>{$LL.Information.Long()}</A></Li> +</List> diff --git a/src/routes/(non-admin)/login/+page.server.ts b/src/routes/(non-admin)/login/+page.server.ts new file mode 100644 index 0000000000000000000000000000000000000000..6baa8cf28d8b6b9f8cc38afe2ce09200b28f5946 --- /dev/null +++ b/src/routes/(non-admin)/login/+page.server.ts @@ -0,0 +1,13 @@ +import { REQUIRED_GROUP } from "$env/static/private"; +import { Config } from "$lib/server/database/entities/Config.entity.js"; +import { User } from "$lib/server/database/entities/User.entity.js"; +import { error, redirect } from "@sveltejs/kit"; + +export const load = async (event)=>{ + const session = await event.locals.auth(); + if(!session) redirect(303, "/"); + if(REQUIRED_GROUP && !session.user.groups.includes(REQUIRED_GROUP)) error(403, `Missing required group ${REQUIRED_GROUP}`); + await User.getOrCreate(session.user.userId, session.user.name); + if(Config.get()) redirect(303, "/admin"); + else redirect(303, "/admin/setup"); +} diff --git a/src/routes/(non-admin)/rabatte/+page.server.ts b/src/routes/(non-admin)/rabatte/+page.server.ts index 3a6654307dc01da0152d2e986eeccffa33f5d190..91753e4af1c875351374efd2923c131b30843342 100644 --- a/src/routes/(non-admin)/rabatte/+page.server.ts +++ b/src/routes/(non-admin)/rabatte/+page.server.ts @@ -1,10 +1,27 @@ -import { getDiscountTags, getDiscounts, getHolidays } from "$lib/server/db.js"; +import { Discount, Tag } from "$lib/server/database/entities/Discount.entity"; +import { getHolidays } from "feiertagejs"; import type { PageServerLoad } from "./$types"; +import fs from "node:fs/promises"; + +const leafletStyle = await fs.readFile("node_modules/leaflet/dist/leaflet.css", "utf-8"); + +let holidays = getCurrentHolidays(); +function getCurrentHolidays(){ + const currentYear = new Date().getFullYear(); + return [...getHolidays(currentYear, "NW"), ...getHolidays(currentYear + 1, "NW")].map(h => h.dateString); +} +setInterval(()=>{ + holidays = getCurrentHolidays(); +}, 1000 * 60 * 60 * 24 * 12); // update every 12 days +// 12 days is the max amount of days you can store in 32 signed int export const load: PageServerLoad = async () => { + const locations = await Discount.getAll(); + const allTags = await Tag.getAll(); return { - locations: await getDiscounts(), - allTags: Object.values(await getDiscountTags()), - holidays: await getHolidays(), + locations: locations.map(l=>({...l, tags: l.tags.map(t=>({...t}))})), + allTags: allTags.map(t=>({...t})), + holidays, + leafletStyle, }; }; diff --git a/src/routes/(non-admin)/rabatte/+page.svelte b/src/routes/(non-admin)/rabatte/+page.svelte index bac732b6f5ac8943b89292ee780df206cd5af8bc..e6f61bb4be689d4070e26bd1cd7a220ca98ad190 100644 --- a/src/routes/(non-admin)/rabatte/+page.svelte +++ b/src/routes/(non-admin)/rabatte/+page.svelte @@ -1,220 +1,227 @@ -<script lang="ts"> - import LeafletMap from "./assets/LeafletMap.svelte"; - import LocationList from "./assets/LocationList.svelte"; - import LocationFilter from "./assets/LocationFilter.svelte"; - import { isOpen } from "./assets/opening"; - import { onMount } from "svelte"; - import { scrollTo as scroll } from "./assets/scrolling"; - import OpeningStatus from "./assets/openingStatus"; - import { makeSearchable, matches } from "./assets/search"; - import { locale } from "$lib/i18n/i18n-svelte"; - - export let data; - - function getValidityStatus(startDate: string, endDate: string, currentDate: number): number{ - if(startDate && endDate){ - let started = Date.parse(startDate) <= currentDate; - let ended = (Date.parse(endDate) + 86_400_000) <= currentDate; - if(ended) return OpeningStatus.ENDED; - else if(started) return OpeningStatus.VALID; - else return OpeningStatus.STARTING_SOON; - }else if(startDate){ - if(Date.parse(startDate) <= currentDate) return OpeningStatus.VALID; - else return OpeningStatus.STARTING_SOON; - }else if(endDate){ - if((Date.parse(endDate) + 86_400_00) <= currentDate) return OpeningStatus.ENDED; - else return OpeningStatus.VALID; - } - return OpeningStatus.VALID; - } - const locations = data.locations - .map((location, i) => Object.assign( - location, - isOpen(data.holidays, location.open, new Date()), - {id: i}, - {status: getValidityStatus(location.start, location.end, Date.now())}, - )) - .sort((a,b)=>{ - if(a.status === b.status){ - switch(a.status){ - case OpeningStatus.VALID: - case OpeningStatus.STARTING_SOON: - if(a.start === b.start){ - return a.name.localeCompare(b.name); - }else{ - return a.start.localeCompare(b.start); - } - case OpeningStatus.ENDED: - return b.end.localeCompare(a.end); - default: // should never happen - console.warn(`Unknown OpeningStatus ${a.status}`); - return 0; - } - }else{ - return a.status - b.status; - } - }); - const allTags = data.allTags; - // TODO rerun filter again on locale change - $: searchTexts = Object.fromEntries(locations.map(location=>[location.id, makeSearchable(location.name, location.description[$locale], location.address)])); - let onlyOpen = false; - let search = ""; - let tags: typeof allTags = []; - const filter = (search: string, tags: typeof allTags, onlyOpen: boolean)=>(location: typeof locations[number])=>{ - if(onlyOpen && !location.isOpen && !location.openingSoon) return false; - if(search && !matches(searchTexts[location.id], search)) return false; - if(tags.some(tag=>!location.tags.includes(tag))) return false; - return true; - }; - - // belongs to code in onMount, but needed in scrollTo - let lastOffset = 0; - let completelyShown = true; - let completelyHidden = false; - let minOffsetY = 0; - - let showOnMap: (id: number)=>void; - let isScrolling = false; - let locationElements: {[k: number]: HTMLDivElement}; - function scrollTo(locationId: number, highlight=true){ - const element = locationElements[locationId]; - if(!element) return; - const y = element.offsetTop + (element.offsetParent as HTMLElement).offsetTop - mapContainer.clientHeight - 5; - isScrolling = true; - if(mapContainer.style.position === "absolute"){ - const currentY = mapContainer.getBoundingClientRect().top; - mapContainer.style.position = "fixed"; - mapContainer.style.top = "0"; - completelyHidden = false; - completelyShown = true; - mapContainer.animate([ - {top: currentY + "px"}, - {top: "0px"} - ], { - duration: 250, - iterations: 1, - easing: "ease-in-out" - }); - } - scroll(y, ()=>{ - mapContainer.style.position = "fixed"; - mapContainer.style.top = "0"; - lastOffset = window.scrollY; - isScrolling = false; - if(highlight){ - element.animate([ - {backgroundColor: "var(--card-bg-color)", transform: "scale(1)"}, - {backgroundColor: "var(--card-highlight-bg-color)", transform: "scale(1.02)"}, - {backgroundColor: "var(--card-bg-color)", transform: "scale(1)"} - ], { - duration: 400, - iterations: 1, - easing: "ease-in" - }); - } - }); - } - function focusLocationOnMap(locationId: number){ - showOnMap(locationId); - scrollTo(locationId, false); - } - - let mapContainer: HTMLDivElement; - onMount(()=>{ - const el = mapContainer; - minOffsetY = (el.offsetParent as HTMLElement).offsetTop; - - const observer = new IntersectionObserver(([e])=>{ - if(isScrolling) return; - completelyShown = e.intersectionRatio===1; - completelyHidden = e.intersectionRatio===0; - if(e.intersectionRatio===0){ - // completely hidden - }else if(e.intersectionRatio===1){ - // completely shown - if(window.scrollY <= minOffsetY){ - el.style.position = "absolute"; - el.style.removeProperty("top"); - }else{ - el.style.position = "fixed"; - el.style.top = "0"; - } - } - }, {threshold: [0, 1]}); - - lastOffset = window.scrollY; - observer.observe(el); - const onScroll = ()=>{ - if(isScrolling) return; - let currentOffset = window.scrollY; - if(currentOffset <= minOffsetY){ - el.style.position = "absolute"; - el.style.removeProperty("top"); - }else{ - if(currentOffset > lastOffset){ - // scroll down - if(completelyShown && el.style.position === "fixed"){ - el.style.position = "absolute"; - el.style.top = (lastOffset-minOffsetY) + "px"; - } - }else{ - // scroll up - if(completelyHidden){ - el.style.top = (lastOffset-el.clientHeight-minOffsetY) + "px"; - } - } - } - lastOffset = currentOffset; - }; - const onResize = ()=>{ - minOffsetY = (el.offsetParent as HTMLElement).offsetTop; - }; - window.addEventListener("scroll", onScroll); - window.addEventListener("resize", onResize); - return ()=>{ - observer.disconnect(); - window.removeEventListener("scroll", onScroll); - window.removeEventListener("resize", onResize); - }; - }); -</script> - -<style> - :global(body) { - --map-height: 40vh; - } - .map-container { - left: 0; - right: 0; - position: absolute; - height: var(--map-height); - } - .list-container { - padding-top: calc(var(--map-height) + 1rem); - --card-bg-color: rgb(255 255 255 / var(--tw-bg-opacity)); - --card-highlight-bg-color: rgb(240 240 240 / var(--tw-bg-opacity)); - } - :global(.dark) .list-container { - --card-bg-color: rgb(31 41 55 / var(--tw-bg-opacity)); - --card-highlight-bg-color: rgb(41 51 65 / var(--tw-bg-opacity)); - } - - /* leaflet doesn't have a dark mode on its own, but this is fairly good */ - :global(.dark .leaflet-layer), - :global(.dark .leaflet-control-zoom-in), - :global(.dark .leaflet-control-zoom-out), - :global(.dark .leaflet-control-attribution) { - filter: invert(100%) hue-rotate(170deg) brightness(95%) contrast(90%); - } - :global(.dark .leaflet-container) { - background-color: #333; - } -</style> - -<div class="map-container z-40 mx-auto max-w-6xl px-4" bind:this={mapContainer}> - <LeafletMap {locations} {scrollTo} bind:showOnMap holidays={data.holidays} /> -</div> -<div class="list-container"> - <LocationFilter {allTags} bind:onlyOpen bind:search bind:tags /> - <LocationList {locations} holidays={data.holidays} filter={filter(search, tags, onlyOpen)} showOnMap={focusLocationOnMap} bind:locationElements bind:searchTags={tags} /> -</div> +<script lang="ts"> + import LeafletMap from "./assets/LeafletMap.svelte"; + import LocationList from "./assets/LocationList.svelte"; + import LocationFilter from "./assets/LocationFilter.svelte"; + import { isOpen } from "./assets/opening"; + import { onMount } from "svelte"; + import { scrollTo as scroll } from "./assets/scrolling"; + import OpeningStatus from "./assets/openingStatus"; + import { makeSearchable, matches } from "./assets/search"; + import { locale } from "$lib/i18n/i18n"; + + export let data; + + function getValidityStatus(startDate: string, endDate: string, currentDate: number): number{ + if(startDate && endDate){ + let started = Date.parse(startDate) <= currentDate; + let ended = (Date.parse(endDate) + 86_400_000) <= currentDate; + if(ended) return OpeningStatus.ENDED; + else if(started) return OpeningStatus.VALID; + else return OpeningStatus.STARTING_SOON; + }else if(startDate){ + if(Date.parse(startDate) <= currentDate) return OpeningStatus.VALID; + else return OpeningStatus.STARTING_SOON; + }else if(endDate){ + if((Date.parse(endDate) + 86_400_00) <= currentDate) return OpeningStatus.ENDED; + else return OpeningStatus.VALID; + } + return OpeningStatus.VALID; + } + const locations = data.locations + .map(location => Object.assign( + location, + isOpen(data.holidays, location.openingHours, new Date()), + {status: getValidityStatus(location.startDate, location.endDate, Date.now())}, + )) + .sort((a,b)=>{ + if(a.status === b.status){ + switch(a.status){ + case OpeningStatus.VALID: + case OpeningStatus.STARTING_SOON: + if(a.startDate === b.startDate){ + return a.title.localeCompare(b.title); + }else{ + return a.title.localeCompare(b.title); + } + case OpeningStatus.ENDED: + return b.endDate.localeCompare(a.endDate); + default: // should never happen + console.warn(`Unknown OpeningStatus ${a.status}`); + return 0; + } + }else{ + return a.status - b.status; + } + }); + const allTags = data.allTags; + // TODO rerun filter again on locale change + $: searchTexts = Object.fromEntries(locations.map(location=>[location.id, makeSearchable(location.title, location.description[$locale], location.address)])); + let onlyOpen = false; + let search = ""; + let tags: number[] = []; + const filter = (search: string, tags: number[], onlyOpen: boolean)=>(location: typeof locations[number])=>{ + if(onlyOpen && !location.isOpen && !location.openingSoon) return false; + if(search && !matches(searchTexts[location.id], search)) return false; + if(tags.some(tagId=>!location.tags.some(t=>t.id===tagId))) return false; + return true; + }; + + // belongs to code in onMount, but needed in scrollTo + let lastOffset = 0; + let completelyShown = true; + let completelyHidden = false; + let minOffsetY = 0; + + let showOnMap: (id: number)=>void; + let isScrolling = false; + let locationElements: {[k: number]: HTMLDivElement}; + function scrollTo(locationId: number, highlight=true){ + const element = locationElements[locationId]; + if(!element) return; + const y = element.offsetTop + (element.offsetParent as HTMLElement).offsetTop - mapContainer.clientHeight - 5; + isScrolling = true; + if(mapContainer.style.position === "absolute"){ + // only make position fixed at top if the map can be partially hidden + // tbh i think the formula is wrong, but it is good enough + if(document.body.scrollHeight - document.body.clientHeight > y){ + const currentY = mapContainer.getBoundingClientRect().top; + mapContainer.style.position = "fixed"; + mapContainer.style.top = "0"; + completelyHidden = false; + completelyShown = true; + mapContainer.animate([ + {top: currentY + "px"}, + {top: "0px"} + ], { + duration: 250, + iterations: 1, + easing: "ease-in-out" + }); + } + } + scroll(y, ()=>{ + mapContainer.style.position = "fixed"; + mapContainer.style.top = "0"; + lastOffset = window.scrollY; + isScrolling = false; + if(highlight){ + element.animate([ + {backgroundColor: "var(--card-bg-color)", transform: "scale(1)"}, + {backgroundColor: "var(--card-highlight-bg-color)", transform: "scale(1.02)"}, + {backgroundColor: "var(--card-bg-color)", transform: "scale(1)"} + ], { + duration: 400, + iterations: 1, + easing: "ease-in" + }); + } + }); + } + function focusLocationOnMap(locationId: number){ + showOnMap(locationId); + scrollTo(locationId, false); + } + + let mapContainer: HTMLDivElement; + onMount(()=>{ + const styleElement = document.head.appendChild(document.createElement("style")); + styleElement.textContent = data.leafletStyle; + + const el = mapContainer; + minOffsetY = (el.offsetParent as HTMLElement).offsetTop; + + const observer = new IntersectionObserver(([e])=>{ + if(isScrolling) return; + completelyShown = e.intersectionRatio===1; + completelyHidden = e.intersectionRatio===0; + if(e.intersectionRatio===0){ + // completely hidden + }else if(e.intersectionRatio===1){ + // completely shown + if(window.scrollY <= minOffsetY){ + el.style.position = "absolute"; + el.style.removeProperty("top"); + }else{ + el.style.position = "fixed"; + el.style.top = "0"; + } + } + }, {threshold: [0, 1]}); + + lastOffset = window.scrollY; + observer.observe(el); + const onScroll = ()=>{ + if(isScrolling) return; + let currentOffset = window.scrollY; + if(currentOffset <= minOffsetY){ + el.style.position = "absolute"; + el.style.removeProperty("top"); + }else{ + if(currentOffset > lastOffset){ + // scroll down + if(completelyShown && el.style.position === "fixed"){ + el.style.position = "absolute"; + el.style.top = (lastOffset-minOffsetY) + "px"; + } + }else{ + // scroll up + if(completelyHidden){ + el.style.top = (lastOffset-el.clientHeight-minOffsetY) + "px"; + } + } + } + lastOffset = currentOffset; + }; + const onResize = ()=>{ + minOffsetY = (el.offsetParent as HTMLElement).offsetTop; + }; + window.addEventListener("scroll", onScroll); + window.addEventListener("resize", onResize); + return ()=>{ + observer.disconnect(); + window.removeEventListener("scroll", onScroll); + window.removeEventListener("resize", onResize); + styleElement.remove(); + }; + }); +</script> + +<style> + :global(body) { + --map-height: 40vh; + } + .map-container { + left: 0; + right: 0; + position: absolute; + height: var(--map-height); + } + .list-container { + padding-top: calc(var(--map-height) + 1rem); + --card-bg-color: rgb(255 255 255 / var(--tw-bg-opacity)); + --card-highlight-bg-color: rgb(240 240 240 / var(--tw-bg-opacity)); + } + :global(.dark) .list-container { + --card-bg-color: rgb(31 41 55 / var(--tw-bg-opacity)); + --card-highlight-bg-color: rgb(41 51 65 / var(--tw-bg-opacity)); + } + + /* leaflet doesn't have a dark mode on its own, but this is fairly good */ + :global(.dark .leaflet-layer), + :global(.dark .leaflet-control-zoom-in), + :global(.dark .leaflet-control-zoom-out), + :global(.dark .leaflet-control-attribution) { + filter: invert(100%) hue-rotate(170deg) brightness(95%) contrast(90%); + } + :global(.dark .leaflet-container) { + background-color: #333; + } +</style> + +<div class="map-container z-40 mx-auto max-w-6xl px-4" bind:this={mapContainer}> + <LeafletMap {locations} {scrollTo} bind:showOnMap holidays={data.holidays} /> +</div> +<div class="list-container"> + <LocationFilter {allTags} bind:onlyOpen bind:search bind:tags /> + <LocationList {locations} holidays={data.holidays} filter={filter(search, tags, onlyOpen)} showOnMap={focusLocationOnMap} bind:locationElements bind:searchTags={tags} /> +</div> diff --git a/src/routes/(non-admin)/rabatte/assets/LeafletMap.svelte b/src/routes/(non-admin)/rabatte/assets/LeafletMap.svelte index 6a6cf4baef2e70b70d8d849a054ef926319ced8d..e0c5b23fb5533527f6658acd023aeaea7bd10374 100644 --- a/src/routes/(non-admin)/rabatte/assets/LeafletMap.svelte +++ b/src/routes/(non-admin)/rabatte/assets/LeafletMap.svelte @@ -1,112 +1,117 @@ -<script lang="ts"> - import { onMount } from 'svelte'; - import { browser } from '$app/environment'; - import { LL, locale } from "$lib/i18n/i18n-svelte"; - import { getOpeningHoursForNextDays, formatOpeningHoursAsTable } from './opening'; - import type { Location } from '$lib/server/discounts'; - import type { Unsubscriber } from 'svelte/store'; - import type { Map, Marker } from 'leaflet'; - let leaflet: typeof import("leaflet"); - - export let - locations: (Location&{id: number, closingSoon: boolean, openingSoon: boolean, isOpen: boolean})[], - scrollTo: (id: number)=>void, - holidays: string[]; - - let map: Map; - let mapElement: HTMLDivElement; - let markers: {[k: number]: Marker} = {}; - export const showOnMap = (id: number)=>{ - if(!leaflet) return; - const location = locations.find(location=>location.id === id); - if(!location) return; - markers[id].openPopup(); - // center map on marker, move it a bit down to make sure the popup is visible - map.setView([location.location[0]+0.002, location.location[1]], 15); - }; - - onMount(async ()=>{ - if(!browser) return; - leaflet = await import('leaflet'); - // when built the default file paths don't exist anymore - leaflet.Marker.prototype.options.icon = new leaflet.Icon({ - iconUrl: "/map/marker-icon.png", - iconRetinaUrl: "/map/marker-icon-2x.png", - shadowUrl: "/map/marker-shadow.png", - iconSize: [25, 41], - iconAnchor: [12, 41], - popupAnchor: [1, -34], - tooltipAnchor: [16, -28], - shadowSize: [41, 41] - }); - map = leaflet.map(mapElement).setView([50.77764, 6.08237], 15); - // set marker to current location - let positionMarker: Marker; - const locationWatcher = navigator.geolocation.watchPosition(position=>{ - if(positionMarker){ - positionMarker.setLatLng(leaflet.latLng(position.coords.latitude, position.coords.longitude)); - }else{ - map.setView([position.coords.latitude, position.coords.longitude], 15); - // title doesn't automatically change because somehow that isn't really possible - maybe make new marker and remove old one?? - positionMarker = leaflet.marker([position.coords.latitude, position.coords.longitude], {title: $LL.Discounts.Map.CurrentLocation()}).addTo(map); - // change color of marker for easier distinction - // @ts-expect-error - positionMarker._icon.style.filter = "hue-rotate(130deg)"; - } - }); - leaflet.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { - maxZoom: 19, - attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>' - }).addTo(map); - // add markers - const unsubscribers: Unsubscriber[] = []; - locations.forEach(location=>{ - const openingHours = getOpeningHoursForNextDays(holidays, location.open, new Date(), 3, true); - const tableDiv = document.createElement("div"); - tableDiv.appendChild(formatOpeningHoursAsTable(openingHours, $locale)); - const popup = document.createElement("div"); - const a = Object.assign(document.createElement("a"), {href: "#", textContent: $LL.Discounts.Map.Details(), className: "block text-center"}); - a.addEventListener("click", e=>(e.preventDefault(),scrollTo(location.id))); - const getStatus = ()=>{ - // TODO: use text-* and dark:text-* classes - if(location.closingSoon) return {textContent: $LL.Discounts.Item.OpeningSoon(), className: "text-red-700"}; - else if(location.openingSoon) return {textContent: $LL.Discounts.Item.OpeningSoon(), className: "text-yellow-600"}; - else if(location.isOpen) return {textContent: $LL.Discounts.Item.Open(), className: "text-green-700"}; - else return {textContent: $LL.Discounts.Item.Closed(), className: "text-gray-700"}; - }; - const statusSpan = Object.assign(document.createElement("span"), getStatus()); - popup.appendChild(Object.assign(document.createElement("b"), {textContent: location.name})); - popup.appendChild(document.createElement("br")); - popup.appendChild(statusSpan); - popup.appendChild(tableDiv); - popup.appendChild(a); - const marker = leaflet.marker(location.location, {title: location.name}).addTo(map).bindPopup(popup); - markers[location.id] = marker; - // subscribe to language change - // both subscriptions are needed because subscriber runs before other store is updated - unsubscribers.push(locale.subscribe(lang=>{ - tableDiv.innerHTML = ""; - tableDiv.appendChild(formatOpeningHoursAsTable(openingHours, lang)); - })); - unsubscribers.push(LL.subscribe(LL=>{ - Object.assign(statusSpan, getStatus()); - a.textContent = LL.Discounts.Map.Details(); - })); - }); - // TODO fix onUnmount removing those, not happening bc returned in Promise - return ()=>{ - map?.remove(); - navigator.geolocation.clearWatch(locationWatcher); - for(const unsubscriber of unsubscribers) unsubscriber(); - }; - }); -</script> - -<style> - @import 'leaflet/dist/leaflet.css'; - div { - height: 100%; - } -</style> - -<div id="map" bind:this={mapElement}></div> +<script lang="ts"> + import { onDestroy, onMount } from 'svelte'; + import { browser } from '$app/environment'; + import { LL, locale } from "$lib/i18n/i18n"; + import { getOpeningHoursForNextDays, formatOpeningHoursAsTable } from './opening'; + import type { Discount as Location } from '$lib/server/database/entities/Discount.entity'; + import type { Unsubscriber } from 'svelte/store'; + import type { Map, Marker } from 'leaflet'; + let leaflet: typeof import("leaflet"); + + export let + locations: (Location&{closingSoon: boolean, openingSoon: boolean, isOpen: boolean})[], + scrollTo: (id: number)=>void, + holidays: string[]; + + let map: Map; + let mapElement: HTMLDivElement; + let markers: {[k: number]: Marker} = {}; + export const showOnMap = (id: number)=>{ + if(!leaflet) return; + const location = locations.find(location=>location.id === id); + if(!location) return; + markers[id].openPopup(); + // center map on marker, move it a bit down to make sure the popup is visible + map.setView([location.location[0]+0.002, location.location[1]], 15); + }; + + let locationWatcher: number; + let unsubscribers: Unsubscriber[] = []; + onMount(async ()=>{ + if(!browser) return; + leaflet = await import('leaflet'); + // when built the default file paths don't exist anymore + leaflet.Marker.prototype.options.icon = new leaflet.Icon({ + iconUrl: "/map/marker-icon.png", + iconRetinaUrl: "/map/marker-icon-2x.png", + shadowUrl: "/map/marker-shadow.png", + iconSize: [25, 41], + iconAnchor: [12, 41], + popupAnchor: [1, -34], + tooltipAnchor: [16, -28], + shadowSize: [41, 41] + }); + map = leaflet.map(mapElement).setView([50.77764, 6.08237], 15); + // set marker to current location + let positionMarker: Marker; + locationWatcher = navigator.geolocation.watchPosition(position=>{ + if(positionMarker){ + positionMarker.setLatLng(leaflet.latLng(position.coords.latitude, position.coords.longitude)); + }else{ + map.setView([position.coords.latitude, position.coords.longitude], 15); + // title doesn't automatically change because somehow that isn't really possible - maybe make new marker and remove old one?? + positionMarker = leaflet.marker([position.coords.latitude, position.coords.longitude], {title: $LL.Discounts.Map.CurrentLocation()}).addTo(map); + // change color of marker for easier distinction + // @ts-expect-error + positionMarker._icon.style.filter = "hue-rotate(130deg)"; + } + }); + leaflet.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { + maxZoom: 19, + attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>' + }).addTo(map); + // add markers + unsubscribers = []; + locations.forEach(location=>{ + const openingHours = getOpeningHoursForNextDays(holidays, location.openingHours, new Date(), 3, true); + const tableDiv = document.createElement("div"); + tableDiv.appendChild(formatOpeningHoursAsTable(openingHours, $locale)); + const popup = document.createElement("div"); + const a = Object.assign(document.createElement("a"), {href: `#${location.id}`, textContent: $LL.Discounts.Map.Details(), className: "block text-center"}); + a.addEventListener("click", e=>(e.preventDefault(),scrollTo(location.id))); + const getStatus = ()=>{ + // TODO: use text-* and dark:text-* classes + if(location.closingSoon) return {textContent: $LL.Discounts.Item.OpeningSoon(), className: "text-red-700"}; + else if(location.openingSoon) return {textContent: $LL.Discounts.Item.OpeningSoon(), className: "text-yellow-600"}; + else if(location.isOpen) return {textContent: $LL.Discounts.Item.Open(), className: "text-green-700"}; + else return {textContent: $LL.Discounts.Item.Closed(), className: "text-gray-700"}; + }; + const statusSpan = Object.assign(document.createElement("span"), getStatus()); + popup.appendChild(Object.assign(document.createElement("b"), {textContent: location.title})); + popup.appendChild(document.createElement("br")); + popup.appendChild(statusSpan); + popup.appendChild(tableDiv); + popup.appendChild(a); + const marker = leaflet.marker(location.location, {title: location.title}).addTo(map).bindPopup(popup); + markers[location.id] = marker; + // subscribe to language change + // both subscriptions are needed because subscriber runs before other store is updated + unsubscribers.push(locale.subscribe(lang=>{ + tableDiv.innerHTML = ""; + tableDiv.appendChild(formatOpeningHoursAsTable(openingHours, lang)); + })); + unsubscribers.push(LL.subscribe(LL=>{ + Object.assign(statusSpan, getStatus()); + a.textContent = LL.Discounts.Map.Details(); + })); + }); + if(window.location.hash.match(/^#\d+$/)){ + const locationId = parseInt(window.location.hash.slice(1)); + if(!locations.some(l=>l.id===locationId)) return; + scrollTo(locationId); + showOnMap(locationId); + } + }); + onDestroy(()=>{ + if(!browser) return; + map?.remove(); + navigator.geolocation.clearWatch(locationWatcher); + for(const unsubscriber of unsubscribers) unsubscriber(); + }); +</script> + +<style> + /*@import 'leaflet/dist/leaflet.css';*/ +</style> + +<div id="map" class="h-full" bind:this={mapElement}></div> diff --git a/src/routes/(non-admin)/rabatte/assets/Location.svelte b/src/routes/(non-admin)/rabatte/assets/Location.svelte index f4dcd3516bf06389bdb15c3cb4b392312c39fe46..414cb99bb4533f91e0c38d4d231be3b44b011c62 100644 --- a/src/routes/(non-admin)/rabatte/assets/Location.svelte +++ b/src/routes/(non-admin)/rabatte/assets/Location.svelte @@ -1,70 +1,70 @@ -<script lang="ts"> - import { Button, ButtonGroup, Card, Heading, P, Secondary, Table, TableBody, TableBodyCell, TableBodyRow } from "flowbite-svelte"; - import { formatOpeningHours, getOpeningHoursForNextDays } from "./opening"; - import { LL, locale } from "$lib/i18n/i18n-svelte"; - import OpeningStatus from "./openingStatus"; - import { Icon } from "flowbite-svelte-icons"; - import type { Location as LocationType, Tag } from "$lib/server/discounts"; - import type { MouseEventHandler } from "svelte/elements"; - - export let - location: LocationType&{status:number,closingSoon:boolean,isOpen:boolean,openingSoon:boolean}, - showOnMap: MouseEventHandler<HTMLAnchorElement>, - searchTags: Tag[], - locationElement: HTMLElement, - holidays: string[]; - - let element: HTMLDivElement; - $: locationElement = element?.parentElement as HTMLElement; - - $: open = formatOpeningHours(getOpeningHoursForNextDays(holidays, location.open, new Date(), 7, true), $locale); - let showAllOpeningHours = false; - - $: rangeFormatter = new Intl.DateTimeFormat($locale, {day: "2-digit", month: "long"}); -</script> - -<Card padding="sm" size="md"> - <Heading tag="h5" class={location.start || location.end ? "" : "mb-2"}>{location.name}</Heading> - {#if location.start || location.end} - <Secondary class="mb-2 -mt-1" color={location.status === OpeningStatus.VALID ? "text-gray-500 dark:text-gray-400" : "text-red-600 dark:text-red-500"}> - {#if location.start && location.end} - {rangeFormatter.formatRange(new Date(location.start), new Date(location.end))} - {/if} - </Secondary> - {/if} - <P whitespace="preline" class="mb-2">{location.description[$locale]}</P> - <div class="mb-2" bind:this={element}> - {#if location.closingSoon} - <P color="text-red-700 dark:text-red-500">{$LL.Discounts.Item.ClosingSoon()}</P> - {:else if location.openingSoon} - <P color="text-yellow-600 dark:text-yellow-400">{$LL.Discounts.Item.OpeningSoon()}</P> - {:else if location.isOpen} - <P color="text-green-700 dark:text-green-500">{$LL.Discounts.Item.Open()}</P> - {:else} - <P color="text-gray-700 dark:text-gray-500">{$LL.Discounts.Item.Closed()}</P> - {/if} - <Table class="mb-1"> - <TableBody> - {#each open as {dayOfWeek, openingHours, different}, i} - {#if showAllOpeningHours || i < 3} - <TableBodyRow> - <TableBodyCell tdClass="p-1">{dayOfWeek}{different?"*":""}:</TableBodyCell> - <TableBodyCell tdClass="p-1">{openingHours}</TableBodyCell> - </TableBodyRow> - {/if} - {/each} - </TableBody> - </Table> - <Button size="xs" color="alternative" on:click={e=>{e.preventDefault();showAllOpeningHours=!showAllOpeningHours;}}> - {showAllOpeningHours ? $LL.Discounts.Item.ShowLess() : $LL.Discounts.Item.ShowMore()} - </Button> - </div> - <a href="#" on:click={showOnMap} class="font-semibold text-gray-900 dark:text-white mb-3 mt-auto"><Icon name="map-location-outline" size="md" class="inline" /> {location.address}</a> - <ButtonGroup class="w-max"> - {#each location.tags as tag} - <!-- TODO highlight searched button --> - <!-- TODO add onClick listener again --> - <Button outline color="light" size="sm" on:click={()=>/*searchTags = searchTags.includes(tag)?searchTags.filter(t=>t!==tag):[...searchTags, tag]*/{}}>{tag[$locale]}</Button> - {/each} - </ButtonGroup> -</Card> +<script lang="ts"> + import { Button, ButtonGroup, Card, Heading, P, Secondary, Table, TableBody, TableBodyCell, TableBodyRow } from "flowbite-svelte"; + import { formatOpeningHours, getOpeningHoursForNextDays } from "./opening"; + import { LL, locale } from "$lib/i18n/i18n"; + import OpeningStatus from "./openingStatus"; + //import { Icon } from "flowbite-svelte-icons"; + import type { Discount as LocationType, Tag } from "$lib/server/database/entities/Discount.entity"; + import type { MouseEventHandler } from "svelte/elements"; + + export let + location: LocationType&{status:number,closingSoon:boolean,isOpen:boolean,openingSoon:boolean}, + showOnMap: MouseEventHandler<HTMLAnchorElement>, + searchTags: Tag[], + locationElement: HTMLElement, + holidays: string[]; + + let element: HTMLDivElement; + $: locationElement = element?.parentElement as HTMLElement; + + $: open = formatOpeningHours(getOpeningHoursForNextDays(holidays, location.openingHours, new Date(), 7, true), $locale); + let showAllOpeningHours = false; + + $: rangeFormatter = new Intl.DateTimeFormat($locale, {day: "2-digit", month: "long"}); +</script> + +<Card padding="sm" size="md"> + <Heading tag="h5" class={location.startDate || location.endDate ? "" : "mb-2"}>{location.title}</Heading> + {#if location.startDate || location.endDate} + <Secondary class="mb-2 -mt-1" color={location.status === OpeningStatus.VALID ? "text-gray-500 dark:text-gray-400" : "text-red-600 dark:text-red-500"}> + {#if location.startDate && location.endDate} + {rangeFormatter.formatRange(new Date(location.startDate), new Date(location.endDate))} + {/if} + </Secondary> + {/if} + <P whitespace="preline" class="mb-2">{location.description[$locale]}</P> + <div class="mb-2" bind:this={element}> + {#if location.closingSoon} + <P color="text-red-700 dark:text-red-500">{$LL.Discounts.Item.ClosingSoon()}</P> + {:else if location.openingSoon} + <P color="text-yellow-600 dark:text-yellow-400">{$LL.Discounts.Item.OpeningSoon()}</P> + {:else if location.isOpen} + <P color="text-green-700 dark:text-green-500">{$LL.Discounts.Item.Open()}</P> + {:else} + <P color="text-gray-700 dark:text-gray-500">{$LL.Discounts.Item.Closed()}</P> + {/if} + <Table class="mb-1"> + <TableBody> + {#each open as {dayOfWeek, openingHours, different}, i} + {#if showAllOpeningHours || i < 3} + <TableBodyRow> + <TableBodyCell tdClass="p-1">{dayOfWeek}{different?"*":""}:</TableBodyCell> + <TableBodyCell tdClass="p-1">{openingHours}</TableBodyCell> + </TableBodyRow> + {/if} + {/each} + </TableBody> + </Table> + <Button size="xs" color="alternative" on:click={e=>{e.preventDefault();showAllOpeningHours=!showAllOpeningHours;}}> + {showAllOpeningHours ? $LL.Discounts.Item.ShowLess() : $LL.Discounts.Item.ShowMore()} + </Button> + </div> + <a href="#{location.id}" on:click={showOnMap} class="font-semibold text-gray-900 dark:text-white mb-3 mt-auto"><!--<Icon name="map-location-outline" size="md" class="inline" />--> {location.address}</a> + <ButtonGroup class="w-max"> + {#each location.tags as tag} + <!-- TODO highlight searched button --> + <!-- TODO add onClick listener again --> + <Button outline color="light" size="sm" on:click={()=>/*searchTags = searchTags.includes(tag)?searchTags.filter(t=>t!==tag):[...searchTags, tag]*/{}}>{tag.name[$locale]}</Button> + {/each} + </ButtonGroup> +</Card> diff --git a/src/routes/(non-admin)/rabatte/assets/LocationFilter.svelte b/src/routes/(non-admin)/rabatte/assets/LocationFilter.svelte index 85a29d83ef2f33e66b51a90469b83e63a39584ee..8d57762db824e1a5c48c9fb727d3d4e9279b0bb3 100644 --- a/src/routes/(non-admin)/rabatte/assets/LocationFilter.svelte +++ b/src/routes/(non-admin)/rabatte/assets/LocationFilter.svelte @@ -1,14 +1,14 @@ -<script lang="ts"> - import { Checkbox, MultiSelect, Search } from "flowbite-svelte"; - import { LL, locale } from "$lib/i18n/i18n-svelte"; - import type { Tag } from "$lib/server/discounts"; - - export let onlyOpen: boolean, search: string, tags: Tag[], allTags: Tag[]; -</script> - -<div class="mb-4"> - <div class="w-80 inline-block mr-4"><Search placeholder={$LL.Discounts.Search.Search()} bind:value={search} size="md" /></div> - <Checkbox bind:checked={onlyOpen} inline class="mr-4">{$LL.Discounts.Search.OnlyOpen()}</Checkbox> - <!-- TODO i18n for tags --> - <div class="max-w-xl inline-block min-w-60 text-gray-800 dark:text-gray-400"><MultiSelect dropdownClass="z-40 bg-gray-100 dark:bg-gray-900" items={allTags.map(t=>({name:t[$locale],value:t}))} bind:value={tags} /></div> -</div> +<script lang="ts"> + import { Checkbox, MultiSelect, Search } from "flowbite-svelte"; + import { LL, locale } from "$lib/i18n/i18n"; + import type { Tag } from "$lib/server/database/entities/Discount.entity"; + + export let onlyOpen: boolean, search: string, tags: number[], allTags: Tag[]; +</script> + +<div class="mb-4"> + <div class="w-80 inline-block mr-4"><Search placeholder={$LL.Discounts.Search.Search()} bind:value={search} size="md" /></div> + <Checkbox bind:checked={onlyOpen} inline class="mr-4">{$LL.Discounts.Search.OnlyOpen()}</Checkbox> + <!-- TODO i18n for tags --> + <div class="max-w-xl inline-block min-w-60 text-gray-800 dark:text-gray-400"><MultiSelect dropdownClass="z-40 bg-gray-100 dark:bg-gray-900" items={allTags.map(t=>({name:t.name[$locale],value:t.id}))} bind:value={tags} /></div> +</div> diff --git a/src/routes/(non-admin)/rabatte/assets/LocationList.svelte b/src/routes/(non-admin)/rabatte/assets/LocationList.svelte index 63a869b7635da7e8cdbaac87566f95f2b3eb5629..c8b73aa805a6e573a15a10cd83623bfceb8e17dc 100644 --- a/src/routes/(non-admin)/rabatte/assets/LocationList.svelte +++ b/src/routes/(non-admin)/rabatte/assets/LocationList.svelte @@ -1,11 +1,12 @@ <script lang="ts"> import Location from './Location.svelte'; - import { LL } from "$lib/i18n/i18n-svelte"; + import { LL } from "$lib/i18n/i18n"; import { P } from 'flowbite-svelte'; - import type { Location as LocationType, Tag } from '$lib/server/discounts'; + import type { Discount, Tag } from '$lib/server/database/entities/Discount.entity'; export let - locations: (LocationType&{id:number})[], filter: (location: LocationType)=>boolean, + locations: (Discount&{isOpen:boolean,openingSoon:boolean,closingSoon:boolean,status:number})[], + filter: (location: typeof locations[number])=>boolean, showOnMap: (id: number)=>void, searchTags: Tag[], holidays: string[]; diff --git a/src/routes/(non-admin)/rabatte/assets/opening.ts b/src/routes/(non-admin)/rabatte/assets/opening.ts index 44e8fb8d4fde1665c718bd1840bff5d21067f2ce..6b96d9e7f655c1bc1060c640d660960491a5f925 100644 --- a/src/routes/(non-admin)/rabatte/assets/opening.ts +++ b/src/routes/(non-admin)/rabatte/assets/opening.ts @@ -1,5 +1,5 @@ -import { i18n } from "$lib/i18n/i18n-util"; -import type { Locales } from "$lib/i18n/i18n-types"; +import { L } from "$lib/i18n/i18n"; +import type { Locale } from "$lib/i18n/i18n"; import type { DateTimeFormat } from "intl"; type OpeningHours = [[number, number], [number, number]][]; @@ -75,9 +75,8 @@ export function getOpeningHoursForDay(holidays: string[], open: GeneralOpeningHo } } -const L = i18n(); type FormattedOpeningHours<K extends OpeningHoursForDay|OpeningHoursForDay[]> = K extends OpeningHoursForDay ? string : {dayOfWeek: string, openingHours: string, different: boolean}[]; -export function formatOpeningHours<K extends OpeningHoursForDay|OpeningHoursForDay[]>(open: K, locale: Locales): FormattedOpeningHours<K> { +export function formatOpeningHours<K extends OpeningHoursForDay|OpeningHoursForDay[]>(open: K, locale: Locale): FormattedOpeningHours<K> { if(Array.isArray(open)) { const days = L[locale].Discounts.Item.Weekdays; return (open as any).map((day: OpeningHoursForDay) => { @@ -101,12 +100,12 @@ export function formatOpeningHours<K extends OpeningHoursForDay|OpeningHoursForD const startFormatters: { [k: string]: DateTimeFormat } = {}; const endFormatters: { [k: string]: DateTimeFormat } = {}; -function getStartFormatter(locale: Locales){ +function getStartFormatter(locale: Locale){ const f = startFormatters[locale]; if(f) return f; return startFormatters[locale] = new Intl.DateTimeFormat(locale, { hour: "2-digit", minute: "2-digit" }); } -function getEndFormatter(locale: Locales) { +function getEndFormatter(locale: Locale) { const f = endFormatters[locale]; if(f) return f; let formatter = new Intl.DateTimeFormat(locale, { hour: "2-digit", minute: "2-digit" }); @@ -114,7 +113,7 @@ function getEndFormatter(locale: Locales) { return endFormatters[locale] = formatter; } -export function formatOpeningHoursAsTable(open: OpeningHoursForDay[], locale: Locales): HTMLTableElement { +export function formatOpeningHoursAsTable(open: OpeningHoursForDay[], locale: Locale): HTMLTableElement { const formatted = formatOpeningHours(open, locale); const table = Object.assign(document.createElement("table"), { style: "border-spacing: 0" }); formatted.forEach(({ dayOfWeek, openingHours, different }) => { diff --git a/src/routes/(non-admin)/rallye/[id=uuid]/+page.server.ts b/src/routes/(non-admin)/rallye/[id=uuid]/+page.server.ts new file mode 100644 index 0000000000000000000000000000000000000000..60d3cacf168e11eda3a0c71d1c0eca91bad748cd --- /dev/null +++ b/src/routes/(non-admin)/rallye/[id=uuid]/+page.server.ts @@ -0,0 +1,40 @@ +import { RallyStation } from '$lib/server/database/entities/RallyStation.entity'; +import { error } from '@sveltejs/kit'; +import type { PageServerLoad } from './$types'; +import { Tutorial } from '$lib/server/database/entities/Tutorial.entity'; +import { RallyPoints } from '$lib/server/database/entities/RallyPoints.entity'; + +export const load = (async event => { + const id = event.params.id; + const rallyStation = await RallyStation.getById(id); + if(!rallyStation) error(404, "Rallye-Station nicht gefunden"); + const tutorials = await Tutorial.getAll(); + return { + rallyStation: {...rallyStation}, + tutorials: tutorials.map(t=>({id: t.id, name: t.name})).sort((a,b)=>a.name.localeCompare(b.name)), + }; +}) satisfies PageServerLoad; + +export const actions = { + setPoints: async (event) => { + const id = event.params.id; + const rallyStation = await RallyStation.getById(id); + if(!rallyStation) error(404, "Rallye-Station nicht gefunden"); + const data = await event.request.formData(); + const tutorialStr = data.get("tutorial"); + const pointsStr = data.get("points"); + const bribeStr = data.get("bribe"); + if(!tutorialStr || typeof tutorialStr !== "string") error(400, "Ungültige Tutorial ID"); + const tutorialId = Number(tutorialStr); + if(!Number.isInteger(tutorialId) || tutorialId < 1) error(400, "Ungültige Tutorial ID"); + const tutorial = await Tutorial.getById(tutorialId); + if(!tutorial) error(404, "Tutorial nicht gefunden"); + if(!pointsStr || typeof pointsStr !== "string") error(400, "Ungültige Punkte"); + const points = Number(pointsStr); + if(!Number.isInteger(points) || points < 0 || points > 10) error(400, "Ungültige Punkte"); + if(!bribeStr || typeof bribeStr !== "string") error(400, "Ungültige Bestechung"); + const bribe = Number(bribeStr); + if(!Number.isInteger(bribe) || bribe < 0 || bribe > 10) error(400, "Ungültige Bestechung"); + await RallyPoints.create({ tutorial: tutorial.id, rallyStation: id, points, bribe }); + }, +}; diff --git a/src/routes/(non-admin)/rallye/[id=uuid]/+page.svelte b/src/routes/(non-admin)/rallye/[id=uuid]/+page.svelte new file mode 100644 index 0000000000000000000000000000000000000000..66222062ce3ef868856e7e9227cc7f8e5e43b605 --- /dev/null +++ b/src/routes/(non-admin)/rallye/[id=uuid]/+page.svelte @@ -0,0 +1,49 @@ +<script lang="ts"> + import { enhance } from "$app/forms"; + import { addMessage } from "$lib/messages"; + import { Button, Heading, Input, Label, P, Select } from "flowbite-svelte"; + import MarkdownIt from "markdown-it"; + + let { data } = $props(); + + const md = new MarkdownIt({ + html: true, + linkify: true, + typographer: true, + breaks: true, + }); +</script> + +<Heading customSize="text-4xl font-bold" class="mb-6 text-center">{data.rallyStation.name}</Heading> + +<P class="mb-4">{@html md.render(data.rallyStation.description)}</P> + +<form method="post" action="?/setPoints" use:enhance={()=>({result, update})=>{ + if(result.type === "success"){ + addMessage({type: "success", text: "Punkte wurden gesetzt"}); + update(); + }else{ + addMessage({type: "error", text: "Fehler beim Setzen der Punkte"}); + console.error(result); + } +}}> + <div class="flex gap-2 mb-2"> + <Label class="w-full"> + Tutorium + <Select name="tutorial" required> + {#each data.tutorials as tutorial} + <option value={tutorial.id}>{tutorial.name}</option> + {/each} + </Select> + </Label> + <Label class="w-28"> + Punkte + <Input name="points" type="number" min="0" max="10" required /> + </Label> + <Label class="w-28"> + Bestechung + <Input name="bribe" type="number" min="0" max="10" required /> + </Label> + </div> + <Button type="submit">Punkte setzen</Button> +</form> diff --git a/src/routes/(non-admin)/tutor/+page.server.ts b/src/routes/(non-admin)/tutor/+page.server.ts new file mode 100644 index 0000000000000000000000000000000000000000..bb61ac86d4aeb9e5ba334b8d135c74f3c37c34d8 --- /dev/null +++ b/src/routes/(non-admin)/tutor/+page.server.ts @@ -0,0 +1,17 @@ +import { Config } from "$lib/server/database/entities/Config.entity"; +import { TutorTraining } from "$lib/server/database/entities/TutorTraining.entity"; +import type { PageServerLoad } from "./$types"; + +export const load: PageServerLoad = async () => { + const config = Config.get(); + const tutorTrainings = await TutorTraining.getAll(); + return { + fresherWeekStart: config?.fresherWeekStart, + fresherWeekEnd: config?.fresherWeekEnd, + registrationOpen: config?.tutorRegistrationOpen ?? false, + dates: tutorTrainings.filter(t=>!t.internal).map(t=>t.date), + semester: config?.currentSemester, + trainingsStart: config?.trainingsStart, + trainingsEnd: config?.trainingsEnd, + }; +}; diff --git a/src/routes/(non-admin)/tutor/+page.svelte b/src/routes/(non-admin)/tutor/+page.svelte new file mode 100644 index 0000000000000000000000000000000000000000..91661472983ae97e384a6dc2f2d60c106b4c90e0 --- /dev/null +++ b/src/routes/(non-admin)/tutor/+page.svelte @@ -0,0 +1,56 @@ +<script> + import TutorFaq from "$lib/components/TutorFAQ.svelte"; + import { Button, P, Heading, List, Li, A } from "flowbite-svelte"; + import { LL, locale } from "$lib/i18n/i18n"; + import LocalizedText from "$lib/components/LocalizedText.svelte"; + + export let data; + + $: dateFormatter = new Intl.DateTimeFormat($locale, {day: "2-digit", month: "long", year: "numeric", weekday: "long"}); +</script> + +<Heading tag="h1" customSize="text-4xl font-bold" class="mb-6 text-center">{$LL.Tutor.Headline()}</Heading> + +<P class="mb-3">{$LL.Tutor.Greeting()}</P> + +<P class="mb-6" whitespace="preline">{$LL.Tutor.Paragraph({semester: data.semester})}</P> + +<Heading tag="h4" class="mb-1">{$LL.Tutor.WhatTitle()}</Heading> +<P class="mb-6" whitespace="preline">{$LL.Tutor.WhatContent()}</P> + +<Heading tag="h4" class="mb-1">{$LL.Tutor.NeedTitle()}</Heading> +<P class="mb-6" whitespace="preline">{$LL.Tutor.NeedContent()}</P> + +<Heading tag="h4" class="mb-1">{$LL.Tutor.WhenTitle()}</Heading> +<P class="mb-1" whitespace="preline"> + {$LL.Tutor.WhenContent({fresherWeek: [new Date(data.fresherWeekStart), new Date(data.fresherWeekEnd)], start: data.trainingsStart, end: data.trainingsEnd})} +</P> +<List tag="ul" class="mb-6"> + {#each data.dates as date} + <Li class="text-gray-900 dark:text-white">{dateFormatter.format(new Date(date))}</Li> + {/each} + {#if data.dates.length === 0} + <Li class="text-gray-900 dark:text-white">{$LL.Tutor.NoDates()}</Li> + {/if} +</List> + +<P class="mb-6"> + <LocalizedText key="Tutor.FurtherQuestions"> + <A href="mailto:esa@fsmpi.rwth-aachen.de">esa@fsmpi.rwth-aachen.de</A> + </LocalizedText> +</P> + +<P class="mb-6 whitespace-pre-line">{$LL.Tutor.ClosingFormula()}</P> + +<div class="flex mb-6 justify-center"> + {#if data.registrationOpen} + <Button href="/tutor/register" size="xl"> + <svg aria-hidden="true" class="mr-2 -ml-1 w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10.293 3.293a1 1 0 011.414 0l6 6a1 1 0 010 1.414l-6 6a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-4.293-4.293a1 1 0 010-1.414z" clip-rule="evenodd"></path></svg> + {$LL.Tutor.RegistrationButton()} + </Button> + {:else} + <Button disabled size="xl">{$LL.Tutor.RegistrationClosed()}</Button> + {/if} +</div> + +<TutorFaq /> diff --git a/src/routes/(non-admin)/tutor/register/+page.server.ts b/src/routes/(non-admin)/tutor/register/+page.server.ts new file mode 100644 index 0000000000000000000000000000000000000000..1e2dd3ab1c0425cc0a27129fdde1bccb630d0fb6 --- /dev/null +++ b/src/routes/(non-admin)/tutor/register/+page.server.ts @@ -0,0 +1,147 @@ +import { error } from "@sveltejs/kit"; +import type { PageServerLoad } from "./$types.js"; +import { Tutor } from "$lib/server/database/entities/Tutor.entity.js"; +import { Config } from "$lib/server/database/entities/Config.entity.js"; +import { StudyProgram } from "$lib/server/database/entities/StudyProgram.entity.js"; +import { TutorTraining } from "$lib/server/database/entities/TutorTraining.entity.js"; +import Gender from "$lib/genders.js"; +import Degree from "$lib/degrees.js"; +import { sendTutorRegisteredMail } from "$lib/server/mail.js"; + +export const load: PageServerLoad = async ()=>{ + const config = Config.get(); + const studyPrograms = await StudyProgram.getAll(); + const missingTutorCounts = await StudyProgram.getMissingTutors(); + const tutorTrainings = await TutorTraining.getAll(); + return { + tutorTrainings: tutorTrainings.filter(t=>!t.internal).map(t=>({id: t.id, date: t.date, location: t.location, isFull: t.participants.length>=t.maxParticipants})), + registrationOpen: config.tutorRegistrationOpen, + shirtSizes: config.shirtSizes, + fresherWeekStart: config.fresherWeekStart, + studyPrograms: studyPrograms.map(s=>({id: s.id, name: s.name, hasWaitlist: missingTutorCounts[s.id] === null ? false : missingTutorCounts[s.id]! <= 0})), + }; +}; + +export const actions = { + default: async (request)=>{ + const config = Config.get(); + const data = await request.request.formData(); + const firstname = data.get("firstname"); + const lastname = data.get("lastname"); + const nickname = data.get("nickname") || ""; + const birthdayString = data.get("birthday"); + const email = data.get("email"); + const phone = data.get("phone"); + const shirtSize = data.get("shirt-size"); + const address = data.get("address"); + const gender = data.get("gender"); + const dietaryRestriction = data.get("dietary-restriction"); + const studyProgramId = Number(data.get("study-program")); + const degree = data.get("degree"); + const trainingString = data.get("training"); + const mentor = !!data.get("mentor"); + const coTutorWish = data.get("co-tutor") || ""; + if(!firstname || !lastname || !email || !phone || !shirtSize || !address || !gender || !degree || !birthdayString || !trainingString){ + throw error(400, "Bitte fülle alle Pflichtfelder aus"); + } + if(!Number.isInteger(studyProgramId) || studyProgramId < 0){ + throw error(422, "Ungültiger Studiengang"); + } + if(typeof email !== "string" || !(email.endsWith("@rwth-aachen.de") || email.endsWith(".rwth-aachen.de"))){ + throw error(422, "Ungültige E-Mail Adresse"); + } + if(typeof birthdayString !== "string" || !birthdayString.match(/^\d{4}-\d{2}-\d{2}$/)){ + throw error(422, "Ungültiger Geburtstag"); + } + const birthday = Date.parse(birthdayString); + if(isNaN(birthday)){ + throw error(422, "Geburtstag ungültig"); + } + if(typeof dietaryRestriction !== "string"){ // is allowed to be empty, but not null + throw error(422, "Ungültige Essenseinschränkung"); + } + const date = new Date(birthday); + date.setFullYear(date.getFullYear() + 18); + if(new Date(config.fresherWeekStart) < date){ + throw error(422, "Du musst mindestens 18 Jahre alt sein"); + } + if(typeof shirtSize !== "string" || !config.shirtSizes.includes(shirtSize)){ + throw error(422, "Ungültige T-Shirt Größe"); + } + if(typeof gender !== "string" || !(gender in Gender)){ + throw error(422, "Ungültiges Geschlecht"); + } + if(typeof degree !== "string" || !(degree in Degree)){ + throw error(422, "Ungültiger Abschluss"); + } + if(typeof firstname !== "string" || typeof lastname !== "string" || typeof nickname !== "string" || typeof phone !== "string" || typeof address !== "string" || typeof coTutorWish !== "string"){ + throw error(422, "Ungültige Eingabe"); + } + if(typeof trainingString !== "string"){ + throw error(422, "Ungültige Schulung"); + } + const trainingId = trainingString !== "-" ? Number(trainingString) : null; + if(trainingId !== null && (!Number.isInteger(trainingId) || trainingId < 0)){ + throw error(422, "Ungültige Schulung"); + } + const studyProgram = await StudyProgram.getById(studyProgramId); + if(!studyProgram){ + throw error(422, "Ungültiger Studiengang"); + } + const training = trainingId !== null ? await TutorTraining.getById(trainingId) : null; + if(trainingId !== null && !training){ + throw error(422, "Ungültige Schulung"); + } + const waitlist = ((await StudyProgram.getMissingTutors())[studyProgramId] || 1) <= 0; + const birthdate = new Date(birthday).toISOString().slice(0, 10); + const tutorId = await Tutor.create({ + firstname, + lastname, + nickname, + birthday: birthdate, + email, + phone, + shirtSize, + address, + gender: gender as keyof typeof Gender, + studyProgram: studyProgramId, + degree: degree as keyof typeof Degree, + training: trainingId, + mentor, + coTutorWish, + notes: "", + trained: false, + dietaryRestriction, + sentTrainingMail: false, + // TODO add waitlist + }); + sendTutorRegisteredMail({ // no await, doesnt need to block response + id: tutorId, + firstname, + lastname, + nickname, + birthday: birthdate, + email, + phone, + shirtSize, + address, + gender: gender as keyof typeof Gender, + studyProgram, + degree: degree as keyof typeof Degree, + training, + mentor, + coTutorWish, + notes: "", + trained: false, + dietaryRestriction, + sentTrainingMail: false, + }).catch(err=>{ + console.error(err); + }); + return { + success: true, + tutorId, + waitlist, + }; + }, +}; diff --git a/src/routes/(non-admin)/tutor/register/+page.svelte b/src/routes/(non-admin)/tutor/register/+page.svelte new file mode 100644 index 0000000000000000000000000000000000000000..aa45f9c2f02f581b1f9afc00ed2a30e7dd87eb25 --- /dev/null +++ b/src/routes/(non-admin)/tutor/register/+page.svelte @@ -0,0 +1,178 @@ +<script lang="ts"> + import { enhance } from "$app/forms"; + import { Select, Label, Button, Helper, Textarea, Checkbox, A, Alert, Heading, Input } from "flowbite-svelte"; + import { LL, locale } from "$lib/i18n/i18n"; + import Gender from "$lib/genders"; + import Degree from "$lib/degrees"; + import LocalizedText from "$lib/components/LocalizedText.svelte"; + import { addMessage } from "$lib/messages.js"; + + export let data; + + let birthdayError = false; + let ageError = false; + let emailError = -1; + + let success = false; + let error: {message: string}|undefined; + let waitlist: boolean; + + let studyProgram: number; + + function validateEmail(email: any) { + if(typeof email !== "string" || !(email.endsWith("@rwth-aachen.de") || email.endsWith(".rwth-aachen.de"))){ + emailError = 1; + }else{ + if(email.match(/^[a-z]{2}\d{6}@rwth-aachen\.de$/i)) emailError = 0; + else emailError = -1; + } + return emailError; + } + function validateBirthday(date: any){ + const birthday = Date.parse(date); + birthdayError = isNaN(birthday); + ageError = false; + if(!birthdayError){ + let date = new Date(birthday); + date.setFullYear(date.getFullYear()+18); + ageError = new Date(data.fresherWeekStart) < date; + } + return !birthdayError && !ageError; + } + + function isTrainingInFuture(trainingDate: string){ + const date = Date.parse(trainingDate); + const now = Date.now(); + return now + 1000 * 60 * 60 * 24 < date; // can't sign up the day before and later + } + + $: dateFormatter = new Intl.DateTimeFormat($locale, {day: "2-digit", month: "long", year: "numeric", weekday: "short"}); +</script> + +<Heading tag="h1" customSize="text-4xl font-bold" class="mb-6 text-center">{$LL.Tutor.SignUp.Headline()}</Heading> + +{#if data.registrationOpen} + {#if success} + {#if waitlist} + <!-- TODO i18n and proper message --> + <Alert border color="yellow" class="mb-6">Du wurdest zur Warteliste hinzugefügt</Alert> + {:else} + <Alert border color="green" class="mb-6">{$LL.Tutor.SignUp.SignedUpSuccessfully()}</Alert> + {/if} + {/if} + {#if error?.message} + <Alert border color="red" class="mb-6">{error.message}</Alert> + {/if} + + <form method="post" use:enhance={({formElement, formData, cancel})=>{ + if(!validateBirthday(formData.get("birthday")) || validateEmail(formData.get("email")) >= 0) { + addMessage({ type: "error", text: $LL.Tutor.SignUp.FormValidationErrors.InvalidForm() }); + return cancel(); + } + return ({result})=>{ + if(result.type === "success" && result.data?.success) { + waitlist = !!result.data.waitlist; + success = true; + error = undefined; + formElement.reset(); + window.scrollTo({top: 0, behavior: "smooth"}); + }else if(result.type === "error"){ + error = result.error; + window.scrollTo({top: 0, behavior: "smooth"}); + } + }; + }}> + <Label class="mb-2"> + {$LL.Tutor.SignUp.FirstName()} + <Input type="text" name="firstname" required /> + </Label> + <Label class="mb-2"> + {$LL.Tutor.SignUp.LastName()} + <Input type="text" name="lastname" required /> + </Label> + <Label class="mb-2"> + {$LL.Tutor.SignUp.NickName()} + <Input type="text" name="nickname" /> + </Label> + <Label class="mb-2"> + {$LL.Tutor.SignUp.BirthDate()} + <div> + <Input type="date" name="birthday" required on:change={(e)=>validateBirthday(e.target?.value)} /> + {#if birthdayError} + <Helper color="red">{$LL.Tutor.SignUp.InvalidDate()}</Helper> + {:else if ageError} + <Helper color="red">{$LL.Tutor.SignUp.NotOldEnough()}</Helper> + {/if} + </div> + </Label> + <Label class="mb-2"> + {$LL.Tutor.SignUp.Email()} + <Input type="email" name="email" required color={emailError>=0?"red":"base"} on:blur={e=>validateEmail(e.target?.value)} /> + {#if emailError>=0} + <Helper color="red">{$LL.Tutor.SignUp.FormValidationErrors.InvalidEmail[emailError]()}</Helper> + {/if} + </Label> + <Label class="mb-2"> + {$LL.Tutor.SignUp.Phone()} + <Input type="tel" name="phone" required /> + <Helper>{$LL.Tutor.SignUp.PhoneHelper()}</Helper> + </Label> + <Label class="mb-2"> + {$LL.Tutor.SignUp.Address()} + <Textarea name="address" required /> + </Label> + <Label class="mb-2"> + {$LL.Tutor.SignUp.Gender()} + <Select name="gender" required placeholder={$LL.Tutor.SignUp.PleaseChoose()} items={Object.entries(Gender).map(([value, name])=>({value, name: name[$locale]}))} /> + </Label> + <Label class="mb-2"> + {$LL.Tutor.SignUp.ShirtSize()} + <Select name="shirt-size" required placeholder={$LL.Tutor.SignUp.PleaseChoose()} items={data.shirtSizes.map(x=>({name:x,value:x}))} /> + </Label> + <Label class="mb-2"> + {$LL.Tutor.SignUp.DietaryRestriction()} + <Input type="text" name="dietary-restriction" /> + <Helper>{$LL.Tutor.SignUp.DietaryRestrictionHelper()}</Helper> + </Label> + <Label class="mb-2"> + {$LL.Tutor.SignUp.StudyProgram()} + <Select name="study-program" required placeholder={$LL.Tutor.SignUp.PleaseChoose()} items={data.studyPrograms.map(x=>({name: x.name[$locale], value: x.id}))} bind:value={studyProgram} /> + {#if data.studyPrograms.find(sp=>sp.id===studyProgram)?.hasWaitlist} + <Helper color="red">{$LL.Tutor.SignUp.StudyProgramWaitlist({studyProgram: data.studyPrograms.find(sp=>sp.id===studyProgram)?.name[$locale]??""})}</Helper> + {/if} + </Label> + <Label class="mb-2"> + {$LL.Tutor.SignUp.Degree()} + <Select name="degree" required placeholder={$LL.Tutor.SignUp.PleaseChoose()} items={Object.entries(Degree).map(([value, name])=>({value, name: name[$locale]}))} /> + <Helper>{$LL.Tutor.SignUp.DegreeHelper()}</Helper> + </Label> + <Label class="mb-2"> + {$LL.Tutor.SignUp.Training()} + <Select name="training" required placeholder={$LL.Tutor.SignUp.PleaseChoose()}> + {#each data.tutorTrainings as t} + <!--TODO i18n for "voll"--> + <option value={t.date} disabled={!isTrainingInFuture(t.date) || t.isFull}>{dateFormatter.format(new Date(t.date))}{t.isFull ? " (voll)" : ""}</option> + {/each} + <option value="-">{$LL.Tutor.SignUp.AlreadyTrained()}</option> + </Select> + </Label> + <Label class="mb-2"> + {$LL.Tutor.SignUp.CoTutorWish()} + <Input name="co-tutor" type="text" /> + <Helper>{$LL.Tutor.SignUp.CoTutorWishHelper()}</Helper> + </Label> + <Checkbox class="mb-2" name="mentor">{$LL.Tutor.SignUp.IsMentor()}</Checkbox> + <Checkbox class="mb-2" required> + <LocalizedText key="Tutor.SignUp.ReadPrivacyPolicy"> + <A class="mx-1" href="/datenschutz" target="_blank">{$LL.Tutor.SignUp.PrivacyPolicy()}</A> + </LocalizedText> + </Checkbox> + <Button type="submit" class="mb-2">{$LL.Tutor.SignUp.Submit()}</Button> + </form> +{:else} + <Alert border color="red"> + <LocalizedText key="Tutor.SignUp.SignUpClosed"> + <A href="mailto:esa@fsmpi.rwth-aachen.de">esa@fsmpi.rwth-aachen.de</A> + </LocalizedText> + </Alert> +{/if} diff --git a/src/routes/(non-admin)/upload/+page.server.ts b/src/routes/(non-admin)/upload/+page.server.ts new file mode 100644 index 0000000000000000000000000000000000000000..e457b94329eaf2cea6ff925cbb91094025367500 --- /dev/null +++ b/src/routes/(non-admin)/upload/+page.server.ts @@ -0,0 +1,24 @@ +import { error } from '@sveltejs/kit'; +import type { PageServerLoad } from './$types'; +import fs from 'node:fs/promises'; + +export const load = (async () => { + return {}; +}) satisfies PageServerLoad; + +export const actions = { + upload: async event => { + const data = await event.request.formData(); + const files = data.getAll('files'); + if(files.length === 0) error(400, 'No files uploaded'); + if(files.some(file=>!(file instanceof File) || !file.type.startsWith("image/"))) error(400, 'Invalid file uploaded'); + await fs.mkdir('static/uploads', { recursive: true }); + await Promise.all(files.map(async (fileObj: FormDataEntryValue) => { + const file = fileObj as File; + const id = Math.random().toString(36).substring(2); + const extension = file.name.split('.').pop(); + const fileContents = await file.arrayBuffer(); + return fs.writeFile(`static/uploads/${id}.${extension}`, Buffer.from(fileContents)); + })); + }, +}; diff --git a/src/routes/(non-admin)/upload/+page.svelte b/src/routes/(non-admin)/upload/+page.svelte new file mode 100644 index 0000000000000000000000000000000000000000..5687b55ff2bbfb7dc5ed4ec2bcb99514e5be19ce --- /dev/null +++ b/src/routes/(non-admin)/upload/+page.svelte @@ -0,0 +1,35 @@ +<script lang="ts"> + import { enhance } from '$app/forms'; + import { addMessage } from '$lib/messages'; + import { Heading, P, Fileupload, Button, Checkbox } from 'flowbite-svelte'; +</script> + +<!-- TODO i18n --> +<Heading tag="h1" customSize="text-4xl font-bold" class="mb-6 text-center">Momente teilen</Heading> + +<P class="mb-3"> + Lade hier Deine Fotos von der Erstiwoche hoch und hilf uns, die besten Momente festzuhalten. + Mit dem Upload stimmst Du zu, dass die Bilder von der Fachschaft für Öffentlichkeitsarbeit verwendet + und z.B. auf Instagram oder anderen Social Media-Plattformen veröffentlicht werden dürfen. +</P> +<P class="mb-3"> + Bitte lade nur Bilder hoch, die Du selbst gemacht hast und für deren Veröffentlichung Du und alle + erkennbaren Personen auf dem Bild einverstanden sind. +</P> +<P class="mb-3"> + Wir freuen uns auf Deine Impressionen! +</P> + +<form action="?/upload" method="post" enctype="multipart/form-data" use:enhance={()=>({result, update})=>{ + if(result.type === "success"){ + addMessage({ type: "success", text: "Deine Bilder wurden erfolgreich hochgeladen. Vielen Dank!" }); + update(); + }else{ + addMessage({ type: "error", text: "Beim Hochladen der Bilder ist ein Fehler aufgetreten. Bitte versuche es erneut." }); + console.error(result); + } +}}> + <Fileupload name="files" accept="image/*" multiple required class="mb-2" /> + <Checkbox class="mb-2" required>Ich bestätige, dass ich die Rechte an diesem Bild oder diesen Bildern besitze und dass alle erkennbaren Personen der Veröffentlichung zugestimmt haben. Ich erlaube der Fachschaft I/1, das Bild für Öffentlichkeitsarbeit zu verwenden und auf Social Media zu veröffentlichen.</Checkbox> + <Button type="submit">Hochladen</Button> +</form> diff --git a/src/routes/+error.svelte b/src/routes/+error.svelte deleted file mode 100644 index 159f2532e041a8e906ac628815a2f7d3824cdcb4..0000000000000000000000000000000000000000 --- a/src/routes/+error.svelte +++ /dev/null @@ -1,25 +0,0 @@ -<script> - import MainLayout from "$lib/components/MainLayout.svelte"; - import AdminLayout from "$lib/components/AdminLayout.svelte"; - import { page } from "$app/stores"; -</script> - -<svelte:head> - <title>Fehler {$page.status} - ESA I/1</title> -</svelte:head> - -{#if $page.url.pathname.startsWith("/admin/") || $page.url.pathname=="/admin"} - <AdminLayout> - <div class="text-center"> - <h2 class="text-3xl font-bold">{$page.status}</h2> - <p>{$page.error?.message}</p> - </div> - </AdminLayout> -{:else} - <MainLayout locale={$page.data.locale ?? "de"}> - <div class="text-center"> - <h2 class="text-3xl font-bold">{$page.status}</h2> - <p>{$page.error?.message}</p> - </div> - </MainLayout> -{/if} diff --git a/src/routes/+layout.server.ts b/src/routes/+layout.server.ts index a7c7e19191e60f8b829342047743d8418abc2fc1..a3c1bd3d151e23bc9f734addb50c8f4f2b76e688 100644 --- a/src/routes/+layout.server.ts +++ b/src/routes/+layout.server.ts @@ -1,10 +1,10 @@ -import type { Locales } from "$lib/i18n/i18n-types.js"; -import { getCurrentYearOfSemester } from "$lib/server/config.js"; +import { Config } from "$lib/server/database/entities/Config.entity"; -export const ssr = true; - -export const load = async (event) => { - const locale = event.locals.locale ?? "de" as Locales; - const semester = await getCurrentYearOfSemester(); - return { locale, semester }; -}; +export const load = (event)=>{ + const locale = event.locals.locale; + const semester = Config.get()?.currentSemester; + return { + locale, + semester, + }; +} diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index f77b8cf48b5d23e2523a22f579f8f28732d18154..8f27fc80a5e8d5df2e356e96467a38d006198e4a 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,16 +1,48 @@ <script lang="ts"> - import { browser } from "$app/environment"; - import { LL, locale } from "$lib/i18n/i18n-svelte"; - import type { Translation } from "$lib/i18n/i18n-types"; + import "../app.pcss"; import { page } from "$app/stores"; - import "../app.postcss"; + import LL, { type Translation } from "$lib/i18n/i18n"; + import MessageList from "$lib/components/MessageList.svelte"; - $: browser && document.querySelector('html')?.setAttribute("lang", $locale); - $: route = $page.route.id as keyof Translation["PageTitle"]; + let { children } = $props(); + + function unproxy<T>(proxy: T): T { + if(typeof proxy !== "object" || proxy === null) return proxy; + if(Array.isArray(proxy)) return proxy.map(unproxy) as unknown as T; + const target = {} as T; + for(const key in proxy){ + target[key] = unproxy(proxy[key]); + } + return target; + } + function reduce(accumulator: Record<string, unknown>, target: Record<string|number, unknown>, prefix: string, encountered: Set<unknown>){ + for(const key in target){ + const value = target[key]; + if(encountered.has(value)) continue; // in case of circular references + encountered.add(value); + accumulator[prefix + key] = value; + if(typeof value === "object" && value !== null && !Array.isArray(value)){ + accumulator[prefix + key] = value; + reduce(accumulator, value as Record<string|number, unknown>, prefix + key + ".", encountered); + } + } + return accumulator; + } + + let args = $derived.by(()=>{ + const allArgs = unproxy($page.data); + const allParams = unproxy($page.params); + const collector = {}; + reduce(collector, {param: allParams}, "", new Set()); + reduce(collector, allArgs, "", new Set()) as Record<string, unknown>; + return collector; + }); </script> <svelte:head> - <title>{$LL.PageTitle[route || "fallback"]() || $LL.PageTitle.fallback()} - ESA I/1</title> + <title>{$LL.PageTitle[($page.route?.id || $page.url.pathname) as keyof Translation["PageTitle"]](args)} | ESA I/1</title> </svelte:head> -<slot /> +<MessageList /> + +{@render children()} diff --git a/src/routes/+layout.ts b/src/routes/+layout.ts index 71f55df75865da519bb95caffd8f1efb61027d32..6c327fa313dd0477815e7d691b2c31572ae27915 100644 --- a/src/routes/+layout.ts +++ b/src/routes/+layout.ts @@ -1,11 +1,10 @@ -import { setLocale } from "$lib/i18n/i18n-svelte"; -import type { Locales } from "$lib/i18n/i18n-types.js"; -import { loadLocaleAsync } from "$lib/i18n/i18n-util.async"; +import { setLocale, type Locale } from '$lib/i18n/i18n.js'; -export const load = async (event) => { - const locale = event.data?.locale ?? "de" as Locales; - const semester = event.data?.semester; - await loadLocaleAsync(locale); +export const load = (event)=>{ + const locale = event.data.locale as Locale; setLocale(locale); - return { locale, semester }; + return { + locale, + semester: event.data.semester, + }; }; diff --git a/src/routes/admin/+layout.server.ts b/src/routes/admin/+layout.server.ts new file mode 100644 index 0000000000000000000000000000000000000000..53e0b8193cb77b7979a4310b3d4adfc2d14d24d8 --- /dev/null +++ b/src/routes/admin/+layout.server.ts @@ -0,0 +1,10 @@ +import { User } from "$lib/server/database/entities/User.entity"; +import type { LayoutServerLoad } from "./$types"; + +export const load: LayoutServerLoad = async event=>{ + const session = (await event.locals.auth())!; + const user = await User.getById(session.user.userId); + return { + user: {...session.user, permissions: user!.permissions.raw()}, // TODO add permissions + }; +}; diff --git a/src/routes/admin/+layout.svelte b/src/routes/admin/+layout.svelte new file mode 100644 index 0000000000000000000000000000000000000000..443d0452c56be5a682632661a220e04a2ac77e8f --- /dev/null +++ b/src/routes/admin/+layout.svelte @@ -0,0 +1,9 @@ +<script> + import AdminLayout from "$lib/components/AdminLayout.svelte"; + + let { children } = $props(); +</script> + +<AdminLayout> + {@render children()} +</AdminLayout> diff --git a/src/routes/admin/+layout.ts b/src/routes/admin/+layout.ts new file mode 100644 index 0000000000000000000000000000000000000000..aac08332b93fc5189535e281d713ebddd46487bd --- /dev/null +++ b/src/routes/admin/+layout.ts @@ -0,0 +1,11 @@ +import { PermissionSet } from "$lib/perms"; +import type { LayoutLoad } from "./$types"; + +export const load: LayoutLoad = event=>{ + return { + user: { + ...event.data.user, + permissions: new PermissionSet(event.data.user.permissions), + }, + }; +}; diff --git a/src/routes/admin/+page.server.ts b/src/routes/admin/+page.server.ts new file mode 100644 index 0000000000000000000000000000000000000000..ddcb8cdc9eb52166d246a4dd6fb5b9655b967fc2 --- /dev/null +++ b/src/routes/admin/+page.server.ts @@ -0,0 +1,115 @@ +import { Permission } from "$lib/perms.js"; +import { Config } from "$lib/server/database/entities/Config.entity"; +import { error } from "@sveltejs/kit"; + +export function load(){ + const config = Config.get(); + return { + config: {...config}, + }; +} + +export const actions = { + async updateBaseConfig(event){ + if(!event.locals.user.permissions.has(Permission.UPDATE_CONFIG)) throw error(403, "Insufficient permissions"); + const data = await event.request.formData(); + const currentSemester = data.get("currentSemester"); + const fresherWeekStart = data.get("fresherWeekStart"); + const fresherWeekEnd = data.get("fresherWeekEnd"); + const defaultPath = data.get("defaultPath"); + const headerLinks = data.getAll("headerLinks"); + if(!currentSemester || typeof currentSemester !== "string") throw error(400, "Invalid currentSemester"); + if(typeof defaultPath !== "string" || !defaultPath.startsWith("/")) throw error(400, "Invalid defaultPath"); + if(headerLinks.some(link=>typeof link !== "string" || !link.startsWith("/"))) throw error(400, "Invalid headerLinks"); + if(typeof fresherWeekStart !== "string") throw error(400, "Invalid fresherWeekStart"); + if(typeof fresherWeekEnd !== "string") throw error(400, "Invalid fresherWeekEnd"); + if(!/^\d{4}-\d{2}-\d{2}$/.test(fresherWeekStart)) throw error(400, "Invalid fresherWeekStart"); + if(!/^\d{4}-\d{2}-\d{2}$/.test(fresherWeekEnd)) throw error(400, "Invalid fresherWeekEnd"); + const start = Date.parse(fresherWeekStart); + const end = Date.parse(fresherWeekEnd); + if(isNaN(start)) throw error(400, "Invalid fresherWeekStart"); + if(isNaN(end)) throw error(400, "Invalid fresherWeekEnd"); + if(start > end) throw error(400, "Invalid fresherWeekStart/fresherWeekEnd"); + await Config.update({ + currentSemester, + fresherWeekStart, + fresherWeekEnd, + defaultPath, + headerLinks: headerLinks as string[], + }); + }, + async updateTutorConfig(event){ + if(!event.locals.user.permissions.has(Permission.UPDATE_CONFIG)) throw error(403, "Insufficient permissions"); + const data = await event.request.formData(); + const shirtSizesString = data.get("shirtSizes"); + const trainingsStart = data.get("trainingsStart"); + const trainingsEnd = data.get("trainingsEnd"); + const tutorRegistrationOpen = !!data.get("tutorRegistrationOpen"); + if(typeof shirtSizesString !== "string") throw error(400, "Invalid shirtSizes"); + const shirtSizes = shirtSizesString.split(";").map(s=>s.trim()).filter(s=>s); + if(shirtSizes.length === 0) throw error(400, "Invalid shirtSizes"); + if(typeof trainingsStart !== "string" || !trainingsStart.match(/^([01]\d|2[0123]):[012345]\d$/)) throw error(400, "Invalid trainingsStart"); + if(typeof trainingsEnd !== "string" || !trainingsEnd.match(/^([01]\d|2[0123]):[012345]\d$/)) throw error(400, "Invalid trainingsEnd"); + await Config.update({ + shirtSizes, + trainingsStart, + trainingsEnd, + tutorRegistrationOpen, + }); + }, + async updateRallyConfig(event){ + if(!event.locals.user.permissions.has(Permission.UPDATE_CONFIG)) throw error(403, "Insufficient permissions"); + const data = await event.request.formData(); + const rallyDate = data.get("rallyDate"); + const rallyBriefingDate = data.get("rallyBriefingDate"); + const rallyBriefingTime = data.get("rallyBriefingTime") || null; + const rallyRegistrationOpen = !!data.get("rallyRegistrationOpen"); + if(typeof rallyDate !== "string") throw error(400, "Invalid rallyDate"); + if(typeof rallyBriefingDate !== "string") throw error(400, "Invalid rallyBriefingDate"); + if(rallyBriefingTime !== null && typeof rallyBriefingTime !== "string") throw error(400, "Invalid rallyBriefingTime"); + if(!/^\d{4}-\d{2}-\d{2}$/.test(rallyDate)) throw error(400, "Invalid rallyDate"); + if(!/^\d{4}-\d{2}-\d{2}$/.test(rallyBriefingDate)) throw error(400, "Invalid rallyBriefingDate"); + if(rallyBriefingTime && !/^([01]\d|2[0-3]):[0-5]\d$/.test(rallyBriefingTime)) throw error(400, "Invalid rallyBriefingTime"); + const date = Date.parse(rallyDate); + if(isNaN(date)) throw error(400, "Invalid rallyDate"); + const briefingDate = Date.parse(rallyBriefingDate); + if(isNaN(briefingDate)) throw error(400, "Invalid rallyBriefingDate"); + await Config.update({ + rallyDate, + rallyBriefingDate, + rallyBriefingTime, + rallyRegistrationOpen, + }); + }, + async updateEsweConfig(event){ + if(!event.locals.user.permissions.has(Permission.UPDATE_CONFIG)) throw error(403, "Insufficient permissions"); + const data = await event.request.formData(); + const esweLink = data.get("esweLink"); + const esweRegistrationStartString = data.get("esweRegistrationStart"); + if(typeof esweLink !== "string") throw error(400, "Invalid esweLink"); + if(typeof esweRegistrationStartString !== "string") throw error(400, "Invalid esweRegistrationStart"); + if(!/^\d{4}-\d{2}-\d{2}T([01]\d|2[0-3]):[0-5]\d$/.test(esweRegistrationStartString)) throw error(400, "Invalid esweRegistrationStart"); + const esweRegistrationStart = Date.parse(esweRegistrationStartString); + if(isNaN(esweRegistrationStart)) throw error(400, "Invalid esweRegistrationStart"); + const esweStart = data.get("esweStart"); + const esweEnd = data.get("esweEnd"); + const eswePrice = Number(data.get("eswePrice") ?? undefined); + if(typeof esweStart !== "string") throw error(400, "Invalid esweStart"); + if(typeof esweEnd !== "string") throw error(400, "Invalid esweEnd"); + if(!/^\d{4}-\d{2}-\d{2}$/.test(esweStart)) throw error(400, "Invalid esweStart"); + if(!/^\d{4}-\d{2}-\d{2}$/.test(esweEnd)) throw error(400, "Invalid esweEnd"); + if(!Number.isInteger(eswePrice) || eswePrice < 0) throw error(400, "Invalid eswePrice"); + const start = Date.parse(esweStart); + const end = Date.parse(esweEnd); + if(isNaN(start)) throw error(400, "Invalid esweStart"); + if(isNaN(end)) throw error(400, "Invalid esweEnd"); + if(start > end) throw error(400, "Invalid esweStart/esweEnd"); + await Config.update({ + esweLink, + esweRegistrationStart, + esweStart, + esweEnd, + eswePrice, + }); + }, +}; diff --git a/src/routes/admin/+page.svelte b/src/routes/admin/+page.svelte new file mode 100644 index 0000000000000000000000000000000000000000..dfe626fab078a04cb762ef405f5fe8e43ca977c4 --- /dev/null +++ b/src/routes/admin/+page.svelte @@ -0,0 +1,152 @@ +<script lang="ts"> + import { enhance } from "$app/forms"; + import { invalidateAll } from "$app/navigation"; + import { addMessage } from "$lib/messages"; + import { Permission } from "$lib/perms.js"; + import { Button, Checkbox, Heading, Input, Label, MultiSelect, Select } from "flowbite-svelte"; + + let { data } = $props(); + + let canEdit = data.user.permissions.has(Permission.UPDATE_CONFIG); + let disabled = !canEdit; +</script> + +<Heading tag="h1" customSize="text-4xl font-bold" class="mb-6 text-center">Einstellungen</Heading> +<Heading tag="h2" customSize="text-3xl font-bold" class="mt-6">Grundeinstellungen</Heading> +<form method="post" action="?/updateBaseConfig" use:enhance={()=>({result})=>{ + if(result.type === "success"){ + addMessage({type: "success", text: "Einstellungen gespeichert"}); + invalidateAll(); + }else{ + addMessage({type: "error", text: "Fehler beim Speichern der Einstellungen"}); + console.error(result); + } +}}> + <Label class="mb-2"> + Aktuelles Semester + <Input type="text" name="currentSemester" value={data.config.currentSemester} required {disabled} /> + </Label> + <div class="flex gap-4 mb-2"> + <Label class="flex-[1_1_50%]"> + Start der Erstiwoche + <Input type="date" name="fresherWeekStart" value={data.config.fresherWeekStart} required {disabled} /> + </Label> + <Label class="flex-[1_1_50%]"> + Ende der Erstiwoche + <Input type="date" name="fresherWeekEnd" value={data.config.fresherWeekEnd} required {disabled} /> + </Label> + </div> + <Label class="mb-2"> + Standardseite + <Select name="defaultPath" value={data.config.defaultPath} {disabled}> + <option value="/information">/information</option> + <option value="/tutor">/tutor</option> + <option value="/rallye">/rallye</option> + <option value="/eswe">/eswe</option> + <option value="/rabatte">/rabatte</option> + <option value="/flyer">/flyer</option> + </Select> + </Label> + <Label class="mb-2"> + Im Header angezeigte Links + <!-- die hier verwendete Reihenfolge in items bestimmt die Reihenfolge im Header selbst --> + <MultiSelect {disabled} name="headerLinks" value={data.config.headerLinks as string[]} items={["/information","/rabatte","/eswe","/flyer","/tutor"].map(link=>({name: link, value: link}))} /> + </Label> + {#if canEdit} + <Button type="submit" class="mb-2">Speichern</Button> + {/if} +</form> +<Heading tag="h2" customSize="text-3xl font-bold" class="mt-6">Tutoren</Heading> +<form method="post" action="?/updateTutorConfig" use:enhance={()=>({result})=>{ + if(result.type === "success"){ + addMessage({type: "success", text: "Einstellungen gespeichert"}); + invalidateAll(); + }else{ + addMessage({type: "error", text: "Fehler beim Speichern der Einstellungen"}); + console.error(result); + } +}}> + <Label class="mb-2"> + T-Shirt-Größen (durch Semikolon getrennt) + <Input type="text" name="shirtSizes" value={data.config.shirtSizes.join(";")} required {disabled} /> + </Label> + <div class="flex gap-4 mb-2"> + <Label class="w-full"> + Beginn Tutschulungen + <Input type="time" name="trainingsStart" value={data.config.trainingsStart} required {disabled} /> + </Label> + <Label class="w-full"> + Ende Tutschulungen + <Input type="time" name="trainingsEnd" value={data.config.trainingsEnd} required {disabled} /> + </Label> + </div> + <Checkbox class="mb-2" name="tutorRegistrationOpen" checked={data.config.tutorRegistrationOpen} {disabled}>Tutorenanmeldung geöffnet</Checkbox> + {#if canEdit} + <Button type="submit" class="mb-2">Speichern</Button> + {/if} +</form> +<Heading tag="h2" customSize="text-3xl font-bold" class="mt-6">Rallye</Heading> +<form method="post" action="?/updateRallyConfig" use:enhance={()=>({result})=>{ + if(result.type === "success"){ + addMessage({type: "success", text: "Einstellungen gespeichert"}); + invalidateAll(); + }else{ + addMessage({type: "error", text: "Fehler beim Speichern der Einstellungen"}); + console.error(result); + } +}}> + <Label class="mb-2"> + Datum der Rallye + <Input type="date" name="rallyDate" value={data.config.rallyDate} required {disabled} /> + </Label> + <div class="flex gap-4 mb-2"> + <Label class="flex-[1_1_50%]"> + Datum des Vortreffens + <Input type="date" name="rallyBriefingDate" value={data.config.rallyBriefingDate} required {disabled} /> + </Label> + <Label class="flex-[1_1_50%]"> + Uhrzeit des Vortreffens + <Input type="time" name="rallyBriefingTime" step="900" value={data.config.rallyBriefingTime?.split(":").slice(0,2).join(":")} {disabled} /> + </Label> + </div> + <Checkbox class="mb-2" name="rallyRegistrationOpen" checked={data.config.rallyRegistrationOpen} {disabled}>Stationsbetreueranmeldung geöffnet</Checkbox> + {#if canEdit} + <Button type="submit" class="mb-2">Speichern</Button> + {/if} +</form> +<Heading tag="h2" customSize="text-3xl font-bold" class="mt-6">ESWE</Heading> +<form method="post" action="?/updateEsweConfig" use:enhance={()=>({result})=>{ + if(result.type === "success"){ + addMessage({type: "success", text: "Einstellungen gespeichert"}); + invalidateAll(); + }else{ + addMessage({type: "error", text: "Fehler beim Speichern der Einstellungen"}); + console.error(result); + } +}}> + <Label class="mb-2"> + Anmeldelink + <Input type="url" name="esweLink" value={data.config.esweLink} required {disabled} /> + </Label> + <div class="flex gap-4 mb-2"> + <Label class="flex-[1_1_25%]"> + Anmeldestart (UTC) <!--TODO change timezone--> + <Input type="datetime-local" name="esweRegistrationStart" value={data.config.esweRegistrationStart?.toISOString().split("Z")[0]} {disabled} /> + </Label> + <Label class="flex-[1_1_25%]"> + Startdatum + <Input type="date" name="esweStart" value={data.config.esweStart} required {disabled} /> + </Label> + <Label class="flex-[1_1_25%]"> + Enddatum + <Input type="date" name="esweEnd" value={data.config.esweEnd} required {disabled} /> + </Label> + <Label class="flex-[1_1_25%]"> + Preis (in Cent) + <Input type="number" name="eswePrice" value={data.config.eswePrice} required {disabled} /> + </Label> + </div> + {#if canEdit} + <Button type="submit" class="mb-2">Speichern</Button> + {/if} +</form> diff --git a/src/routes/admin/eswe/+page.server.ts b/src/routes/admin/eswe/+page.server.ts new file mode 100644 index 0000000000000000000000000000000000000000..05f4e9e124e87d9afd8e3935691fd53ef479dfba --- /dev/null +++ b/src/routes/admin/eswe/+page.server.ts @@ -0,0 +1,54 @@ +import { locales } from "$lib/i18n/i18n.js"; +import { SUPPORTED_EXTENSIONS } from "$lib/server/eswe"; +import { deleteImage, editImage, getImages, uploadImage } from "$lib/server/images.js"; +import type { Localized } from "$lib/utils.js"; +import { error } from "@sveltejs/kit"; + +export async function load(){ + const images = (await getImages<number>("eswe")).sort((a, b) => a.custom! - b.custom!); + return { + images, + extensions: SUPPORTED_EXTENSIONS, + }; +} + +export const actions = { + async updateImage(event){ + const data = await event.request.formData(); + const imageId = data.get("id"); + const sorting = Number(data.get("sorting") ?? undefined); + const description = {} as Localized; + if(!imageId) throw error(400, "Missing image id"); + if(typeof imageId !== "string") throw error(400, "Invalid image id"); + if(!Number.isInteger(sorting) || sorting < 0 || sorting > 99) throw error(400, "Invalid sorting"); + for(const locale of locales) description[locale] = ""; + const descriptionDE = data.get(`description`); + if(!descriptionDE || typeof descriptionDE !== "string") throw error(400, "Missing description"); + description.de = descriptionDE; + await editImage("eswe", imageId, { description, custom: sorting }); + }, + async deleteImage(event){ + const data = await event.request.formData(); + const imageId = data.get("id"); + if(!imageId) throw error(400, "Missing image id"); + if(typeof imageId !== "string") throw error(400, "Invalid image id"); + await deleteImage("eswe", imageId); + }, + async uploadImage(event){ + const data = await event.request.formData(); + const file = data.get("image"); + const sorting = Number(data.get("sorting") ?? undefined); + const description = {} as Localized; + if(!file) throw error(400, "Missing image file"); + if(!(file instanceof File)) throw error(400, "Invalid image file"); + if(!Number.isInteger(sorting) || sorting < 0 || sorting > 99) throw error(400, "Invalid sorting"); + for(const locale of locales) description[locale] = ""; + const descriptionDE = data.get(`description`); + if(!descriptionDE || typeof descriptionDE !== "string") throw error(400, "Missing description"); + description.de = descriptionDE; + const extension = file.name.split(".").pop()!.toLowerCase(); + if(!SUPPORTED_EXTENSIONS.includes(extension)) throw error(400, "Invalid extension"); + const imageData = await file.arrayBuffer(); + uploadImage(imageData, description, "eswe", sorting); + }, +}; diff --git a/src/routes/admin/eswe/+page.svelte b/src/routes/admin/eswe/+page.svelte new file mode 100644 index 0000000000000000000000000000000000000000..f3187bfba4aab86628377d805f07acd3f274cf11 --- /dev/null +++ b/src/routes/admin/eswe/+page.svelte @@ -0,0 +1,86 @@ +<script lang="ts"> + import { enhance } from "$app/forms"; + import { invalidateAll } from "$app/navigation"; + import Image from "$lib/components/Image.svelte"; + import { addMessage } from "$lib/messages.js"; + import { Button, Input, Label, Table, TableBody, TableBodyCell, TableBodyRow, TableHead, TableHeadCell } from "flowbite-svelte"; + + export let data; +</script> + +{#each data.images as image} +<form method="post" action="?/updateImage" id={image.identifier} use:enhance={()=>({result})=>{ + if(result.type === "success"){ + addMessage({type: "success", text: "Bild gespeichert"}); + invalidateAll(); + }else{ + addMessage({type: "error", text: "Fehler beim Speichern"}); + console.error(result); + } +}}> + <input type="hidden" name="id" value={image.identifier} /> +</form> +<form method="post" action="?/deleteImage" id="{image.identifier}-delete" use:enhance={()=>({result})=>{ + if(result.type === "success"){ + addMessage({type: "success", text: "Bild gelöscht"}); + invalidateAll(); + }else{ + addMessage({type: "error", text: "Fehler beim Löschen"}); + console.error(result); + } +}}> + <input type="hidden" name="id" value={image.identifier} /> +</form> +{/each} +<form method="post" action="?/uploadImage" id="new" enctype="multipart/form-data" use:enhance={()=>({result})=>{ + if(result.type === "success"){ + addMessage({type: "success", text: "Bild hochgeladen"}); + invalidateAll(); + }else{ + addMessage({type: "error", text: "Fehler beim Hochladen"}); + console.error(result); + } +}}></form> + +<Table> + <TableHead> + <TableHeadCell>Bild</TableHeadCell> + <TableHeadCell>Sortierung / Titel</TableHeadCell> + <TableHeadCell></TableHeadCell> + </TableHead> + <TableBody> + {#each data.images as image} + <TableBodyRow> + <TableBodyCell><Image src={image} class="max-h-36 w-auto" sizes="216px" autosize /></TableBodyCell> + <TableBodyCell> + <Label> + Position + <Input type="number" value={image.custom} name="sorting" form={image.identifier} min={0} max={99} required /> + </Label> + <Label> + Beschreibung + <Input value={image.description.de} name="description" form={image.identifier} required /> + </Label> + </TableBodyCell> + <TableBodyCell> + <Button type="submit" form={image.identifier}>Speichern</Button> + <Button color="red" type="submit" form="{image.identifier}-delete">Löschen</Button> + </TableBodyCell> + </TableBodyRow> + {/each} + <TableBodyRow> + <TableBodyCell class="max-w-min"><Input type="file" name="image" form="new" class="max-w-min" accept={data.extensions.map(ext=>`.${ext}`).join(",")} /></TableBodyCell> + <TableBodyCell> + <Label> + Sortierung + <Input type="number" name="sorting" form="new" value={Math.max(0,...data.images.map(i=>i.custom!))+1} min={0} max={99} required /> + </Label> + <Label> + Beschreibung + <Input name="description" form="new" required /> + </Label> + </TableBodyCell> + <TableBodyCell><Button type="submit" form="new">Hochladen</Button></TableBodyCell> + </TableBodyRow> + </TableBody> +</Table> diff --git a/src/routes/admin/flyer/+page.server.ts b/src/routes/admin/flyer/+page.server.ts new file mode 100644 index 0000000000000000000000000000000000000000..522471185eacc7057cd78b666460e96770d17c26 --- /dev/null +++ b/src/routes/admin/flyer/+page.server.ts @@ -0,0 +1,52 @@ +import "$lib/polyfill"; +import type { PageServerLoad } from './$types'; +import fs from "node:fs/promises"; +import { createCanvas } from 'canvas'; +import * as pdfjs from "pdfjs-dist"; +import { getImages, uploadImage } from "$lib/server/images"; + +export const load = (async () => { + const flyer = await getImages<string>("flyer"); + return { + flyer, + }; +}) satisfies PageServerLoad; + +export const actions = { + upload: async (event) => { + const data = await event.request.formData(); + const file = data.get('file'); + if(!(file instanceof File)) return new Response(null, { status: 400 }); + if(!file.name.toLowerCase().endsWith('.pdf')) return new Response(null, { status: 400 }); + let filename = file.name.replace(/[^a-z0-9._\-äöü+]/gi, '_'); + filename = filename.substring(0, filename.length - 4); + const path = `static/flyer/${filename}.pdf`; + await fs.mkdir("static/flyer", { recursive: true }); + await fs.writeFile(path, Buffer.from(await file.arrayBuffer()), {}); + const image = await renderFlyerImages(filename); + await uploadImage(image, { de: "", en: "" }, "flyer", filename); + }, +} + +async function renderFlyerImages(filename: string){ + const file = await pdfjs.getDocument({ + url: `static/flyer/${filename}.pdf`, + standardFontDataUrl: "node_modules/pdfjs-dist/standard_fonts/", + }).promise; + const page = await file.getPage(1); + return renderPage(page, 1080, 1080); +} + +async function renderPage(page: pdfjs.PDFPageProxy, minWidth: number, minHeight: number): Promise<Buffer> { + const unscaledViewport = page.getViewport({ scale: 1 }); + const scale = Math.max(minWidth / unscaledViewport.width, minHeight / unscaledViewport.height); + const viewport = page.getViewport({ scale }); + const canvas = createCanvas(viewport.width, viewport.height); + const ctx = canvas.getContext("2d"); + await page.render({ + // @ts-expect-error ctx is a valid canvas context, but from a different library + canvasContext: ctx, + viewport, + }).promise; + return Buffer.from(canvas.toDataURL("image/jpeg").split(",")[1], "base64"); +} diff --git a/src/routes/admin/flyer/+page.svelte b/src/routes/admin/flyer/+page.svelte new file mode 100644 index 0000000000000000000000000000000000000000..2a8859e7b1c982f1d2678c7d5b089d0b33e66007 --- /dev/null +++ b/src/routes/admin/flyer/+page.svelte @@ -0,0 +1,40 @@ +<script lang="ts"> + import { Button, Input, Li, List } from 'flowbite-svelte'; + import { enhance } from '$app/forms'; + import Image from '$lib/components/Image.svelte'; + import { addMessage } from '$lib/messages.js'; + + let { data } = $props(); + + function deleteFlyer(flyer: string) { + fetch(`/admin/flyer/${flyer}`, { + method: "DELETE", + }).then(()=>window.location.reload()); + } +</script> + +<List list="none"> + {#each data.flyer as flyer} + <Li class="mb-2"> + <div class="flex gap-3 items-center"> + <a href="/flyer/{flyer.custom}.pdf"><Image src={flyer} sizes="240px" class="w-60 h-40 object-contain" /></a> + <Button color="red" class="h-[fit-content]" on:click={()=>deleteFlyer(flyer.identifier)}>Löschen</Button> + </div> + </Li> + {/each} + <Li> + <form method="post" enctype="multipart/form-data" action="?/upload" use:enhance={()=>({result})=>{ + if(result.type === "success"){ + location.reload(); + }else{ + addMessage({type: "error", text: "Fehler beim Hochladen"}); + console.error(result); + } + }}> + <div class="flex gap-3"> + <Input type="file" name="file" accept=".pdf" /> + <Button type="submit">Hochladen</Button> + </div> + </form> + </Li> +</List> diff --git a/src/routes/admin/flyer/[flyer]/+server.ts b/src/routes/admin/flyer/[flyer]/+server.ts new file mode 100644 index 0000000000000000000000000000000000000000..f8357ee0a6e776f42d3a7452a2219a8475161f35 --- /dev/null +++ b/src/routes/admin/flyer/[flyer]/+server.ts @@ -0,0 +1,12 @@ +import { deleteImage, getImages } from '$lib/server/images'; +import type { RequestHandler } from './$types'; +import fs from 'node:fs/promises'; + +export const DELETE: RequestHandler = async (event) => { + const flyer = event.params.flyer; + const f = (await getImages('flyer')).find(f=>f.identifier===flyer); + if(!f) return new Response(null, { status: 404 }); + await deleteImage('flyer', flyer); + await fs.unlink(`static/flyer/${f.custom}.pdf`); + return new Response(null, { status: 204 }); +}; diff --git a/src/routes/admin/rabatte/+page.server.ts b/src/routes/admin/rabatte/+page.server.ts new file mode 100644 index 0000000000000000000000000000000000000000..d5000419766bbd3778a4bf96a7ef25c2bd6e576f --- /dev/null +++ b/src/routes/admin/rabatte/+page.server.ts @@ -0,0 +1,66 @@ +import { Discount, Tag } from '$lib/server/database/entities/Discount.entity'; +import { error } from '@sveltejs/kit'; +import type { PageServerLoad } from './$types'; +import { locales } from '$lib/i18n/i18n'; +import type { Localized } from '$lib/utils'; + +export const load = (async () => { + const discounts = await Discount.getAll(); + const tags = await Tag.getAll(); + return { + discounts: discounts.map(d=>({...d, tags: d.tags.map(t=>({...t}))})), + tags: tags.map(t=>({...t})), + }; +}) satisfies PageServerLoad; + +export const actions = { + create: async (event) => { + const data = await event.request.formData(); + const title = data.get("title"); + const description = {} as Localized; + const startDate = data.get("startDate"); + const endDate = data.get("endDate"); + if(!title || typeof title !== "string") error(400, "Invalid title"); + for(const locale of locales){ + const desc = data.get(`description[${locale}]`); + if(!desc || typeof desc !== "string") error(400, "Invalid description for locale " + locale); + description[locale] = desc; + } + if(!startDate || typeof startDate !== "string") error(400, "Invalid startDate"); + if(!endDate || typeof endDate !== "string") error(400, "Invalid endDate"); + const parsedStart = Date.parse(startDate); + const parsedEnd = Date.parse(endDate); + if(!startDate.match(/^\d{4}-\d{2}-\d{2}$/) || isNaN(parsedStart)) error(400, "Invalid startDate format"); + if(!endDate.match(/^\d{4}-\d{2}-\d{2}$/) || isNaN(parsedEnd)) error(400, "Invalid endDate format"); + if(parsedStart > parsedEnd) error(400, "startDate must be before endDate"); + const address = ""; + const location = [0, 0] satisfies Discount["location"]; + const openingHours = {} as Record<number, unknown>; + for(let i = 1; i <= 7; i++) openingHours[i] = []; + const id = await Discount.create({ title, description, startDate, endDate, address, location, openingHours }); + return { id }; + }, + createTag: async (event) => { + const data = await event.request.formData(); + const name = {} as Localized; + for(const locale of locales){ + const n = data.get(`name[${locale}]`); + if(!n || typeof n !== "string") error(400, `Invalid name for locale ${locale}`); + name[locale] = n; + } + const id = await Tag.create({ name }); + return { id }; + }, + updateTag: async (event) => { + const data = await event.request.formData(); + const id = data.get("id"); + const name = {} as Localized; + if(typeof id !== "string" || !id.match(/^\d+$/)) error(400, "Invalid id"); + for(const locale of locales){ + const n = data.get(`name[${locale}]`); + if(!n || typeof n !== "string") error(400, `Invalid name for locale ${locale}`); + name[locale] = n; + } + await Tag.update(parseInt(id), { name }); + }, +}; diff --git a/src/routes/admin/rabatte/+page.svelte b/src/routes/admin/rabatte/+page.svelte new file mode 100644 index 0000000000000000000000000000000000000000..82a99e1400743669a4ed96d13cb42c960fe6d661 --- /dev/null +++ b/src/routes/admin/rabatte/+page.svelte @@ -0,0 +1,158 @@ +<script lang="ts"> + import { Button, Input, Label, P, Table, TableBody, TableBodyCell, TableBodyRow, TableHead, TableHeadCell, Textarea } from 'flowbite-svelte'; + import type { PageData } from './$types'; + import { locales } from '$lib/i18n/i18n'; + import { enhance } from '$app/forms'; + import { goto, invalidateAll } from '$app/navigation'; + import { addMessage } from '$lib/messages'; + + export let data: PageData; + + const rangeFormatter = new Intl.DateTimeFormat('de', { + year: 'numeric', + month: 'short', + day: 'numeric', + }); + + function deleteDiscount(id: number) { + fetch(`/admin/rabatte/${id}`, { + method: 'DELETE', + }).then(() => { + location.reload(); + }); + } + function deleteTag(id: number){ + fetch(`/admin/rabatte/tag/${id}`, { + method: "DELETE", + }).then(()=>{ + location.reload(); + }); + } +</script> + +<form action="?/create" method="post" id="form-new" use:enhance={()=>({result})=>{ + if(result.type === "success"){ + goto(`/admin/rabatte/${result.data!.id}`); + }else{ + addMessage({type: "error", text: "Fehler beim Erstellen"}); + console.error(result); + } +}}></form> + +<Table class="mb-2"> + <TableHead> + <TableHeadCell>Geschäft</TableHeadCell> + <TableHeadCell>Beschreibung</TableHeadCell> + <TableHeadCell>Zeitraum</TableHeadCell> + <TableHeadCell></TableHeadCell> + </TableHead> + <TableBody> + {#each data.discounts as discount} + <TableBodyRow> + <TableBodyCell>{discount.title}</TableBodyCell> + <TableBodyCell> + {#each locales as locale} + <P class="pb-2">{locale}: {discount.description[locale]}</P> + {/each} + </TableBodyCell> + <TableBodyCell>{rangeFormatter.formatRange(new Date(discount.startDate), new Date(discount.endDate))}</TableBodyCell> + <TableBodyCell> + <Button href="/admin/rabatte/{discount.id}">Bearbeiten</Button> + <Button color="red" on:click={()=>deleteDiscount(discount.id)}>Löschen</Button> + </TableBodyCell> + </TableBodyRow> + {/each} + <TableBodyRow> + <TableBodyCell> + <Label> + Name + <Input name="title" type="text" required form="form-new" /> + </Label> + </TableBodyCell> + <TableBodyCell> + {#each locales as locale} + <Label class="flex flex-col"> + Beschreibung ({locale}) + <Textarea name="description[{locale}]" required form="form-new" /> + </Label> + {/each} + </TableBodyCell> + <TableBodyCell> + <Label> + Startdatum + <Input name="startDate" type="date" required form="form-new" /> + </Label> + <Label> + Enddatum + <Input name="endDate" type="date" required form="form-new" /> + </Label> + </TableBodyCell> + <TableBodyCell> + <Button type="submit" form="form-new">Erstellen</Button> + </TableBodyCell> + </TableBodyRow> + </TableBody> +</Table> + +{#each data.tags as tag (tag.id)} +<form id="tag-{tag.id}" action="?/updateTag" method="post" use:enhance={()=>({result})=>{ + if(result.type === "success"){ + addMessage({type: "success", text: "Tag gespeichert"}); + invalidateAll(); + }else{ + addMessage({type: "error", text: "Fehler beim Speichern"}); + console.error(result); + } +}}> + <input type="hidden" name="id" value={tag.id} /> +</form> +{/each} +<form id="tag-new" action="?/createTag" method="post" use:enhance={()=>({result})=>{ + if(result.type === "success"){ + addMessage({type: "success", text: "Tag erstellt"}); + invalidateAll(); + }else{ + addMessage({type: "error", text: "Fehler beim Erstellen"}); + console.error(result); + } +}}></form> + +<Table class="mb-2"> + <TableHead> + <TableHeadCell>Tag</TableHeadCell> + <TableHeadCell></TableHeadCell> + </TableHead> + <TableBody> + {#each data.tags as tag (tag.id)} + <TableBodyRow> + <TableBodyCell> + <div class="flex gap-3"> + {#each locales as locale} + <Label class="w-full"> + Name ({locale}) + <Input form="tag-{tag.id}" name="name[{locale}]" type="text" value={tag.name[locale]} required /> + </Label> + {/each} + </div> + </TableBodyCell> + <TableBodyCell> + <Button form="tag-{tag.id}" type="submit">Bearbeiten</Button> + <Button color="red" on:click={()=>deleteTag(tag.id)} disabled={data.discounts.some(d=>d.tags.some(t=>t.id===tag.id))||undefined}>Löschen</Button> + </TableBodyCell> + </TableBodyRow> + {/each} + <TableBodyRow> + <TableBodyCell> + <div class="flex gap-3"> + {#each locales as locale} + <Label class="w-full"> + Name ({locale}) + <Input form="tag-new" name="name[{locale}]" type="text" required /> + </Label> + {/each} + </div> + </TableBodyCell> + <TableBodyCell><Button form="tag-new" type="submit">Erstellen</Button></TableBodyCell> + </TableBodyRow> + </TableBody> +</Table> diff --git a/src/routes/admin/rabatte/[id]/+page.server.ts b/src/routes/admin/rabatte/[id]/+page.server.ts new file mode 100644 index 0000000000000000000000000000000000000000..87d7c9aa8a5fa14c8987fcd44f05c2e20503a697 --- /dev/null +++ b/src/routes/admin/rabatte/[id]/+page.server.ts @@ -0,0 +1,88 @@ +import { Discount, Tag } from '$lib/server/database/entities/Discount.entity'; +import { error } from '@sveltejs/kit'; +import type { PageServerLoad } from './$types'; +import type { Localized } from '$lib/utils'; +import { locales } from '$lib/i18n/i18n'; +import fs from "node:fs/promises"; + +const leafletStyle = await fs.readFile("node_modules/leaflet/dist/leaflet.css", "utf-8"); + +export const load = (async (event) => { + const id = Number(event.params.id); + if(!Number.isInteger(id)) error(404, 'Discount not found'); + const discount = await Discount.getById(id); + if(!discount) error(404, 'Discount not found'); + const tags = await Tag.getAll(); + return { + discount: {...discount, tags: discount.tags.map(t=>({...t}))}, + tags: tags.map(t=>({...t})), + leafletStyle, // man i love svelte just removing "unused" css an there not being a way to bypass that (like :global(selector)) for imports + }; +}) satisfies PageServerLoad; + +export const actions = { + update: async (event) => { + const id = Number(event.params.id); + if(!Number.isInteger(id)) error(404, 'Discount not found'); + const discount = await Discount.getById(id); + if(!discount) error(404, "Discount not found"); + const data = await event.request.formData(); + const title = data.get("title"); + const address = data.get("address"); + const description = {} as Localized; + const startDate = data.get("startDate"); + const endDate = data.get("endDate"); + const tags = data.getAll("tags"); + const latitude = data.get("latitude"); + const longitude = data.get("longitude"); + if(!title || typeof title !== "string") error(400, "Invalid title"); + if(!address || typeof address !== "string") error(400, "Invalid address"); + for(const locale of locales){ + const desc = data.get(`description[${locale}]`); + if(!desc || typeof desc !== "string") error(400, `Invalid description for locale ${locale}`); + description[locale] = desc; + } + if(typeof startDate !== "string" || !startDate.match(/^\d{4}-\d{2}-\d{2}$/)) error(400, "Invalid start date"); + if(typeof endDate !== "string" || !endDate.match(/^\d{4}-\d{2}-\d{2}$/)) error(400, "Invalid end date"); + if(tags.some(tag=>typeof tag !== "string" || !tag.match(/^\d+$/))) error(400, "Invalid tag"); + if(typeof latitude !== "string" || !latitude.match(/^\d+(\.\d+)?$/)) error(400, "Invalid latitude"); + if(typeof longitude !== "string" || !longitude.match(/^\d+(\.\d+)?$/)) error(400, "Invalid longitude"); + const startParsed = Date.parse(startDate); + const endParsed = Date.parse(endDate); + if(isNaN(startParsed) || isNaN(endParsed)) error(400, "Invalid date"); + if(startParsed > endParsed) error(400, "Start date must be before end date"); + const openingHours = {} as Discount["openingHours"]; + let currentDay = 0; + while(data.has(`openingHours[${currentDay}]`)){ + const hours = [] as [[number, number], [number, number]][]; + let currentOpening = 0; + while(data.has(`openingHours[${currentDay}][${currentOpening}][sh]`)){ + const startHourRaw = data.get(`openingHours[${currentDay}][${currentOpening}][sh]`); + const startMinuteRaw = data.get(`openingHours[${currentDay}][${currentOpening}][sm]`); + const endHourRaw = data.get(`openingHours[${currentDay}][${currentOpening}][eh]`); + const endMinuteRaw = data.get(`openingHours[${currentDay}][${currentOpening}][em]`); + if(typeof startHourRaw !== "string" || !startHourRaw.match(/^\d{1,2}$/) || typeof startMinuteRaw !== "string" || !startMinuteRaw.match(/^\d{1,2}$/) || typeof endHourRaw !== "string" || !endHourRaw.match(/^\d{1,2}$/) || typeof endMinuteRaw !== "string" || !endMinuteRaw.match(/^\d{1,2}$/)) error(400, `Invalid start or end time`); + const startHour = parseInt(startHourRaw); + const startMinute = parseInt(startMinuteRaw); + let endHour = parseInt(endHourRaw); + const endMinute = parseInt(endMinuteRaw); + if(endHour < startHour || (endHour === startHour && endMinute <= startMinute)) endHour += 24; + hours.push([[startHour, startMinute], [endHour, endMinute]]); + currentOpening++; + } + const day = data.get(`openingHours[${currentDay}]`); + if(typeof day !== "string" || !(["1","2","3","4","5","6","7"].includes(day) || day==="holiday" || (day.match(/^\d{4}-\d{2}-\d{2}$/) && !isNaN(Date.parse(day))))) error(400, `Invalid day: ${day}`); + openingHours[day as keyof Discount["openingHours"]] = hours; + currentDay++; + } + await Discount.update(id, { + title, + description, + address, + location: [parseFloat(latitude), parseFloat(longitude)], + startDate, + endDate, + openingHours, + }, tags.map(t=>parseInt(t as string))); + }, +}; diff --git a/src/routes/admin/rabatte/[id]/+page.svelte b/src/routes/admin/rabatte/[id]/+page.svelte new file mode 100644 index 0000000000000000000000000000000000000000..1d5643a28753b214b14479dd26117a50d05a88a3 --- /dev/null +++ b/src/routes/admin/rabatte/[id]/+page.svelte @@ -0,0 +1,153 @@ +<script lang="ts"> + import { Button, Input, Label, MultiSelect, Textarea } from 'flowbite-svelte'; + import type { PageData } from './$types'; + import { locale, locales } from '$lib/i18n/i18n'; + import { onDestroy, onMount } from 'svelte'; + import type { Map, Marker } from 'leaflet'; + import { enhance } from '$app/forms'; + import { invalidateAll } from '$app/navigation'; + import OpeningHoursInput from './OpeningHoursInput.svelte'; + import { addMessage } from '$lib/messages'; + + export let data: PageData; + + let [latitude, longitude] = data.discount.location; + let openingHours = data.discount.openingHours; + let tags = data.discount.tags.map(tag=>tag.id); + + let mapElement: HTMLDivElement; + let map: Map; + let marker: Marker; + let styleElement: HTMLStyleElement; + onMount(async ()=>{ + styleElement = document.head.appendChild(document.createElement('style')); + styleElement.textContent = data.leafletStyle; + const leaflet = await import('leaflet'); + // when built the default file paths don't exist anymore + leaflet.Marker.prototype.options.icon = new leaflet.Icon({ + iconUrl: "/map/marker-icon.png", + iconRetinaUrl: "/map/marker-icon-2x.png", + shadowUrl: "/map/marker-shadow.png", + iconSize: [25, 41], + iconAnchor: [12, 41], + popupAnchor: [1, -34], + tooltipAnchor: [16, -28], + shadowSize: [41, 41] + }); + map = leaflet.map(mapElement).setView(data.discount.location, 16); + leaflet.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { + maxZoom: 19, + attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>' + }).addTo(map); + marker = leaflet.marker(data.discount.location).addTo(map); + map.addEventListener("click", e=>{ + const { lat, lng } = e.latlng; + refocusMap(lat, lng); + }); + }); + onDestroy(()=>{ + map?.remove(); + styleElement?.remove(); + }); + + function roundTo5Digits(num: number): number{ + return Math.round(num * 1e5) / 1e5; + } + function refocusMap(address: string): void; + function refocusMap(latitude: number, longitude: number): void; + async function refocusMap(){ + if(arguments.length === 1){ + const [address] = arguments; + const url = new URL("https://nominatim.openstreetmap.org/search"); + url.searchParams.append("format", "jsonv2"); + url.searchParams.append("street", address); + url.searchParams.append("city", "Aachen"); + const response = await fetch(url).then(res=>res.json()); + if(response.length > 0){ + const { lat, lon } = response[0]; + refocusMap(parseFloat(lat), parseFloat(lon)); + map?.setZoom(19); + } + }else if(arguments.length === 2){ + const lat = roundTo5Digits(arguments[0]); + const lon = roundTo5Digits(arguments[1]); + map?.setView([lat, lon]); + marker?.setLatLng([lat, lon]); + latitude = lat; + longitude = lon; + } + } +</script> + +<style> + /*@import 'leaflet/dist/leaflet.css';*/ + + /* leaflet doesn't have a dark mode on its own, but this is fairly good */ + :global(.dark .leaflet-layer), + :global(.dark .leaflet-control-zoom-in), + :global(.dark .leaflet-control-zoom-out), + :global(.dark .leaflet-control-attribution) { + filter: invert(100%) hue-rotate(170deg) brightness(95%) contrast(90%); + } + :global(.dark .leaflet-container) { + background-color: #333; + } +</style> + +<form method="post" action="?/update" use:enhance={()=>({result})=>{ + if(result.type === "success"){ + addMessage({type: "success", text: "Daten gespeichert"}); + invalidateAll(); + }else{ + addMessage({type: "error", text: "Fehler beim Speichern"}); + console.error(result); + } +}}> + <Label class="mb-2"> + Geschäft + <Input name="title" value={data.discount.title} required type="text" /> + </Label> + <Label class="mb-2"> + Adresse + <Input name="address" value={data.discount.address} required type="text" on:change={e=>refocusMap(e.target!.value)} /> + </Label> + <div class="flex gap-3"> + {#each locales as locale} + <Label class="w-full mb-2"> + Beschreibung ({locale}) + <Textarea name="description[{locale}]" value={data.discount.description[locale]} required /> + </Label> + {/each} + </div> + <div class="flex gap-3"> + <Label class="w-full mb-2"> + Startdatum + <Input name="startDate" value={data.discount.startDate} required type="date" /> + </Label> + <Label class="w-full mb-2"> + Enddatum + <Input name="endDate" value={data.discount.endDate} required type="date" /> + </Label> + </div> + <Label class="mb-2"> + Tags + <MultiSelect name="tags" bind:value={tags} items={data.tags.map(t=>({name: t.name[$locale], value: t.id}))} /> + </Label> + <div class="mb-2"> + <OpeningHoursInput bind:openingHours /> + </div> + <div class="mx-auto w-full h-72 mb-2"> + <div class="h-full" bind:this={mapElement}></div> + </div> + <div class="flex gap-3"> + <Label class="w-full mb-2"> + Latitude + <Input name="latitude" value={latitude} required type="number" step="0.00001" on:input={e=>refocusMap(e.target!.value, longitude)} /> + </Label> + <Label class="w-full mb-2"> + Longitude + <Input name="longitude" value={longitude} required type="number" step="0.00001" on:input={e=>refocusMap(latitude, e.target!.value)} /> + </Label> + </div> + <Button type="submit" class="mb-2">Speichern</Button> +</form> diff --git a/src/routes/admin/rabatte/[id]/+server.ts b/src/routes/admin/rabatte/[id]/+server.ts new file mode 100644 index 0000000000000000000000000000000000000000..ae10ac9a61c53d4713911034fab09473bc69edbd --- /dev/null +++ b/src/routes/admin/rabatte/[id]/+server.ts @@ -0,0 +1,9 @@ +import { Discount } from "$lib/server/database/entities/Discount.entity.js"; +import { error } from "@sveltejs/kit"; + +export const DELETE = async (event) => { + const id = event.params.id; + if(!id.match(/^\d+$/)) error(400, "Invalid id"); + await Discount.delete(parseInt(id)); + return new Response(null, { status: 204 }); +}; diff --git a/src/routes/admin/rabatte/[id]/OpeningHoursInput.svelte b/src/routes/admin/rabatte/[id]/OpeningHoursInput.svelte new file mode 100644 index 0000000000000000000000000000000000000000..4bd81c0812a0ce2e0c45078e6935accc0e6723d0 --- /dev/null +++ b/src/routes/admin/rabatte/[id]/OpeningHoursInput.svelte @@ -0,0 +1,68 @@ +<script lang="ts"> + import type { Discount } from "$lib/server/database/entities/Discount.entity"; + import { Button, CloseButton, Input, Table, TableBody, TableBodyCell, TableBodyRow, TableHead, TableHeadCell } from "flowbite-svelte"; + + export let openingHours: Discount["openingHours"]; + for(const day of [1,2,3,4,5,6,7] as const){ + if(!openingHours[day]) openingHours[day] = []; + } + for(const day in openingHours){ + for(const [, end] of openingHours[day as keyof typeof openingHours]!){ + if(end[0] > 24) end[0] -= 24; + } + } +</script> + +<Table class="table-fixed"> + <TableHead> + <TableHeadCell class="w-44">Tag</TableHeadCell> + <TableHeadCell>Öffnungszeiten</TableHeadCell> + </TableHead> + <TableBody> + {#each Object.keys(openingHours).filter(day=>openingHours[day]) as day, i} + <TableBodyRow> + <TableBodyCell class="px-3 py-1"> + {#if ["1","2","3","4","5","6","7"].includes(day)} + {["Sonntag","Montag","Dienstag","Mittwoch","Donnerstag","Freitag","Samstag"][parseInt(day)%7]} + <input name="openingHours[{i}]" type="hidden" value={day} /> + {:else if day==="holiday"} + Feiertag + <CloseButton name="entfernen" class="p-1 my-0" on:click={()=>openingHours = Object.assign(openingHours, { holiday: undefined })} /> + <input name="openingHours[{i}]" type="hidden" value="holiday" /> + {:else} + <Input name="openingHours[{i}]" type="date" bind:value={day} class="w-min inline" /> + <CloseButton name="entfernen" class="p-1 my-0" on:click={()=>openingHours = Object.assign(openingHours, { [day]: undefined })} /> + {/if} + </TableBodyCell> + <TableBodyCell class="px-3 py-1"> + {#each openingHours[day]||[] as [start, end], j} + <div class="w-min mx-1 inline-block rtl:text-right px-2 py-[0.15rem] border focus-within:ring-1 focus-within:border-primary-500 focus-within:ring-primary-500 dark:focus-within:border-primary-500 dark:focus-within:ring-primary-500 bg-gray-50 text-gray-900 dark:bg-gray-700 dark:text-white border-gray-300 dark:border-gray-600 text-sm rounded-lg"> + <input name="openingHours[{i}][{j}][sh]" class="w-[2em] p-1 border-none focus:ring-0 [appearance:textfield] bg-gray-50 text-gray-900 dark:bg-gray-700 dark:text-white" type="number" step="1" min="0" max="23" bind:value={start[0]} required /> + : + <input name="openingHours[{i}][{j}][sm]" class="w-[2em] p-1 border-none focus:ring-0 [appearance:textfield] bg-gray-50 text-gray-900 dark:bg-gray-700 dark:text-white" type="number" step="1" min="0" max="59" bind:value={start[1]} required /> + bis + <!-- no max on this input, 25 o'clock is 1 o'clock of the next day --> + <input name="openingHours[{i}][{j}][eh]" class="w-[2em] p-1 border-none focus:ring-0 [appearance:textfield] bg-gray-50 text-gray-900 dark:bg-gray-700 dark:text-white" type="number" step="1" min="0" max="47" bind:value={end[0]} required /> + : + <input name="openingHours[{i}][{j}][em]" class="w-[2em] p-1 border-none focus:ring-0 [appearance:textfield] bg-gray-50 text-gray-900 dark:bg-gray-700 dark:text-white" type="number" step="1" min="0" max="59" bind:value={end[1]} required /> + <CloseButton name="entfernen" class="p-1 my-0" on:click={()=>openingHours = Object.assign(openingHours, {[day]: openingHours[day].filter((_,k)=>k!==j)})} /> + </div> + {/each} + <Button on:click={()=>openingHours = Object.assign(openingHours, {[day]: [...(openingHours[day]||[]), [[0,0],[0,0]]]})}>+</Button> + </TableBodyCell> + </TableBodyRow> + {/each} + <TableBodyRow> + <TableBodyCell colspan="2" class="px-3 py-1"> + {#if !openingHours.holiday} + <Button on:click={()=>openingHours = Object.assign(openingHours, {holiday: []})}>Feiertag hinzufügen</Button> + {/if} + <Button on:click={()=>{ + let date = new Date(); + while(openingHours[date.toISOString().slice(0,10)]) date.setDate(date.getDate()+1); + openingHours = Object.assign(openingHours, {[date.toISOString().slice(0,10)]: []}); + }}>bestimmten Tag hinzufügen</Button> + </TableBodyCell> + </TableBodyRow> + </TableBody> +</Table> diff --git a/src/routes/admin/rabatte/tag/[id]/+server.ts b/src/routes/admin/rabatte/tag/[id]/+server.ts new file mode 100644 index 0000000000000000000000000000000000000000..f516254ae976f927612ceb65219168d0ae547f8d --- /dev/null +++ b/src/routes/admin/rabatte/tag/[id]/+server.ts @@ -0,0 +1,10 @@ +import { error } from '@sveltejs/kit'; +import type { RequestHandler } from './$types'; +import { Tag } from '$lib/server/database/entities/Discount.entity'; + +export const DELETE: RequestHandler = async (event) => { + const id = event.params.id; + if(!id.match(/^\d+$/)) error(400, 'Invalid id'); + await Tag.delete(parseInt(id)); + return new Response(null, { status: 204 }); +}; diff --git a/src/routes/admin/rallye/station/+page.server.ts b/src/routes/admin/rallye/station/+page.server.ts new file mode 100644 index 0000000000000000000000000000000000000000..5db54091e67942cae1d236b841c968f3cbf01f47 --- /dev/null +++ b/src/routes/admin/rallye/station/+page.server.ts @@ -0,0 +1,38 @@ +import { RallyStation } from '$lib/server/database/entities/RallyStation.entity'; +import { error } from '@sveltejs/kit'; +import type { PageServerLoad } from './$types'; + +export const load = (async () => { + const stations = await RallyStation.getAll(); + return { + stations: stations.map(s=>({...s})), + }; +}) satisfies PageServerLoad; + +export const actions = { + create: async (event) => { + const data = await event.request.formData(); + const name = data.get('name'); + const description = data.get('description'); + if(!name || typeof name !== "string") error(400, "Invalid name"); + if(!description || typeof description !== "string") error(400, "Invalid description"); + const id = await RallyStation.create({ name, description }); + return { id }; + }, + update: async (event) => { + const data = await event.request.formData(); + const id = data.get('id'); + const name = data.get('name'); + const description = data.get('description'); + if(!id || typeof id !== "string") error(400, "Invalid id"); + if(!name || typeof name !== "string") error(400, "Invalid name"); + if(!description || typeof description !== "string") error(400, "Invalid description"); + await RallyStation.update({ id, name, description }); + }, + delete: async (event) => { + const data = await event.request.formData(); + const id = data.get('id'); + if(!id || typeof id !== "string") error(400, "Invalid id"); + await RallyStation.delete(id); + }, +}; diff --git a/src/routes/admin/rallye/station/+page.svelte b/src/routes/admin/rallye/station/+page.svelte new file mode 100644 index 0000000000000000000000000000000000000000..95cdbf7a798707e9fe7c1de789ffc714ed72864c --- /dev/null +++ b/src/routes/admin/rallye/station/+page.svelte @@ -0,0 +1,76 @@ +<script lang="ts"> + import { enhance } from "$app/forms"; + import { addMessage } from "$lib/messages"; + import { Button, Input, Table, TableBody, TableBodyCell, TableBodyRow, TableHead, TableHeadCell, Textarea } from "flowbite-svelte"; + + let { data } = $props(); +</script> + +{#each data.stations as station} +<form id="form-{station.id}" method="post" action="?/update" use:enhance={()=>({result, update})=>{ + if(result.type === "success"){ + addMessage({type: "success", text: "Station wurde gespeichert"}); + update({ reset: false }); + }else{ + addMessage({type: "error", text: "Fehler beim Speichern der Station"}); + console.error(result); + } +}}> + <input type="hidden" name="id" value={station.id} /> +</form> +<form id="delete-form-{station.id}" method="post" action="?/delete" use:enhance={()=>({result, update})=>{ + if(result.type === "success"){ + addMessage({type: "success", text: "Station wurde gelöscht"}); + update(); + }else{ + addMessage({type: "error", text: "Fehler beim Löschen der Station"}); + console.error(result); + } +}}> + <input type="hidden" name="id" value={station.id} /> +</form> +{/each} +<form id="new-form" method="post" action="?/create" use:enhance={()=>({result, update})=>{ + if(result.type === "success"){ + addMessage({type: "success", text: "Station wurde erstellt"}); + update(); + }else{ + addMessage({type: "error", text: "Fehler beim Erstellen der Station"}); + console.error(result); + } +}}></form> + +<Table> + <TableHead> + <TableHeadCell>Station</TableHeadCell> + <TableHeadCell>Beschreibung</TableHeadCell> + <TableHeadCell></TableHeadCell> + </TableHead> + <TableBody> + {#each data.stations as station (station.id)} + <TableBodyRow> + <TableBodyCell> + <Input name="name" type="text" value={station.name} required form="form-{station.id}" /> + </TableBodyCell> + <TableBodyCell> + <Textarea name="description" value={station.description} required form="form-{station.id}" /> + </TableBodyCell> + <TableBodyCell> + <Button type="submit" form="form-{station.id}">Speichern</Button> + <Button type="submit" form="delete-form-{station.id}" color="red">Löschen</Button> + </TableBodyCell> + </TableBodyRow> + {/each} + <TableBodyRow> + <TableBodyCell> + <Input name="name" type="text" required form="new-form" /> + </TableBodyCell> + <TableBodyCell> + <Textarea name="description" required form="new-form" /> + </TableBodyCell> + <TableBodyCell> + <Button type="submit" form="new-form">Erstellen</Button> + </TableBodyCell> + </TableBodyRow> + </TableBody> +</Table> diff --git a/src/routes/admin/schedule/+page.server.ts b/src/routes/admin/schedule/+page.server.ts new file mode 100644 index 0000000000000000000000000000000000000000..8e0f8f1e9e9faec5f7fe696a62b0a8e75b23e918 --- /dev/null +++ b/src/routes/admin/schedule/+page.server.ts @@ -0,0 +1,201 @@ +import { locales } from "$lib/i18n/i18n"; +import { Schedule } from "$lib/server/database/entities/Schedule.entity"; +import { StudyProgram } from "$lib/server/database/entities/StudyProgram.entity"; +import type { Localized } from "$lib/utils"; +import { error } from "@sveltejs/kit"; +import type { PageServerLoad } from "./$types"; +import fs from "node:fs/promises"; +import { renderAllSchedules, type RenderParams } from "$lib/server/schedules"; +import { Config } from "$lib/server/database/entities/Config.entity"; +import { Permission } from "$lib/perms"; + +export const load: PageServerLoad = async ()=>{ + const schedules = await Schedule.getAll(); + const studyPrograms = await StudyProgram.getAll(); + const config = Config.get(); + return { + schedules: schedules.map(s=>({ + id: s.id, + title: s.title, + updated: s.updated, + studyProgram: {...s.studyProgram}, + timeslots: s.timeslots.map(t=>({...t})), + })), + studyPrograms: studyPrograms.map(p=>({id: p.id, name: p.name})), + config: config.scheduleConfig, + }; +}; + +export const actions = { + async branding(event){ + if(!event.locals.user.permissions.has(Permission.UPDATE_SCHEDULE_CONFIG)) throw error(403, "Insufficient permissions"); + const data = await event.request.formData(); + const image = data.get("image"); + if(!(image instanceof File)) throw error(400, "Invalid image"); + const imageBuffer = await image.arrayBuffer(); + await fs.writeFile("static/stundenplaene/branding.png", Buffer.from(imageBuffer)); + await renderAllSchedules(); + }, + async baseConfig(event){ + if(!event.locals.user.permissions.has(Permission.UPDATE_SCHEDULE_CONFIG)) throw error(403, "Insufficient permissions"); + const data = await event.request.formData(); + const fontFamily = data.get("fontFamily"); + const fontSize = Number(data.get("fontSize")); + const fontWeight = data.get("fontWeight"); + const titleFontFamily = data.get("titleFontFamily"); + const titleFontSize = Number(data.get("titleFontSize")); + const titleFontWeight = data.get("titleFontWeight"); + const startTimeText = data.get("startTime"); + const endTimeText = data.get("endTime"); + const dayWidth = Number(data.get("dayWidth")); + const timeColWidth = Number(data.get("timeColWidth")); + const timeSlotHeight = Number(data.get("timeSlotHeight")); + const lineWidth = Number(data.get("lineWidth")); + const brandingImageDistance = Number(data.get("brandingImageDistance")); + const brandingImageScale = Number(data.get("brandingImageScale")); + const branding = {} as Localized; + for(const locale of locales){ + const t = data.get(`branding[${locale}]`); + if(typeof t !== "string") throw error(400, `Invalid branding for locale ${locale}`); + branding[locale] = t; + } + const phoneNumber = data.get("phoneNumber"); + const emailAddress = data.get("emailAddress"); + const website = data.get("website"); + const stateAsOf = {} as Localized; + for(const locale of locales){ + const t = data.get(`stateAsOf[${locale}]`); + if(typeof t !== "string") throw error(400, `Invalid stateAsOf for locale ${locale}`); + stateAsOf[locale] = t; + } + if(typeof fontFamily !== "string") throw error(400, "Invalid fontFamily"); + if(!Number.isInteger(fontSize) || fontSize < 0) throw error(400, "Invalid fontSize"); + if(typeof fontWeight !== "string" || !["100", "200", "300", "400", "500", "600", "700", "800", "900"].includes(fontWeight)) throw error(400, "Invalid fontWeight"); + if(typeof titleFontFamily !== "string") throw error(400, "Invalid titleFontFamily"); + if(!Number.isInteger(titleFontSize) || titleFontSize < 0) throw error(400, "Invalid titleFontSize"); + if(typeof titleFontWeight !== "string" || !["100", "200", "300", "400", "500", "600", "700", "800", "900"].includes(titleFontWeight)) throw error(400, "Invalid titleFontWeight"); + if(typeof startTimeText !== "string" || !/^\d{2}:\d{2}$/.test(startTimeText)) throw error(400, "Invalid startTime"); + if(typeof endTimeText !== "string" || !/^\d{2}:\d{2}$/.test(endTimeText)) throw error(400, "Invalid endTime"); + if(!Number.isFinite(dayWidth) || dayWidth < 0) throw error(400, "Invalid dayWidth"); + if(!Number.isFinite(timeColWidth) || timeColWidth < 0) throw error(400, "Invalid timeColWidth"); + if(!Number.isFinite(timeSlotHeight) || timeSlotHeight < 0) throw error(400, "Invalid timeSlotHeight"); + if(!Number.isInteger(lineWidth) || lineWidth < 0) throw error(400, "Invalid lineWidth"); + if(!Number.isInteger(brandingImageDistance) || brandingImageDistance < 0) throw error(400, "Invalid brandingImageDistance"); + if(!Number.isFinite(brandingImageScale) || brandingImageScale < 0) throw error(400, "Invalid brandingImageScale"); + if(typeof phoneNumber !== "string") throw error(400, "Invalid phoneNumber"); + if(typeof emailAddress !== "string") throw error(400, "Invalid emailAddress"); + if(typeof website !== "string") throw error(400, "Invalid website"); + const startTime = parseInt(startTimeText.split(":")[0]) * 60 + parseInt(startTimeText.split(":")[1]); + const endTime = parseInt(endTimeText.split(":")[0]) * 60 + parseInt(endTimeText.split(":")[1]) || 24*60; + if(startTime >= endTime) throw error(400, "Start must be before end"); + await Config.update({ + scheduleConfig: { + config: { + fontFamily, + fontSize, + fontWeight: fontWeight as "100"|"200"|"300"|"400"|"500"|"600"|"700"|"800"|"900", + titleFontFamily, + titleFontSize, + titleFontWeight: titleFontWeight as "100"|"200"|"300"|"400"|"500"|"600"|"700"|"800"|"900", + startTime, + endTime, + dayWidth, + timeColWidth, + timeSlotHeight, + lineWidth, + brandingImageDistance, + brandingImageScale, + branding, + phoneNumber, + emailAddress, + website, + stateAsOf, + }, + }, + }); + await renderAllSchedules(); + }, + async darkmodeConfig(event){ + if(!event.locals.user.permissions.has(Permission.UPDATE_SCHEDULE_CONFIG)) throw error(403, "Insufficient permissions"); + const data = await event.request.formData(); + const activityBackgroundColor = data.get("activityBackgroundColor"); + const activityTextColor = data.get("activityTextColor"); + const borderColor = data.get("borderColor"); + const textColor = data.get("textColor"); + if(typeof activityBackgroundColor !== "string" || !/^#[0-9a-f]{6}$/i.test(activityBackgroundColor)) throw error(400, "Invalid activityBackgroundColor"); + if(typeof activityTextColor !== "string" || !/^#[0-9a-f]{6}$/i.test(activityTextColor)) throw error(400, "Invalid activityTextColor"); + if(typeof borderColor !== "string" || !/^#[0-9a-f]{6}$/i.test(borderColor)) throw error(400, "Invalid borderColor"); + if(typeof textColor !== "string" || !/^#[0-9a-f]{6}$/i.test(textColor)) throw error(400, "Invalid textColor"); + await Config.update({ + scheduleConfig: { + darkmodeConfig: { + activityBackgroundColor, + activityTextColor, + borderColor, + textColor, + }, + }, + }); + await renderAllSchedules({darkmodeImage: true}); + }, + async lightmodeConfig(event){ + if(!event.locals.user.permissions.has(Permission.UPDATE_SCHEDULE_CONFIG)) throw error(403, "Insufficient permissions"); + const data = await event.request.formData(); + const activityBackgroundColor = data.get("activityBackgroundColor"); + const activityTextColor = data.get("activityTextColor"); + const borderColor = data.get("borderColor"); + const textColor = data.get("textColor"); + if(typeof activityBackgroundColor !== "string" || !/^#[0-9a-f]{6}$/i.test(activityBackgroundColor)) throw error(400, "Invalid activityBackgroundColor"); + if(typeof activityTextColor !== "string" || !/^#[0-9a-f]{6}$/i.test(activityTextColor)) throw error(400, "Invalid activityTextColor"); + if(typeof borderColor !== "string" || !/^#[0-9a-f]{6}$/i.test(borderColor)) throw error(400, "Invalid borderColor"); + if(typeof textColor !== "string" || !/^#[0-9a-f]{6}$/i.test(textColor)) throw error(400, "Invalid textColor"); + await Config.update({ + scheduleConfig: { + lightmodeConfig: { + activityBackgroundColor, + activityTextColor, + borderColor, + textColor, + }, + }, + }); + await renderAllSchedules({lightmodeImage: true}); + }, + async spacing(event){ + if(!event.locals.user.permissions.has(Permission.UPDATE_SCHEDULE_CONFIG)) throw error(403, "Insufficient permissions"); + const data = await event.request.formData(); + const imagePadding = Number(data.get("imagePadding")); + const pdfPadding = Number(data.get("pdfPadding")); + if(!Number.isInteger(imagePadding) || imagePadding < 0) throw error(400, "Invalid imagePadding"); + if(!Number.isInteger(pdfPadding) || pdfPadding < 0) throw error(400, "Invalid pdfPadding"); + const config = Config.get(); + const render: Partial<RenderParams> = {}; + if(imagePadding !== config.scheduleConfig.imagePadding.padding){ + render.darkmodeImage = true; + render.lightmodeImage = true; + } + if(pdfPadding !== config.scheduleConfig.pdfPadding.padding){ + render.pdf = true; + } + await Config.update({ + scheduleConfig: { + imagePadding: {padding: imagePadding}, + pdfPadding: {padding: pdfPadding}, + }, + }); + await renderAllSchedules(render); + }, + async createSchedule(event){ + if(!event.locals.user.permissions.has(Permission.UPDATE_SCHEDULE_CONFIG)) throw error(403, "Insufficient permissions"); + const data = await event.request.formData(); + const studyProgram = Number(data.get("studyProgram")); + if(!Number.isInteger(studyProgram)) throw error(400, "Invalid study program"); + const title = {} as Localized; + for(const locale of locales){ + const t = data.get(`title[${locale}]`); + if(typeof t !== "string") throw error(400, `Invalid title for locale ${locale}`); + title[locale] = t; + } + await Schedule.create(title, studyProgram); + }, +}; diff --git a/src/routes/admin/schedule/+page.svelte b/src/routes/admin/schedule/+page.svelte new file mode 100644 index 0000000000000000000000000000000000000000..15f8e6c67be2b38223ad3192551f472a34d0d100 --- /dev/null +++ b/src/routes/admin/schedule/+page.svelte @@ -0,0 +1,286 @@ +<script lang="ts"> + import { Accordion, AccordionItem, Button, Heading, Input, Label, Modal, P, Select, Table, TableBody, TableBodyCell, TableBodyRow, TableHead, TableHeadCell, Textarea } from "flowbite-svelte"; + import type { Schedule } from "$lib/server/database/entities/Schedule.entity.js"; + import { enhance } from "$app/forms"; + import { Permission } from "$lib/perms"; + import { invalidateAll } from "$app/navigation"; + import { locale, locales } from "$lib/i18n/i18n"; + import { addMessage } from "$lib/messages.js"; + + export let data; + + function pad(num: number){ + return num.toString().padStart(2, "0"); + } + + let showModal = false; + let scheduleToDelete: Schedule; + function confirmDeleteSchedule(schedule: Schedule){ + scheduleToDelete = schedule; + showModal = true; + } + function deleteSchedule(id: number){ + fetch(`/admin/schedule/${id}`, {method: "DELETE"}).then(()=>{ + invalidateAll(); + }); + } + + let brandingImageElement: HTMLImageElement; +</script> + +{#if data.user.permissions.has(Permission.UPDATE_SCHEDULE_CONFIG)} +<Accordion> + <AccordionItem> + <span slot="header">Einstellungen</span> + <Heading tag="h2" customSize="text-2xl font-bold" class="mb-2">Branding</Heading> + <form method="post" action="?/branding" use:enhance={()=>({result})=>{ + if(result.type === "success"){ + location.reload(); + }else{ + addMessage({type: "error", text: "Fehler beim Speichern der Einstellungen"}); + console.error(result); + } + }}> + <img src="/stundenplaene/branding.png" class="mb-2 max-h-28" bind:this={brandingImageElement} alt="Branding für Stundenpläne" /> + <Input type="file" name="image" accept="image/png" on:change={e=>{ + brandingImageElement.src = URL.createObjectURL(e.target?.files[0]); + }} /> + <Button type="submit">Speichern</Button> + </form> + <Heading tag="h2" customSize="text-2xl font-bold" class="mb-2">Allgemein</Heading> + <Button color="alternative" href="/admin/schedule/fonts">Schriftarten verwalten</Button> + <form method="post" action="?/baseConfig" use:enhance={()=>({result})=>{ + if(result.type === "success"){ + location.reload(); + }else{ + addMessage({type: "error", text: "Fehler beim Speichern der Einstellungen"}); + console.error(result); + } + }}> + <div class="flex gap-4"> + <Label class="flex-[1_1_50%]"> + Schriftart + <Input type="text" name="fontFamily" value={data.config.config.fontFamily} required /> + </Label> + <Label class="flex-[1_1_50%]"> + Schriftstärke + <Select name="fontWeight" value={data.config.config.fontWeight} items={["100","200","300","400","500","600","700","800","900"].map(x=>({name: x, value: x}))} required /> + </Label> + <Label class="flex-[1_1_50%]"> + Schriftgröße (px) + <Input type="number" name="fontSize" min={5} max={100} value={data.config.config.fontSize} required /> + </Label> + </div> + <div class="flex gap-4"> + <Label class="flex-[1_1_50%]"> + Schriftart des Titels + <Input type="text" name="titleFontFamily" value={data.config.config.titleFontFamily} required /> + </Label> + <Label class="flex-[1_1_50%]"> + Schriftstärke des Titels + <Select name="titleFontWeight" value={data.config.config.titleFontWeight} items={["100","200","300","400","500","600","700","800","900"].map(x=>({name: x, value: x}))} required /> + </Label> + <Label class="flex-[1_1_50%]"> + Schriftgröße des Titels (px) + <Input type="number" name="titleFontSize" min={5} max={100} value={data.config.config.titleFontSize} required /> + </Label> + </div> + <div class="flex gap-4"> + <Label class="flex-[1_1_50%]"> + Startzeit + <Input type="time" name="startTime" step="1800" value="{pad(Math.floor(data.config.config.startTime/60))}:{pad(data.config.config.startTime%60)}" required /> + </Label> + <Label class="flex-[1_1_50%]"> + Endzeit + <Input type="time" name="endTime" step="1800" value="{pad(Math.floor(data.config.config.endTime/60)%24)}:{pad(data.config.config.endTime%60)}" required /> + </Label> + </div> + <div class="flex gap-4"> + <Label class="flex-[1_1_50%]"> + Breite der Tage (rem) + <Input type="number" name="dayWidth" min={5} step={0.5} value={data.config.config.dayWidth} required /> + </Label> + <Label class="flex-[1_1_50%]"> + Breite der Zeitspalte (rem) + <Input type="number" name="timeColWidth" min={3} step={0.1} value={data.config.config.timeColWidth} required /> + </Label> + <Label class="flex-[1_1_50%]"> + Höhe der Zeilen (rem) + <Input type="number" name="timeSlotHeight" min={1} step={0.05} value={data.config.config.timeSlotHeight} required /> + </Label> + <Label class="flex-[1_1_50%]"> + Linienbreite (px) + <Input type="number" name="lineWidth" min={1} step={1} value={data.config.config.lineWidth} required /> + </Label> + </div> + <div class="flex gap-4"> + <Label class="flex-[1_1_50%]"> + Abstand des Branding Bildes (px) + <Input type="number" name="brandingImageDistance" min={0} step={1} value={data.config.config.brandingImageDistance} required /> + </Label> + <Label class="flex-[1_1_50%]"> + Skalierung des Branding Bildes (%) + <Input type="number" name="brandingImageScale" min={5} step={1} value={data.config.config.brandingImageScale} required /> + </Label> + </div> + <div class="flex gap-4"> + {#each locales as lang} + <Label class="flex-[1_1_50%]"> + Branding Text ({lang}) + <Textarea type="text" name={`branding[${lang}]`} value={data.config.config.branding[lang]} required /> + </Label> + {/each} + </div> + <div class="flex gap-4"> + <Label class="flex-[1_1_50%]"> + Telefonnummer + <Input type="tel" name="phoneNumber" value={data.config.config.phoneNumber} required /> + </Label> + <Label class="flex-[1_1_50%]"> + E-Mail-Adresse + <Input type="email" name="emailAddress" value={data.config.config.emailAddress} required /> + </Label> + <Label class="flex-[1_1_50%]"> + Webseite + <Input type="text" name="website" value={data.config.config.website} required /> + </Label> + </div> + <div class="flex gap-4"> + {#each locales as lang} + <Label class="flex-[1_1_50%]"> + Update Text ({lang}) + <Input type="text" name={`stateAsOf[${lang}]`} value={data.config.config.stateAsOf[lang]} required /> + </Label> + {/each} + </div> + <Button type="submit">Speichern</Button> + </form> + <Heading tag="h2" customSize="text-2xl font-bold" class="mb-2">Farben</Heading> + {#each ["light", "dark"] as mode} + <Heading tag="h3" customSize="text-xl font-bold" class="mb-2">{mode[0].toUpperCase()}{mode.substring(1)} Mode</Heading> + <form method="post" action="?/{mode}modeConfig" use:enhance={()=>({result})=>{ + if(result.type === "success"){ + location.reload(); + }else{ + addMessage({type: "error", text: "Fehler beim Speichern der Einstellungen"}); + console.error(result); + } + }}> + <div class="flex gap-4"> + <Label class="flex-[1_1_50%]"> + Rahmenfarbe + <Input type="color" name="borderColor" value={data.config[`${mode}modeConfig`].borderColor} required /> + </Label> + <Label class="flex-[1_1_50%]"> + Textfarbe + <Input type="color" name="textColor" value={data.config[`${mode}modeConfig`].textColor} required /> + </Label> + </div> + <div class="flex gap-4"> + <Label class="flex-[1_1_50%]"> + Hintergrund von Aktivitäten + <Input type="color" name="activityBackgroundColor" value={data.config[`${mode}modeConfig`].activityBackgroundColor} required /> + </Label> + <Label class="flex-[1_1_50%]"> + Textfarbe von Aktivitäten + <Input type="color" name="activityTextColor" value={data.config[`${mode}modeConfig`].activityTextColor} required /> + </Label> + </div> + <Button type="submit">Speichern</Button> + </form> + {/each} + <Heading tag="h2" customSize="text-2xl font-bold" class="mb-2">Spacing</Heading> + <form method="post" action="?/spacing" use:enhance={()=>({result})=>{ + if(result.type === "success"){ + location.reload(); + }else{ + addMessage({type: "error", text: "Fehler beim Speichern der Einstellungen"}); + console.error(result); + } + }}> + <div class="flex"> + <Label> + Padding der Bilder (px) + <Input type="number" name="imagePadding" min={0} value={data.config.imagePadding.padding} required /> + </Label> + <Label> + Padding der PDFs (px) + <Input type="number" name="pdfPadding" min={0} value={data.config.pdfPadding.padding} required /> + </Label> + </div> + <Button type="submit">Speichern</Button> + </form> + </AccordionItem> +</Accordion> +{/if} + +{#if data.user.permissions.has(Permission.UPDATE_SCHEDULES)} +<div class="mb-6"> + <form method="post" action="?/createSchedule" use:enhance={()=>({result})=>{ + if(result.type === "success"){ + addMessage({type: "success", text: "Einstellungen gespeichert"}); + invalidateAll(); + }else{ + addMessage({type: "error", text: "Fehler beim Speichern der Einstellungen"}); + console.error(result); + } + }}> + <Heading tag="h1" customSize="text-3xl font-bold" class="mb-2">Neuen Stundenplan erstellen</Heading> + <Label> + Studiengang + <Select required name="studyProgram"> + {#each data.studyPrograms as studyProgram} + <option value={studyProgram.id} disabled={data.schedules.some(schedule=>schedule.studyProgram.id===studyProgram.id)}>{studyProgram.name[$locale]}</option> + {/each} + </Select> + </Label> + {#each locales as locale} + <Label> + Titel ({locale}) + <Input required name={`title[${locale}]`} placeholder="Erstsemestereinführung WiSe 2023/24 - Mathematik"/> + </Label> + {/each} + <Button type="submit">Erstellen</Button> + </form> +</div> +{/if} + +<div> + <Heading tag="h1" customSize="text-3xl font-bold" class="mb-2">Vorhandene Stundenpläne</Heading> + <Table> + <TableHead> + <TableHeadCell>Studiengang</TableHeadCell> + <TableHeadCell>Vorschau</TableHeadCell> + {#if data.user.permissions.has(Permission.UPDATE_SCHEDULES)} + <TableHeadCell></TableHeadCell> + {/if} + </TableHead> + <TableBody> + {#each data.schedules as schedule} + <TableBodyRow> + <TableBodyCell>{schedule.studyProgram.name[$locale]}</TableBodyCell> + <TableBodyCell> + <img src="/stundenplaene/{schedule.id}/{$locale}.dark.svg" class="dark:block hidden h-64" /> + <img src="/stundenplaene/{schedule.id}/{$locale}.light.svg" class="block dark:hidden h-64" /> + </TableBodyCell> + {#if data.user.permissions.has(Permission.UPDATE_SCHEDULES)} + <TableBodyCell class="max-w-min"> + <Button href={`/admin/schedule/${schedule.id}`}>Bearbeiten</Button> + <Button color="red" on:click={()=>confirmDeleteSchedule(schedule)}>Löschen</Button> + </TableBodyCell> + {/if} + </TableBodyRow> + {/each} + </TableBody> + </Table> +</div> + +<Modal open={showModal} title="Bestätigen" autoclose outsideclose> + <P> + Soll der Stundenplan für {scheduleToDelete.studyProgram.name[$locale]} wirklich gelöscht werden? + </P> + <svelte:fragment slot="footer"> + <Button>Behalten</Button> + <Button color="alternative" on:click={()=>deleteSchedule(scheduleToDelete.id)}>Löschen</Button> + </svelte:fragment> +</Modal> diff --git a/src/routes/admin/schedule/[id]/+page.server.ts b/src/routes/admin/schedule/[id]/+page.server.ts new file mode 100644 index 0000000000000000000000000000000000000000..149f8fe575c1ccfbc5fa7609a72c6be1bd2d8b2c --- /dev/null +++ b/src/routes/admin/schedule/[id]/+page.server.ts @@ -0,0 +1,94 @@ +import { locales } from "$lib/i18n/i18n"; +import { Permission } from "$lib/perms.js"; +import { Config } from "$lib/server/database/entities/Config.entity.js"; +import { Schedule } from "$lib/server/database/entities/Schedule.entity.js"; +import type { Localized } from "$lib/utils.js"; +import { error, redirect } from "@sveltejs/kit"; + +export async function load(event){ + if(!event.locals.user.permissions.has(Permission.UPDATE_SCHEDULES)) redirect(303, "/admin/schedule"); + const id = Number(event.params.id); + if(!Number.isInteger(id)) error(400, "Invalid schedule id"); + const schedule = await Schedule.getById(id); + if(!schedule) error(404, "Schedule not found"); + const config = Config.get(); + return { + schedule: { + ...schedule, + studyProgram: {...schedule.studyProgram}, + timeslots: schedule.timeslots.map(t=>({...t})), + }, + fresherWeekStart: config.fresherWeekStart, + fresherWeekEnd: config.fresherWeekEnd, + }; +} + +function parseTimeslot(data: FormData){ + const title = {} as Localized; + for(const locale of locales){ + const t = data.get(`title[${locale}]`); + if(typeof t !== "string" || !t) error(400, `Invalid title for locale ${locale}`); + title[locale] = t; + } + const location = {} as Localized; + for(const locale of locales){ + const l = data.get(`location[${locale}]`); + if(typeof l !== "string") error(400, `Invalid location for locale ${locale}`); + location[locale] = l; + } + const date = data.get("date"); + if(typeof date !== "string" || isNaN(Date.parse(date))) error(400, "Invalid date"); + const startStr = data.get("start"); + if(typeof startStr !== "string" || !/^([01]\d|2[0-3]):[0-5]\d$/.test(startStr)) error(400, "Invalid start"); + const endStr = data.get("end"); + if(typeof endStr !== "string" || !/^([01]\d|2[0-3]):[0-5]\d$/.test(startStr)) error(400, "Invalid end"); + const start = Number(startStr.slice(0, 2)) * 60 + Number(startStr.slice(3)); + const end = Number(endStr.slice(0, 2)) * 60 + Number(endStr.slice(3)) || 24*60; + const startUncertainty = Number(data.get("startUncertainty")); + const endUncertainty = Number(data.get("endUncertainty")); + if(!Number.isInteger(startUncertainty) || startUncertainty < 0) error(400, "Invalid start uncertainty"); + if(!Number.isInteger(endUncertainty) || endUncertainty < 0) error(400, "Invalid end uncertainty"); + if(start >= end) error(400, "Start must be before end"); + return { title, location, date, start, end, startUncertainty, endUncertainty }; +} + +export const actions = { + async updateSchedule(event){ + if(!event.locals.user.permissions.has(Permission.UPDATE_SCHEDULES)) error(403, "Insufficient permissions"); + const data = await event.request.formData(); + const id = Number(event.params.id); + if(!Number.isInteger(id)) error(400, "Invalid schedule id"); + const title = {} as Localized; + for(const locale of locales){ + const t = data.get(`title[${locale}]`); + if(typeof t !== "string" || !t) error(400, `Invalid title for locale ${locale}`); + title[locale] = t; + } + await Schedule.update(id, title); + }, + async createTimeslot(event){ + if(!event.locals.user.permissions.has(Permission.UPDATE_SCHEDULES)) error(403, "Insufficient permissions"); + const data = await event.request.formData(); + const scheduleId = Number(event.params.id); + if(!Number.isInteger(scheduleId)) error(400, "Invalid schedule id"); + const timeslot = parseTimeslot(data); + await Schedule.addTimeslot({ + ...timeslot, + schedule: scheduleId, + }); + }, + async updateTimeslot(event){ + if(!event.locals.user.permissions.has(Permission.UPDATE_SCHEDULES)) error(403, "Insufficient permissions"); + const data = await event.request.formData(); + const schedule = Number(event.params.id); + const id = Number(data.get("id")); + if(!Number.isInteger(schedule)) error(400, "Invalid schedule id"); + if(!Number.isInteger(id)) error(400, "Invalid timeslot id"); + const timeslot = parseTimeslot(data); + await Schedule.updateTimeslot({ + ...timeslot, + schedule, + id, + }); + }, +}; diff --git a/src/routes/admin/schedule/[id]/+page.svelte b/src/routes/admin/schedule/[id]/+page.svelte new file mode 100644 index 0000000000000000000000000000000000000000..9a0130941d0e37c73eda03993f329495770ed19b --- /dev/null +++ b/src/routes/admin/schedule/[id]/+page.svelte @@ -0,0 +1,193 @@ +<script lang="ts"> + import { Button, Heading, Input, Label, Table, TableBody, TableBodyCell, TableBodyRow, TableHead, TableHeadCell, Textarea } from "flowbite-svelte"; + import { enhance } from "$app/forms"; + import { locale, locales } from "$lib/i18n/i18n"; + import type { Timeslot } from "$lib/server/database/entities/Schedule.entity.js"; + import { addMessage } from "$lib/messages.js"; + + export let data; + const schedule = Object.assign(data.schedule, {timeslots: data.schedule.timeslots.sort((a,b)=>a.date.localeCompare(b.date))}); + + function pad(num: number){ + return num.toString().padStart(2, "0"); + } + + function confirmDeleteTimeslot(timeslot: Timeslot){ + if(confirm("Wirklich löschen?")){ + deleteTimeslot(timeslot.id); + } + } + function deleteTimeslot(timeslotId: number){ + fetch(`/admin/schedule/${schedule.id}/${timeslotId}`, {method: "DELETE"}).then(()=>location.reload()); + } + + function rerender(){ + fetch("", {method: "POST", body: JSON.stringify({action: "rerender"}), headers: {"Content-Type": "application/json"}}).then(()=>location.reload()); + } +</script> + +<Button href="/admin/schedule">Zurück zur Übersicht</Button> + +<Heading tag="h1" customSize="text-3xl font-bold" class="mb-2">Stundenplan für {schedule.studyProgram.name[$locale]}</Heading> + +<Heading tag="h2" customSize="text-2xl font-bold" class="mb-2">Vorschau</Heading> +<div> + <img src="/stundenplaene/{schedule.id}/{$locale}.dark.svg" class="hidden dark:block" /> + <img src="/stundenplaene/{schedule.id}/{$locale}.light.svg" class="block dark:hidden" /> +</div> +<Button size="sm" on:click={rerender}>Neu rendern</Button> + +<Heading tag="h2" customSize="text-2xl font-bold" class="mb-2">Titel</Heading> +<form method="post" action="?/updateSchedule" use:enhance={()=>({result})=>{ + if(result.type === "success"){ + location.reload(); + }else{ + addMessage({type: "error", text: "Fehler beim Speichern der Einstellungen"}); + console.error(result); + } +}}> + {#each locales as lang} + <Label> + Titel ({lang}) + <Input required name="title[{lang}]" value={schedule.title[lang]} /> + </Label> + {/each} + <Button type="submit">Speichern</Button> +</form> + +<Heading tag="h2" customSize="text-2xl font-bold" class="mb-2">Termin hinzufügen</Heading> +<div> + <form method="post" action="?/createTimeslot" use:enhance={()=>({result})=>{ + if(result.type === "success"){ + location.reload(); + }else{ + addMessage({type: "error", text: "Fehler beim Erstellen"}); + console.error(result); + } + }}> + <Label> + Datum + <Input required type="date" name="date" min={data.fresherWeekStart} max={data.fresherWeekEnd} /> + </Label> + <div class="flex gap-4"> + {#each locales as lang} + <Label class="flex-[1_1_50%]"> + Titel ({lang}) + <Textarea required name="title[{lang}]" class="block w-full disabled:cursor-not-allowed disabled:opacity-50 p-2.5 focus:border-primary-500 focus:ring-primary-500 dark:focus:border-primary-500 dark:focus:ring-primary-500 bg-gray-50 text-gray-900 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 border-gray-300 dark:border-gray-600 text-sm rounded-lg h-12" /> + </Label> + {/each} + </div> + <div class="flex gap-4"> + {#each locales as lang} + <Label class="flex-[1_1_50%]"> + Ort ({lang}) + <Textarea name="location[{lang}]" class="block w-full disabled:cursor-not-allowed disabled:opacity-50 p-2.5 focus:border-primary-500 focus:ring-primary-500 dark:focus:border-primary-500 dark:focus:ring-primary-500 bg-gray-50 text-gray-900 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 border-gray-300 dark:border-gray-600 text-sm rounded-lg h-12" /> + </Label> + {/each} + </div> + <div class="flex gap-4"> + <Label> + Beginn + <Input required type="time" name="start" /> + </Label> + <Label> + Ende + <Input required type="time" name="end" /> + </Label> + <Label> + Fließender Beginn (Minuten) + <Input required type="number" name="startUncertainty" value={0} /> + </Label> + <Label> + Fließendes Ende (Minuten) + <Input required type="number" name="endUncertainty" value={0} /> + </Label> + </div> + <Button type="submit">Erstellen</Button> + </form> +</div> + +{#each schedule.timeslots as timeslot} +<form id={String(timeslot.id)} method="post" action="?/updateTimeslot" use:enhance={()=>({result})=>{ + if(result.type === "success"){ + location.reload(); + }else{ + addMessage({type: "error", text: "Fehler beim Speichern"}); + console.error(result); + } +}}> + <input type="hidden" name="id" value={timeslot.id} /> +</form> +{/each} + +<Heading tag="h2" customSize="text-2xl font-bold" class="mb-2">Vorhandene Termine</Heading> +<Table> + <TableHead> + <TableHeadCell>Datum</TableHeadCell> + <TableHeadCell>Titel</TableHeadCell> + <TableHeadCell>Ort</TableHeadCell> + <TableHeadCell>Zeit</TableHeadCell> + <TableHeadCell></TableHeadCell> + </TableHead> + <TableBody> + {#each schedule.timeslots as timeslot} + <TableBodyRow> + <TableBodyCell> + <Label> + Datum + <Input required type="date" name="date" form={timeslot.id} value={timeslot.date} min={data.fresherWeekStart} max={data.fresherWeekEnd} /> + </Label> + </TableBodyCell> + <TableBodyCell> + <div> + {#each locales as lang} + <Label class="flex-auto"> + Titel ({lang}) + <Textarea required name="title[{lang}]" form={timeslot.id} value={timeslot.title[lang]} class="block w-full disabled:cursor-not-allowed disabled:opacity-50 p-2.5 focus:border-primary-500 focus:ring-primary-500 dark:focus:border-primary-500 dark:focus:ring-primary-500 bg-gray-50 text-gray-900 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 border-gray-300 dark:border-gray-600 text-sm rounded-lg h-12" /> + </Label> + {/each} + </div> + </TableBodyCell> + <TableBodyCell> + <div> + {#each locales as lang} + <Label class="flex-auto"> + Ort ({lang}) + <Textarea name="location[{lang}]" form={timeslot.id} value={timeslot.location?.[lang] ?? ""} class="block w-full disabled:cursor-not-allowed disabled:opacity-50 p-2.5 focus:border-primary-500 focus:ring-primary-500 dark:focus:border-primary-500 dark:focus:ring-primary-500 bg-gray-50 text-gray-900 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 border-gray-300 dark:border-gray-600 text-sm rounded-lg h-12" /> + </Label> + {/each} + </div> + </TableBodyCell> + <TableBodyCell> + <div class="flex gap-3"> + <div> + <Label> + Beginn + <Input required type="time" name="start" form={timeslot.id} value="{pad(Math.floor(timeslot.start/60))}:{pad(timeslot.start%60)}" /> + </Label> + <Label> + Ende + <Input required type="time" name="end" form={timeslot.id} value="{pad(Math.floor(timeslot.end/60)%24)}:{pad(timeslot.end%60)}" /> + </Label> + </div><div> + <Label> + Fließender Beginn (Minuten) + <Input required type="number" name="startUncertainty" form={timeslot.id} value={timeslot.startUncertainty} /> + </Label> + <Label> + Fließendes Ende (Minuten) + <Input required type="number" name="endUncertainty" form={timeslot.id} value={timeslot.endUncertainty} /> + </Label> + </div> + </div> + </TableBodyCell> + <TableBodyCell> + <div class="flex flex-col gap-2"> + <Button type="submit" form={timeslot.id}>Speichern</Button> + <Button color="red" on:click={()=>confirmDeleteTimeslot(timeslot)}>Löschen</Button> + </div> + </TableBodyCell> + </TableBodyRow> + {/each} + </TableBody> +</Table> diff --git a/src/routes/admin/schedule/[id]/+server.ts b/src/routes/admin/schedule/[id]/+server.ts new file mode 100644 index 0000000000000000000000000000000000000000..4b48d9d5adadec58242555157ebad2721b9c9005 --- /dev/null +++ b/src/routes/admin/schedule/[id]/+server.ts @@ -0,0 +1,31 @@ +import { Permission } from "$lib/perms.js"; +import { Schedule } from "$lib/server/database/entities/Schedule.entity.js"; +import { renderSchedule } from "$lib/server/schedules.js"; +import { error } from "@sveltejs/kit"; + +export const DELETE = async (event)=>{ + if(!event.locals.user.permissions.has(Permission.UPDATE_SCHEDULES)) throw error(403, "Insufficient permissions"); + const id = parseInt(event.params.id); + if(!Number.isInteger(id)) throw error(400, "Invalid schedule id"); + await Schedule.delete(id); + return new Response(null, { status: 200 }); +}; + +export const POST = async (event)=>{ + const data = await event.request.json(); + const action = data.action; + if(typeof action !== "string") throw error(400, "Invalid action"); + switch(action){ + case "rerender": { + const id = Number(event.params.id); + if(!Number.isInteger(id)) throw error(400, "Invalid schedule id"); + const schedule = await Schedule.getById(id); + if(!schedule) throw error(404, "Schedule not found"); + await renderSchedule(schedule); + return new Response(undefined, { status: 200 }); + } + default: { + throw error(400, "Invalid action"); + } + } +}; diff --git a/src/routes/admin/schedule/[id]/[timeslot]/+server.ts b/src/routes/admin/schedule/[id]/[timeslot]/+server.ts new file mode 100644 index 0000000000000000000000000000000000000000..485abb1b48cfd577b5c15651267adb6f64040be9 --- /dev/null +++ b/src/routes/admin/schedule/[id]/[timeslot]/+server.ts @@ -0,0 +1,11 @@ +import { Schedule } from "$lib/server/database/entities/Schedule.entity.js"; +import { error } from "@sveltejs/kit"; + +export async function DELETE(event){ + const scheduleId = Number(event.params.id); + if(!Number.isInteger(scheduleId)) throw error(400, "Invalid schedule id"); + const timeslotId = Number(event.params.timeslot); + if(!Number.isInteger(timeslotId)) throw error(400, "Invalid timeslot id"); + await Schedule.deleteTimeslot(scheduleId, timeslotId); + return new Response(null, { status: 200 }); +} diff --git a/src/routes/admin/schedule/fonts/+page.server.ts b/src/routes/admin/schedule/fonts/+page.server.ts new file mode 100644 index 0000000000000000000000000000000000000000..c467050d905786a9f6b7470077291cca2a343d9a --- /dev/null +++ b/src/routes/admin/schedule/fonts/+page.server.ts @@ -0,0 +1,42 @@ +import { FONTS_FOLDER, SUPPORTED_EXTENSIONS } from "$lib/server/schedules"; +import { error } from "@sveltejs/kit"; +import fs from "node:fs/promises"; + +export async function load(){ + const fontFiles = (await fs.readdir(FONTS_FOLDER)).filter(filename => SUPPORTED_EXTENSIONS.includes(filename.split(".").pop()!)); + return { + fonts: fontFiles, + supportedExtensions: SUPPORTED_EXTENSIONS, + }; +} + +const ILLEGAL_FONT_NAME_CHARS = /[^a-z0-9\-_. ]/gi; +export const actions = { + async uploadFont(event){ + const data = await event.request.formData(); + const fontFile = data.get("font"); + let fontName = data.get("fontName"); + if(!fontFile || !fontName) throw error(400, "Missing font file or name"); + if(typeof fontName !== "string") throw error(400, "Invalid font name"); + if(!(fontFile instanceof File)) throw error(400, "Invalid font file"); + const ext = fontFile.name.split(".").pop()!.toLowerCase(); + if(!SUPPORTED_EXTENSIONS.includes(ext)) throw error(400, "Invalid font file extension"); + fontName = (fontName.toLowerCase().endsWith(`.${ext}`) ? fontName.slice(0, -ext.length-1) : fontName).trim(); + fontName = fontName.replace(ILLEGAL_FONT_NAME_CHARS, ""); + const fontPathWithoutExt = `${FONTS_FOLDER}/${fontName}`; + const fontExists = await Promise.all(SUPPORTED_EXTENSIONS.map(ext => fs.stat(`${fontPathWithoutExt}.${ext}`).then(()=>true, ()=>false))).then(results => results.some(result => result)); + if(fontExists) throw error(400, "Font already exists"); + const arrayBuffer = await fontFile.arrayBuffer(); + const buffer = Buffer.from(arrayBuffer); + await fs.writeFile(`${fontPathWithoutExt}.${ext}`, buffer); + }, + async deleteFont(event){ + const data = await event.request.formData(); + const fontName = data.get("name"); + if(!fontName) throw error(400, "Missing font name"); + if(typeof fontName !== "string") throw error(400, "Invalid font name"); + if(fontName.match(ILLEGAL_FONT_NAME_CHARS)) throw error(400, "Invalid font name"); + const fontPath = `${FONTS_FOLDER}/${fontName}`; + await fs.rm(fontPath, { force: true }); + }, +}; diff --git a/src/routes/admin/schedule/fonts/+page.svelte b/src/routes/admin/schedule/fonts/+page.svelte new file mode 100644 index 0000000000000000000000000000000000000000..744d90f9ff4da11c01f346531e747cb62df57b17 --- /dev/null +++ b/src/routes/admin/schedule/fonts/+page.svelte @@ -0,0 +1,69 @@ +<script lang="ts"> + import { enhance } from "$app/forms"; + import { invalidateAll } from "$app/navigation"; + import { addMessage } from "$lib/messages.js"; + import { Button, Input, Label, Table, TableBody, TableBodyCell, TableBodyRow, TableHead, TableHeadCell } from "flowbite-svelte"; + + export let data; + function transformFontName(filename: string): string{ + // remove file extension + // add space before the last one of consequent capital letters + // example: "OpenSansBold" -> "Open Sans Bold" + // example: "HelveticaNeueLTCom" -> "Helvetica Neue LT Com" + return filename + .split(".") + .slice(0, -1) + .join(".") + .replace(/([A-Z])(?=[A-Z][a-z])/g, "$1 ") + .replace(/([a-z])(?=[A-Z])/g, "$1 "); + } + let fontName = ""; +</script> + +<Table> + <TableHead> + <TableHeadCell>Schriftart</TableHeadCell> + <TableHeadCell></TableHeadCell> + </TableHead> + <TableBody> + {#each data.fonts as font, i} + <form method="post" action="?/deleteFont" id={String(i)} use:enhance={()=>({result})=>{ + if(result.type === "success"){ + addMessage({type: "success", text: "Schriftart gelöscht"}); + invalidateAll(); + }else{ + addMessage({type: "error", text: "Fehler beim Löschen"}); + console.error(result); + } + }}> + <input type="hidden" value={font} name="name" /> + </form> + <TableBodyRow> + <TableBodyCell>{font.split(".").slice(0, -1).join(".")}</TableBodyCell> + <TableBodyCell><Button form={i} color="red" type="submit">Löschen</Button></TableBodyCell> + </TableBodyRow> + {/each} + <form method="post" action="?/uploadFont" id="new" use:enhance={()=>({result})=>{ + if(result.type === "success"){ + addMessage({type: "success", text: "Schriftart hochgeladen"}); + invalidateAll(); + }else{ + addMessage({type: "error", text: "Fehler beim Hochladen"}); + console.error(result); + } + }}></form> + <TableBodyRow> + <TableBodyCell> + <Label> + Datei + <Input type="file" name="font" required form="new" accept={data.supportedExtensions.map(x=>`.${x}`).join(",")} on:change={e=>fontName=transformFontName(e.target.files[0].name)} /> + </Label> + <Label> + Name der Schriftart + <Input type="text" name="fontName" required form="new" bind:value={fontName} /> + </Label> + </TableBodyCell> + <TableBodyCell><Button form="new" type="submit">Hochladen</Button></TableBodyCell> + </TableBodyRow> + </TableBody> +</Table> diff --git a/src/routes/admin/setup/+page.server.ts b/src/routes/admin/setup/+page.server.ts new file mode 100644 index 0000000000000000000000000000000000000000..e5af9ebbff13709d9ba081114f3eda818e17b1ad --- /dev/null +++ b/src/routes/admin/setup/+page.server.ts @@ -0,0 +1,118 @@ +import { Config } from "$lib/server/database/entities/Config.entity"; +import { error, redirect } from "@sveltejs/kit"; +import type { PageServerLoad } from "./$types"; +import { User } from "$lib/server/database/entities/User.entity"; +import { PermissionSet } from "$lib/perms"; + +export const load: PageServerLoad = ()=>{ + if(Config.get() !== undefined) redirect(302, "/admin"); + return {}; +}; + +export const actions = { + default: async event => { + if(Config.get() !== undefined) throw error(400, "Setup already completed"); + const data = await event.request.formData(); + const semester = data.get("semester"); + const weekStart = data.get("week-start"); + const weekEnd = data.get("week-end"); + const rallyDate = data.get("rally-date"); + const rallyBriefingDate = data.get("rally-briefing-date"); + const rallyBriefingTime = data.get("rally-briefing-time"); + const shirtSizes = data.get("shirt-sizes"); + if(!semester || !weekStart || !weekEnd || !rallyDate || !rallyBriefingDate || !shirtSizes) throw error(400, "Missing form data"); + if(typeof semester !== "string" || typeof weekStart !== "string" || typeof weekEnd !== "string" || typeof rallyDate !== "string" || typeof rallyBriefingDate !== "string" || typeof rallyBriefingTime !== "string" || typeof shirtSizes !== "string") throw error(400, "Invalid form data"); + if(isNaN(Date.parse(weekStart))) throw error(400, "Invalid week start date"); + if(isNaN(Date.parse(weekEnd))) throw error(400, "Invalid week end date"); + if(isNaN(Date.parse(rallyDate))) throw error(400, "Invalid rally date"); + if(isNaN(Date.parse(rallyBriefingDate))) throw error(400, "Invalid rally briefing date"); + if(!/^(([01]\d|2[0-3]):[0-5]\d)?$/.test(rallyBriefingTime)) throw error(400, "Invalid rally briefing time"); + const shirtSizesArray = shirtSizes.split(";").map(s=>s.trim()).filter(s=>s.length > 0); + if(shirtSizesArray.length < 1) throw error(400, "Invalid shirt sizes"); + await Config.create({ + currentSemester: semester, + fresherWeekStart: weekStart, + fresherWeekEnd: weekEnd, + rallyDate, + rallyBriefingDate, + rallyBriefingTime: rallyBriefingTime.length > 0 ? rallyBriefingTime : null, + shirtSizes: shirtSizesArray, + // defaults + defaultPermissions: 0, + tutorRegistrationOpen: false, + trainingsStart: "09:45", + trainingsEnd: "16:00", + rallyRegistrationOpen: false, + esweLink: "", + esweRegistrationStart: null, + esweStart: "", //TODO + esweEnd: "", //TODO + eswePrice: 0, //TODO + defaultPath: "/information", + headerLinks: ["/information", "/tutor", "/eswe", "/flyer", "/rabatte"], + scheduleConfig: { + config: { + fontFamily: "Arial", + fontWeight: "300", + fontSize: 20, + titleFontFamily: "Arial", + titleFontWeight: "100", + titleFontSize: 45, + startTime: 9 * 60 + 0, + endTime: 22 * 60 + 30, + dayWidth: 16, //rem + timeSlotHeight: 1.25, //rem + timeColWidth: 5.5, //rem + lineWidth: 2, + brandingImageDistance: 30, + branding: { + de: "Fachschaft für\nMathematik/Physik/Informatik\nAugustinerbach 2a, 52062 Aachen", + en: "Student Council for\nMathematics/Physics/Computer Science\nAugustinerbach 2a, 52062 Aachen", + }, + phoneNumber: "Tel: +49 241 80 94506", + emailAddress: "esa@fsmpi.rwth-aachen.de", + website: "www.fsmpi.rwth-aachen.de", + stateAsOf: { + de: "Stand: %%", + en: "State as of: %%", + }, + brandingImageScale: 100, + }, + lightmodeConfig: { + activityBackgroundColor: "#a20f35", + activityTextColor: "#ffffff", + borderColor: "#000000", + textColor: "#000000", + }, + darkmodeConfig: { + activityBackgroundColor: "#a20f35", + activityTextColor: "#ffffff", + borderColor: "#ffffff", + textColor: "#ffffff", + }, + imagePadding: { + padding: 0, + }, + pdfPadding: { + padding: 50, + }, + }, + mailTemplates: { + tutorRegistered: { + replyTo: "-", + subject: { de: "", en: "" }, + text: { de: "", en: "" }, + }, + trainingInformation: { + replyTo: "-", + subject: { de: "", en: "" }, + text: { de: "", en: "" }, + }, + }, + trainingMailReminderDays: 7, + }); + const session = (await event.locals.auth())!; + await User.update(session.user.userId, PermissionSet.all()); + throw redirect(302, "/admin"); + }, +}; diff --git a/src/routes/admin/setup/+page.svelte b/src/routes/admin/setup/+page.svelte new file mode 100644 index 0000000000000000000000000000000000000000..bf137d98bbdaf8f46c64a685309647a9800a604d --- /dev/null +++ b/src/routes/admin/setup/+page.svelte @@ -0,0 +1,46 @@ +<script lang="ts"> + import { Button, Input, Label } from "flowbite-svelte"; +</script> + +<form method="post"> + <div class="mb-4"> + <Label> + Aktuelles Semester + <Input name="semester" type="text" placeholder="WiSe 2023/24" required /> + </Label> + </div> + <div class="mb-4 flex gap-3"> + <Label class="w-full"> + Erster Tag der Erstiwoche + <Input name="week-start" type="date" required /> + </Label> + <Label class="w-full"> + Letzter Tag der Erstiwoche + <Input name="week-end" type="date" required /> + </Label> + </div> + <div class="mb-4"> + <Label> + Datum der Rallye + <Input name="rally-date" type="date" required /> + </Label> + </div> + <div class="mb-4"> + <Label> + Datum und Zeit (optional) des Rallye-Vortreffens + <div class="flex gap-3"> + <Input name="rally-briefing-date" type="date" required /> + <Input name="rally-briefing-time" type="time" /> + </div> + </Label> + </div> + <div class="mb-4"> + <Label> + T-Shirt-Größen (durch Semikolon getrennt) + <Input name="shirt-sizes" type="text" placeholder="XS;S;M;L;XL;XXL" required /> + </Label> + </div> + <div class="mb-4"> + <Button type="submit">Speichern</Button> + </div> +</form> diff --git a/src/routes/admin/studyprogram/+page.server.ts b/src/routes/admin/studyprogram/+page.server.ts new file mode 100644 index 0000000000000000000000000000000000000000..f7875114f921d18bcc92f2565dc237d894e2da35 --- /dev/null +++ b/src/routes/admin/studyprogram/+page.server.ts @@ -0,0 +1,49 @@ +import { locales } from "$lib/i18n/i18n"; +import { Permission } from "$lib/perms.js"; +import { StudyProgram } from "$lib/server/database/entities/StudyProgram.entity.js"; +import type { Localized } from "$lib/utils.js"; +import { error } from "@sveltejs/kit"; + +export async function load(){ + const studyPrograms = await StudyProgram.getAll(); + return { + studyPrograms: studyPrograms.map(p=>({...p})), + }; +} + +export const actions = { + async updateStudyProgram(event){ + if(!event.locals.user.permissions.has(Permission.UPDATE_STUDY_PROGRAMS)) error(403, "Insufficient permissions"); + const data = await event.request.formData(); + const id = Number(data.get("id") ?? undefined); + if(!Number.isInteger(id)) error(400, "Invalid study program id"); + const name = {} as Localized; + for(const locale of locales){ + const n = data.get(`name[${locale}]`); + if(typeof n !== "string" || !n) error(400, `Invalid name for locale ${locale}`); + name[locale] = n; + } + const tutorsWanted = Number(data.get("tutorsWanted") ?? undefined); + if(!Number.isInteger(tutorsWanted) || tutorsWanted < 0) error(400, "Invalid tutors wanted"); + const tutorialNamesString = data.get("tutorialNames"); + if(!tutorialNamesString || typeof tutorialNamesString !== "string") error(400, "Invalid tutorial names"); + const tutorialNames = tutorialNamesString.split(";").map(s=>s.trim()).filter(s=>s); + await StudyProgram.update({id, name, tutorsWanted, tutorialNames}); + }, + async createStudyProgram(event){ + if(!event.locals.user.permissions.has(Permission.UPDATE_STUDY_PROGRAMS)) error(403, "Insufficient permissions"); + const data = await event.request.formData(); + const name = {} as Localized; + for(const locale of locales){ + const n = data.get(`name[${locale}]`); + if(typeof n !== "string" || !n) error(400, `Invalid name for locale ${locale}`); + name[locale] = n; + } + const tutorsWanted = Number(data.get("tutorsWanted")); + if(!Number.isInteger(tutorsWanted) || tutorsWanted < 0) error(400, "Invalid tutors wanted"); + const tutorialNamesString = data.get("tutorialNames"); + if(!tutorialNamesString || typeof tutorialNamesString !== "string") error(400, "Invalid tutorial names"); + const tutorialNames = tutorialNamesString.split(";").map(s=>s.trim()).filter(s=>s); + await StudyProgram.create({name, tutorsWanted, tutorialNames}); + }, +}; diff --git a/src/routes/admin/studyprogram/+page.svelte b/src/routes/admin/studyprogram/+page.svelte new file mode 100644 index 0000000000000000000000000000000000000000..18cad2512e627edab478b79c9a30c0570b9c63d8 --- /dev/null +++ b/src/routes/admin/studyprogram/+page.svelte @@ -0,0 +1,106 @@ +<script lang="ts"> + import { enhance } from '$app/forms'; + import { invalidateAll } from '$app/navigation'; + import { locales } from '$lib/i18n/i18n.js'; + import { addMessage } from '$lib/messages.js'; + import type { StudyProgram } from '$lib/server/database/entities/StudyProgram.entity.js'; + import { Button, Input, Label, Table, TableBody, TableBodyCell, TableBodyRow, TableHead, TableHeadCell } from 'flowbite-svelte'; + + export let data; + + function confirmStudyProgramDelete(studyProgram: StudyProgram) { + if (confirm('Wirklich löschen?')) { // TODO + deleteStudyProgram(studyProgram.id); + } + } + function deleteStudyProgram(id: number) { + fetch(`/admin/studyprogram/${id}`, { method: 'DELETE' }).then(() => { + invalidateAll(); + }); + } +</script> + +{#each data.studyPrograms as studyProgram (studyProgram.id)} +<form method="post" action="?/updateStudyProgram" id={String(studyProgram.id)} use:enhance={()=>({result, update})=>{ + if(result.type === "success"){ + addMessage({type: "success", text: "Studiengang gespeichert"}); + update({ reset: false }); + }else{ + addMessage({type: "error", text: "Fehler beim Speichern"}); + console.error(result); + } +}}> + <input type="hidden" name="id" value={studyProgram.id} /> +</form> +{/each} +<form method="post" action="?/createStudyProgram" id="new" use:enhance={()=>({result})=>{ + if(result.type === "success"){ + addMessage({type: "success", text: "Studiengang erstellt"}); + invalidateAll(); + }else{ + addMessage({type: "error", text: "Fehler beim Erstellen"}); + console.error(result); + } +}}></form> + +<Table> + <TableHead> + <TableHeadCell>Name</TableHeadCell> + <TableHeadCell>gewünschte Tutorenzahl</TableHeadCell> + <TableHeadCell></TableHeadCell> + </TableHead> + <TableBody> + {#each data.studyPrograms as studyProgram (studyProgram.id)} + <TableBodyRow> + <TableBodyCell> + {#each locales as lang} + <Label> + Name ({lang}) + <Input required name="name[{lang}]" value={studyProgram.name[lang]} form={studyProgram.id} /> + </Label> + {/each} + </TableBodyCell> + <TableBodyCell> + <Label> + gewünschte Tutorenzahl + <Input required type="number" name="tutorsWanted" min={0} value={studyProgram.tutorsWanted} form={studyProgram.id} /> + </Label> + <Label> + Tutoriennamen (mit Semikolon getrennt) + <Input required name="tutorialNames" value={studyProgram.tutorialNames.join(';')} form={studyProgram.id} /> + </Label> + </TableBodyCell> + <TableBodyCell> + <Button type="submit" form={studyProgram.id}>Speichern</Button> + <Button color="red" on:click={()=>confirmStudyProgramDelete(studyProgram)}>Löschen</Button> + </TableBodyCell> + </TableBodyRow> + {/each} + {#key "new"} + + <TableBodyRow> + <TableBodyCell> + {#each locales as lang} + <Label> + Name ({lang}) + <Input required name="name[{lang}]" form="new" /> + </Label> + {/each} + </TableBodyCell> + <TableBodyCell> + <Label> + gewünschte Tutorenzahl + <Input required type="number" name="tutorsWanted" min={0} form="new" /> + </Label> + <Label> + Tutoriennamen (mit Semikolon getrennt) + <Input required name="tutorialNames" form="new" /> + </Label> + </TableBodyCell> + <TableBodyCell> + <Button type="submit" form="new">Erstellen</Button> + </TableBodyCell> + </TableBodyRow> + {/key} + </TableBody> +</Table> diff --git a/src/routes/admin/studyprogram/[id]/+server.ts b/src/routes/admin/studyprogram/[id]/+server.ts new file mode 100644 index 0000000000000000000000000000000000000000..cff06824c381bd826ede0a62c481ce98cfa7ec40 --- /dev/null +++ b/src/routes/admin/studyprogram/[id]/+server.ts @@ -0,0 +1,9 @@ +import { StudyProgram } from "$lib/server/database/entities/StudyProgram.entity.js"; +import { error } from "@sveltejs/kit"; + +export async function DELETE(event){ + const id = Number(event.params.id); + if(!Number.isInteger(id)) error(400, "Invalid study program id"); + await StudyProgram.delete(id); + return new Response(null, { status: 200 }); +} diff --git a/src/routes/admin/templates/+page.server.ts b/src/routes/admin/templates/+page.server.ts new file mode 100644 index 0000000000000000000000000000000000000000..608799ffb9adfc176d087ddea6e91f2087002330 --- /dev/null +++ b/src/routes/admin/templates/+page.server.ts @@ -0,0 +1,58 @@ +import { locales } from '$lib/i18n/i18n'; +import type { MailTemplate } from '$lib/mail'; +import { Config } from '$lib/server/database/entities/Config.entity'; +import { StudyProgram } from '$lib/server/database/entities/StudyProgram.entity'; +import { TutorTraining } from '$lib/server/database/entities/TutorTraining.entity'; +import type { Localized } from '$lib/utils'; +import { error } from '@sveltejs/kit'; +import type { PageServerLoad } from './$types'; +import { Permission } from '$lib/perms'; + +export const load = (async () => { + const config = Config.get(); + const studyPrograms = await StudyProgram.getAll(); + const trainings = await TutorTraining.getAll(); + return { + templates: config.mailTemplates, + trainingMailReminderDays: config.trainingMailReminderDays, + studyPrograms: studyPrograms.map(sp => ({ id: sp.id, name: sp.name })), + trainings: trainings.map(t => ({ id: t.id, date: t.date, location: t.location })), + }; +}) satisfies PageServerLoad; + +function parseMail(data: FormData): MailTemplate { + const replyTo = data.get('replyTo'); + if (typeof replyTo !== 'string') error(400, 'Missing replyTo'); + const subject = {} as Localized; + for(const locale of locales){ + const s = data.get(`subject[${locale}]`); + if(typeof s !== "string") error(400, `Missing subject for locale ${locale}`); + subject[locale] = s; + } + const text = {} as Localized; + for(const locale of locales){ + const t = data.get(`text[${locale}]`); + if(typeof t !== "string") error(400, `Missing text for locale ${locale}`); + text[locale] = t; + } + return { replyTo, subject, text }; +} + +export const actions = { + updateTutorRegistered: async event => { + if(!event.locals.user.permissions.has(Permission.UPDATE_MAIL_TEMPLATES)) error(403, "Insufficient permissions"); + const data = await event.request.formData(); + const template = parseMail(data); + await Config.update({ mailTemplates: { tutorRegistered: template } }); + }, + updateTrainingInformation: async event => { + if(!event.locals.user.permissions.has(Permission.UPDATE_MAIL_TEMPLATES)) error(403, "Insufficient permissions"); + const data = await event.request.formData(); + const template = parseMail(data); + const days = data.get("reminderDays"); + if(typeof days !== "string") error(400, "Missing reminderDays"); + const reminderDays = Number(days); + if(!Number.isInteger(reminderDays) || reminderDays < 1) error(400, "Invalid reminderDays"); + await Config.update({ mailTemplates: { trainingInformation: template }, trainingMailReminderDays: reminderDays }); + }, +}; diff --git a/src/routes/admin/templates/+page.svelte b/src/routes/admin/templates/+page.svelte new file mode 100644 index 0000000000000000000000000000000000000000..69d746440e38ce7435cf406841465f8ffca44583 --- /dev/null +++ b/src/routes/admin/templates/+page.svelte @@ -0,0 +1,69 @@ +<script lang="ts"> + import { enhance } from "$app/forms"; + import { invalidateAll } from "$app/navigation"; + import MailInput from "$lib/components/MailInput.svelte"; + import { addMessage } from "$lib/messages"; + import { Permission } from "$lib/perms.js"; + import { Input, Label, P, TabItem, Tabs } from "flowbite-svelte"; + + let { data } = $props(); + + let templates = $state(data.templates); + let canEdit = data.user.permissions.has(Permission.UPDATE_MAIL_TEMPLATES); +</script> + +<Tabs> + <TabItem title="Tutoranmeldung" open> + <P class="mb-2">Die Email, die versendet wird, wenn sich jemand als Tutor anmeldet.</P> + <form method="post" action="?/updateTutorRegistered" use:enhance={()=>({result})=>{ + if(result.type === "success"){ + addMessage({type: "success", text: "Änderungen gespeichert"}); + invalidateAll(); + }else{ + addMessage({type: "error", text: "Fehler beim Speichern"}); + console.error(result); + } + }}> + <MailInput + bind:subject={templates.tutorRegistered.subject} + bind:text={templates.tutorRegistered.text} + bind:replyTo={templates.tutorRegistered.replyTo} + replyToOptions={["esa@fsmpi.rwth-aachen.de", data.user.email]} + allowCustomReplyTo + studyPrograms={data.studyPrograms} + trainings={data.trainings} + buttonText="Speichern" + buttonType="submit" + disabled={!canEdit} + /> + </form> + </TabItem> + <TabItem title="Schulungserinnerung"> + <P class="mb-2">Die Mail, die versendet wird, um Tutoren an ihre Schulung zu erinnern.</P> + <form method="post" action="?/updateTrainingInformation" use:enhance={()=>({result})=>{ + if(result.type === "success"){ + addMessage({type: "success", text: "Änderungen gespeichert"}); + invalidateAll(); + }else{ + addMessage({type: "error", text: "Fehler beim Speichern"}); + console.error(result); + } + }}> + <Label> + Wie viele Tage vor Schulung verschicken? + <Input name="reminderDays" value={data.trainingMailReminderDays} disabled={!canEdit} type="number" min="1" /> + </Label> + <MailInput + bind:subject={templates.trainingInformation.subject} + bind:text={templates.trainingInformation.text} + bind:replyTo={templates.trainingInformation.replyTo} + replyToOptions={["esa@fsmpi.rwth-aachen.de", data.user.email]} + allowCustomReplyTo + studyPrograms={data.studyPrograms} + trainings={data.trainings} + buttonText="Speichern" + buttonType="submit" + disabled={!canEdit} + /> + </TabItem> +</Tabs> diff --git a/src/routes/admin/tutor/+page.server.ts b/src/routes/admin/tutor/+page.server.ts new file mode 100644 index 0000000000000000000000000000000000000000..ab64701e7c9f9db1a29097356fb675e65283b69a --- /dev/null +++ b/src/routes/admin/tutor/+page.server.ts @@ -0,0 +1,18 @@ +import { Permission } from "$lib/perms"; +import { Config } from "$lib/server/database/entities/Config.entity"; +import { StudyProgram } from "$lib/server/database/entities/StudyProgram.entity"; +import { Tutor } from "$lib/server/database/entities/Tutor.entity"; +import { pick } from "$lib/utils"; +import type { PageServerLoad } from "./$types"; + +export const load: PageServerLoad = async event => { + let tutors = await Tutor.getAll(); + const shirtSizes = Config.get().shirtSizes.map(s=>[s, tutors.filter(t=>t.shirtSize===s).length] as const); + if(!event.locals.user.permissions.has(Permission.VIEW_TUTOR_DETAILS)) tutors = tutors.map(t=>pick(t, ["id", "firstname", "lastname", "nickname", "studyProgram", "training", "notes"])); + const studyPrograms = await StudyProgram.getAll(); + return { + tutors: event.locals.user.permissions.has(Permission.VIEW_TUTORS) ? tutors.map(t=>({...t, studyProgram: {...t.studyProgram}, training: t.training ? {...t.training} : null })) : [], + studyPrograms: studyPrograms.map(p=>({...p, tutorCount: tutors.filter(t=>t.studyProgram.id===p.id).length})), + shirtSizes, + }; +}; diff --git a/src/routes/admin/tutor/+page.svelte b/src/routes/admin/tutor/+page.svelte new file mode 100644 index 0000000000000000000000000000000000000000..0f4e348643a5bab3c7eb97eb1a3e6c8edc2720a8 --- /dev/null +++ b/src/routes/admin/tutor/+page.svelte @@ -0,0 +1,227 @@ +<script lang="ts"> + import { Button, Checkbox, Heading, Label, Modal, P, Radio, Table, TableBody, TableBodyCell, TableBodyRow, TableHead, TableHeadCell } from "flowbite-svelte"; + import { locale } from "$lib/i18n/i18n"; + import type { ObjectToDotProp } from "$lib/utils"; + import { Permission } from "$lib/perms.js"; + import { toCSV } from "$lib/csv"; + import type { Tutor } from "$lib/server/database/entities/Tutor.entity"; + + export let data; + let sortedTutors = data.tutors; + + $: dateFormatter = new Intl.DateTimeFormat($locale, { day: "2-digit", month: "2-digit", year: "numeric" }); + type SortProp = string|number|null|undefined; + type SortKey = ObjectToDotProp<typeof data.tutors[number], SortProp>; + let sortKey: SortKey = "firstname"; + let sortDirection: -1|1 = 1; + const sortTable = (key: SortKey) => { + if (sortKey === key) { + sortDirection *= -1; + } else { + sortKey = key; + sortDirection = 1; + } + } + $: { + sortedTutors = [...sortedTutors].sort((a, b) => { + const aVal = sortKey!.split(".").reduce((val, key) => val?.[key], a) as unknown as SortProp; + const bVal = sortKey!.split(".").reduce((val, key) => val?.[key], b) as unknown as SortProp; + if(aVal == null && bVal == null) return 0; + else if(aVal == null) return sortDirection; + else if(bVal == null) return -sortDirection; + else if(aVal < bVal) return -sortDirection; + else if(aVal > bVal) return sortDirection; + else return 0; + }); + } + + function getClassName(key: SortKey, sortKey: SortKey, sortDirection: -1|1){ + if(sortKey === key){ + return sortDirection === 1 ? "thc sorting-asc" : "thc sorting-desc"; + } + return "thc"; + } + + let showExportModal = false; + let exportType = "json"; + const fields: { prop: ObjectToDotProp<Tutor>, label: string, value: string }[] = [ + { prop: "firstname", label: "Vorname", value: "firstname" }, + { prop: "lastname", label: "Nachname", value: "lastname" }, + { prop: "nickname", label: "Spitzname", value: "nickname" }, + { prop: "studyProgram.name.de", label: "Fach", value: "studyProgram" }, + { prop: "training.date", label: "Schulung", value: "training" }, + { prop: "notes", label: "Notiz", value: "notes" }, + ...(data.user.permissions.has(Permission.VIEW_TUTOR_DETAILS) ? [ + { prop: "gender", label: "Geschlecht", value: "gender" }, + { prop: "mentor", label: "Mentor", value: "mentor" }, + { prop: "shirtSize", label: "Shirt", value: "shirtSize" }, + { prop: "coTutorWish", label: "Co-Tutor", value: "coTutorWish" }, + { prop: "trained", label: "Geschult", value: "trained" }, + { prop: "degree", label: "Abschluss", value: "degree" }, + { prop: "email", label: "E-Mail", value: "email" }, + { prop: "phone", label: "Telefon", value: "phone" }, + { prop: "address", label: "Adresse", value: "address" }, + { prop: "birthday", label: "Geburtstag", value: "birthday" }, + { prop: "dietaryRestriction", label: "Essgewohnheiten", value: "dietaryRestriction" }, + ] satisfies {prop: ObjectToDotProp<Tutor>, label: string, value: string}[] : []), + ]; + let selectedFields: string[] = fields.map(f => f.value); + function exportTutors(){ + const tutors = data.tutors.map(t=>{ + const tutor: Record<string, unknown> = {}; + for(const field of fields){ + if(selectedFields.includes(field.value)){ + tutor[field.value] = field.prop.split(".").reduce((val, key) => val?.[key], t); + } + } + return tutor; + }); + const parsed = exportType === "json" ? JSON.stringify(tutors, null, 2) : toCSV(tutors, selectedFields); + const blob = new Blob([parsed], { type: exportType === "json" ? "application/json" : "text/csv" }); + const url = URL.createObjectURL(blob); + Object.assign(document.createElement("a"), { + href: url, + download: `tutors.${exportType}`, + }).click(); + URL.revokeObjectURL(url); + } + + let showMatchModal = false; + function matchTutors(){ + + } + function matchRemainingTutors(){ + + } +</script> + +<style> + div :global(.thc) { + cursor: pointer; + position: relative; + } + div :global(.thc::after) { + content: ""; + position: absolute; + right: 0; + padding-right: 1rem; + } + div :global(.sorting-asc::after) { + content: " ▲"; + } + div :global(.sorting-desc::after) { + content: " ▼"; + } +</style> + +{#if data.user.permissions.has(Permission.VIEW_TUTORS)} +<Modal bind:open={showExportModal} autoclose outsideclose title="Tutoren exportieren" classBody="space-y-1"> + <Heading tag="h2" customSize="text-2xl font-bold">Format</Heading> + <Radio bind:group={exportType} value="json">JSON</Radio> + <Radio bind:group={exportType} value="csv">CSV</Radio> + <Heading tag="h2" customSize="text-2xl font-bold">Felder</Heading> + {#each fields as field} + <Label class="text-sm rtl:text-right font-medium text-gray-900 dark:text-gray-300 flex items-center"> + <input type="checkbox" value={field.value} bind:group={selectedFields} class="w-4 h-4 bg-gray-100 border-gray-300 dark:ring-offset-gray-800 focus:ring-2 me-2 dark:bg-gray-600 dark:border-gray-500 rounded text-primary-600 focus:ring-primary-500 dark:focus:ring-primary-600" /> + {field.label} + </Label> + {/each} + <svelte:fragment slot="footer"> + <Button on:click={exportTutors}>Exportieren</Button> + <Button color="light">Abbrechen</Button> + </svelte:fragment> +</Modal> +{#if data.user.permissions.has(Permission.UPDATE_TUTOR)} +<Modal bind:open={showMatchModal} autoclose outsideclose title="Co-Tutoren automatisch zuweisen" classBody="space-y-1"> + <Heading tag="h2" customSize="text-2xl font-bold">Wünsche erkennen</Heading> + <P class="mb-2"> + Hier können Co-Tutoren automatisch anhand ihrer Wünsche zugeordnet werden. Tutoren, die sich gegenseitig als Co-Tutor wünschen, werden einander zugeordnet. Wenn kein Wunsch erkannt wird oder der Wunsch nicht automatisch zugewiesen werden kann, wird der Tutor <i>nicht</i> zugeordnet. + </P> + <P class="mb-2" color="text-red-700 dark:text-red-500"> + <b>Vorsicht:</b> Alle bisherigen Zuordnungen werden gelöscht und durch die automatischen Zuordnungen ersetzt. + </P> + <Heading tag="h2" customSize="text-2xl font-bold">Verbleibende Tutoren zuweisen</Heading> + <P class="mb-2"> + Hier werden soweit möglich alle Tutoren, die noch keinem Tutorium zugeordnet sind, zufällig zugeordnet. Bitte prüfe vorher, dass alle Wünsche berücksichtigt sind. + </P> + <svelte:fragment slot="footer"> + <Button on:click={matchTutors}>Wünsche erkennen</Button> + <Button on:click={matchRemainingTutors}>Verbleibende Tutoren zuweisen</Button> + <Button color="light">Abbrechen</Button> + </svelte:fragment> +</Modal> +{/if} + +<div class="flex gap-2 mb-3"> + {#if data.user.permissions.has(Permission.UPDATE_TUTOR)} + <Button href="/admin/tutor/new">Neuer Tutor</Button> + <Button on:click={()=>showMatchModal=true}>Tutorien zuweisen</Button> + {/if} + <Button on:click={()=>showExportModal=true}>Exportieren</Button> +</div> +{/if} + +<Table class="mb-6"> + <TableHead> + {#each data.studyPrograms as studyProgram} + <TableHeadCell>{studyProgram.name[$locale]}</TableHeadCell> + {/each} + <TableHeadCell>Gesamt</TableHeadCell> + </TableHead> + <TableBody> + <TableBodyRow> + {#each data.studyPrograms as studyProgram} + <TableBodyCell>{studyProgram.tutorCount}{#if studyProgram.tutorsWanted>0}{" von "}{studyProgram.tutorsWanted}{/if}</TableBodyCell> + {/each} + <TableBodyCell>{data.studyPrograms.map(p=>p.tutorCount).reduce((a,b)=>a+b, 0)}</TableBodyCell> + </TableBodyRow> + </TableBody> +</Table> + +<Table class="mb-6"> + <TableHead> + {#each data.shirtSizes as size} + <TableHeadCell>{size[0]}</TableHeadCell> + {/each} + </TableHead> + <TableBody> + <TableBodyRow> + {#each data.shirtSizes as size} + <TableBodyCell>{size[1]}</TableBodyCell> + {/each} + </TableBodyRow> + </TableBody> +</Table> + +{#if data.user.permissions.has(Permission.VIEW_TUTORS)} +<Table class="mb-6" hoverable> + <TableHead> + <TableHeadCell on:click={()=>sortTable("firstname")} class={getClassName("firstname", sortKey, sortDirection)}>Vorname</TableHeadCell> + <TableHeadCell on:click={()=>sortTable("lastname")} class={getClassName("lastname", sortKey, sortDirection)}>Nachname</TableHeadCell> + <TableHeadCell on:click={()=>sortTable(`studyProgram.name.${$locale}`)} class={getClassName(`studyProgram.name.${$locale}`, sortKey, sortDirection)}>Fach</TableHeadCell> + <TableHeadCell on:click={()=>sortTable("shirtSize")} class={getClassName("shirtSize", sortKey, sortDirection)}>Shirt</TableHeadCell> + <TableHeadCell on:click={()=>sortTable("coTutorWish")} class={getClassName("coTutorWish", sortKey, sortDirection)}>Co-Tutor</TableHeadCell> + <TableHeadCell on:click={()=>sortTable("training.date")} class={getClassName("training.date", sortKey, sortDirection)}>Schulung</TableHeadCell> + <TableHeadCell on:click={()=>sortTable("notes")} class={getClassName("notes", sortKey, sortDirection)}>Notiz</TableHeadCell> + <TableHeadCell></TableHeadCell> + </TableHead> + <TableBody> + {#each sortedTutors as tutor} + <TableBodyRow> + <TableBodyCell>{tutor.firstname}{tutor.nickname?` (${tutor.nickname})`:""}</TableBodyCell> + <TableBodyCell>{tutor.lastname}</TableBodyCell> + <TableBodyCell>{tutor.studyProgram.name[$locale]}</TableBodyCell> + <TableBodyCell>{tutor.shirtSize}</TableBodyCell> + <TableBodyCell>{tutor.coTutorWish || ""}</TableBodyCell> + <TableBodyCell>{tutor.training?dateFormatter.format(new Date(tutor.training.date)):"Bereits geschult"}</TableBodyCell> + <TableBodyCell>{tutor.notes}</TableBodyCell> + <TableBodyCell> + <Button href={`/admin/tutor/${tutor.id}`} color="light" size="xs">Bearbeiten</Button> + </TableBodyCell> + </TableBodyRow> + {/each} + </TableBody> +</Table> +{/if} + +<!-- TODO: auto match co-tutors based on wishes ("npm i modern-diacritics" for names) --> diff --git a/src/routes/admin/tutor/[id]/+page.server.ts b/src/routes/admin/tutor/[id]/+page.server.ts new file mode 100644 index 0000000000000000000000000000000000000000..71bfa6a3138be9023186242f3ac92227849f2f6c --- /dev/null +++ b/src/routes/admin/tutor/[id]/+page.server.ts @@ -0,0 +1,68 @@ +import { Config } from "$lib/server/database/entities/Config.entity.js"; +import { StudyProgram } from "$lib/server/database/entities/StudyProgram.entity.js"; +import { Tutor } from "$lib/server/database/entities/Tutor.entity.js"; +import { TutorTraining } from "$lib/server/database/entities/TutorTraining.entity.js"; +import { error } from "@sveltejs/kit"; + +export const load = async (request) => { + const id = Number(request.params.id); + if(isNaN(id)) error(400, "Bad Request"); + const tutor = await Tutor.getById(id); + if(!tutor) error(404, "Not Found"); + const trainings = await TutorTraining.getAll(); + const studyPrograms = await StudyProgram.getAll(); + return { + tutor: {...tutor, studyProgram: {...tutor.studyProgram}, training: tutor.training ? {...tutor.training} : null}, + trainings: trainings.map(t=>({...t, participants: t.participants.length})), + shirtSizes: Config.get().shirtSizes, + studyPrograms: studyPrograms.map(s=>({...s})), + }; +}; + +export const actions = { + default: async (request) => { + const data = await request.request.formData(); + const firstname = data.get("firstname") as string ?? undefined; + const lastname = data.get("lastname") as string ?? undefined; + const nickname = data.get("nickname") || ""; + // TODO update birthday format + const birthday = Date.parse(`${data.get("birthday-year")}-${data.get("birthday-month")}-${data.get("birthday-day")}`); + const email = data.get("email") ?? undefined; + const phone = data.get("phone") ?? undefined; + const shirtSize = data.get("shirtSize") ?? undefined; + const address = data.get("address") ?? undefined; + const gender = data.get("gender") ?? undefined; + const studyProgram = data.get("studyProgram") ?? undefined; + const degree = data.get("degree") ?? undefined; + const training = data.get("training") ?? undefined; + const trained = !!data.get("trained") ?? undefined; + const mentor = !!data.get("mentor") ?? undefined; + const coTutorWish = data.get("coTutorWish") || ""; + const comment = data.get("comment") || ""; + const tutorId = parseInt(request.params.id); + // validation not necessary, because the frontend only allows valid values and this is + // the admin panel. i think we can trust admins to not craft invalid requests by hand. + + // TODO update in database + // eslint-disable-next-line no-unused-vars + const updatedTutor = await Tutor.update({ + id: tutorId, + firstname, + lastname, + nickname, + birthday, + email, + phone, + shirtSize, + address, + gender, + studyProgram, + degree, + training, + trained, + mentor, + coTutorWish, + comment, + }); + }, +}; diff --git a/src/routes/admin/tutor/[id]/+page.svelte b/src/routes/admin/tutor/[id]/+page.svelte new file mode 100644 index 0000000000000000000000000000000000000000..a507e7d3305f8a4fbfbac814754a464eb1ffadaf --- /dev/null +++ b/src/routes/admin/tutor/[id]/+page.svelte @@ -0,0 +1,88 @@ +<script lang="ts"> + import { A, Button, Checkbox, Input, Label, P, Select, Textarea } from "flowbite-svelte"; + import { locale } from "$lib/i18n/i18n"; + import Gender from "$lib/genders"; + import Degree from "$lib/degrees"; + import { enhance } from "$app/forms"; + import { invalidateAll } from "$app/navigation"; + import { addMessage } from "$lib/messages.js"; + + export let data; + + $: dateFormatter = new Intl.DateTimeFormat($locale, { day: "2-digit", month: "2-digit", year: "numeric" }); +</script> + +{#if data.tutor} + <form method="post" use:enhance={()=>({result})=>{ + if(result.type === "success"){ + addMessage({type: "success", text: "Tutor gespeichert"}); + invalidateAll(); + }else{ + addMessage({type: "error", text: "Fehler beim Speichern"}); + console.error(result); + } + }}> + <Label class="mb-2"> + Vorname + <Input name="firstname" value={data.tutor.firstname} /> + </Label> + <Label class="mb-2"> + Nachname + <Input name="lastname" value={data.tutor.lastname} /> + </Label> + <Label class="mb-2"> + Rufname + <Input name="nickname" value={data.tutor.nickname} /> + </Label> + <Label class="mb-2"> + Geburtstag + <Input name="birthday" value={data.tutor.birthday} type="date" /> + </Label> + <Label class="mb-2"> + Handynummer + <Input name="phone" value={data.tutor.phone} /> + </Label> + <Label class="mb-2"> + E-Mail + <Input name="email" value={data.tutor.email} /> + </Label> + <Label class="mb-2"> + Adresse + <Textarea name="address" value={data.tutor.address} /> + </Label> + <Label class="mb-2"> + Geschlecht + <Select name="gender" value={data.tutor.gender} items={Object.entries(Gender).map(([value, name])=>({value, name: name[$locale]}))} /> + </Label> + <Label class="mb-2"> + Shirt-Größe + <Select name="shirtSize" value={data.tutor.shirtSize} items={data.shirtSizes.map(s=>({value:s,name:s}))} /> + </Label> + <Label class="mb-2"> + Studiengang + <Select name="studyProgram" value={data.tutor.studyProgram} items={data.studyPrograms.map(p=>({value:p.id,name:p.name[$locale]}))} /> + </Label> + <Label class="mb-2"> + Abschluss + <Select name="degree" value={data.tutor.degree} items={Object.entries(Degree).map(([value, name])=>({value, name: name[$locale]}))} /> + </Label> + <Checkbox class="mb-2" label="Geschult" name="trained" checked={data.tutor.trained}>Geschult</Checkbox> + <Label class="mb-2"> + Schulung + <Select name="training" value={data.tutor.training?.date ?? "-"} items={[{name:"Bereits geschult",value:"-"}, ...data.trainings.map(t=>({value:t.date,name:dateFormatter.format(new Date(t.date))}))]} /> + </Label> + <Label class="mb-2"> + Co-Tutor-Wunsch + <Input name="coTutorWish" value={data.tutor.coTutorWish} /> + </Label> + <Label class="mb-2"> + Notiz + <Textarea name="notes" value={data.tutor.notes} /> + </Label> + <Checkbox class="mb-2" name="mentor" checked={data.tutor.mentor}>Informatik Mentor</Checkbox> + <Button type="submit" color="primary">Speichern</Button> + </form> +{:else} + <P class="mb-6">Der Tutor konnte nicht gefunden werden.</P> + <P class="mb-6"><A href="/admin/tutor">Zurück zur Übersicht</A></P> +{/if} diff --git a/src/routes/admin/tutor/mail/+page.server.ts b/src/routes/admin/tutor/mail/+page.server.ts new file mode 100644 index 0000000000000000000000000000000000000000..89af0bede7e18e9a32f1d0a8029474c66fad9c21 --- /dev/null +++ b/src/routes/admin/tutor/mail/+page.server.ts @@ -0,0 +1,18 @@ +import { Permission } from '$lib/perms'; +import { StudyProgram } from '$lib/server/database/entities/StudyProgram.entity'; +import { Tutor } from '$lib/server/database/entities/Tutor.entity'; +import { TutorTraining } from '$lib/server/database/entities/TutorTraining.entity'; +import type { PageServerLoad } from './$types'; + +export const load = (async event => { + const studyPrograms = (await StudyProgram.getAll()).map(s=>({id: s.id, name: s.name})); + const tutors = event.locals.user.permissions.has(Permission.VIEW_TUTORS) ? (await Tutor.getAll()).map(t=>({id: t.id, firstname: t.firstname, lastname: t.lastname})) : null; + const degrees = (await StudyProgram.getAll()).map(s=>({id: s.id, name: s.name})); + const trainings = (await TutorTraining.getAll()).map(t=>({id: t.id, date: t.date, location: t.location})); + return { + studyPrograms, + tutors, + degrees, + trainings, + }; +}) satisfies PageServerLoad; diff --git a/src/routes/admin/tutor/mail/+page.svelte b/src/routes/admin/tutor/mail/+page.svelte new file mode 100644 index 0000000000000000000000000000000000000000..6665d9d2d34a04300850c2f5a070a54345456d89 --- /dev/null +++ b/src/routes/admin/tutor/mail/+page.svelte @@ -0,0 +1,78 @@ +<script lang="ts"> + import { Degree } from '$lib/degrees'; + import { Button, Heading, Label, MultiSelect, Select, Span } from 'flowbite-svelte'; + import MailInput from '$lib/components/MailInput.svelte'; + import type { Filter } from './+server.js'; + import { locales } from '$lib/i18n/i18n.js'; + import type { Localized } from '$lib/utils.js'; + + let { data } = $props(); + + let filters: Filter[] = $state([ + {degree: [], studyProgram: [], training: [], trained: null, mentor: null}, + ]); + function addFilter(){ + filters = [...filters, {degree: [], studyProgram: [], training: [], trained: null, mentor: null}]; + } + + let mailSubject = $state(Object.fromEntries(locales.map(l=>[l, ""])) as Localized); + let mailText = $state(Object.fromEntries(locales.map(l=>[l, ""])) as Localized); + let mailReplyTo = $state("-"); + + function sendMail() { + fetch("/admin/tutor/mail", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + subject: mailSubject, + text: mailText, + replyTo: mailReplyTo, + filters, + }), + }); + } +</script> + +<Heading tag="h1" customSize="text-4xl font-bold" class="mb-6 text-center">E-Mail senden</Heading> + +{#each filters as filter, i (filter)} +{#if i > 0} +<Span>oder</Span> +{/if} +<div class="flex gap-2"> + <Label> + Studiengang + <MultiSelect size="sm" class="w-44" bind:value={filter.studyProgram} items={data.studyPrograms.map(s=>({value: s.id, name: s.name.de}))} /> + </Label> + <Label> + Abschluss + <MultiSelect size="sm" class="w-44" bind:value={filter.degree} items={Object.entries(Degree).map(([value, {de: name}])=>({value, name}))} /> + </Label> + <Label> + Schulung + <MultiSelect size="sm" class="w-44" bind:value={filter.training} items={data.trainings.map(t=>({value: t.id, name: t.date}))} /> + </Label> + <Label> + Geschult + <Select size="sm" class="w-20" bind:value={filter.trained}> + <option value={null}>egal</option> + <option value={true}>ja</option> + <option value={false}>nein</option> + </Select> + </Label> + <Label> + Mentor + <Select size="sm" class="w-20" bind:value={filter.mentor}> + <option value={null}>egal</option> + <option value={true}>ja</option> + <option value={false}>nein</option> + </Select> + </Label> + <Button color="red" disabled={filters.length === 1} on:click={()=>filters=filters.filter((f)=>f!==filter)}>-</Button> +</div> +{/each} +<Button color="green" on:click={addFilter} class="mt-2">+</Button> + +<MailInput bind:text={mailText} bind:subject={mailSubject} bind:replyTo={mailReplyTo} replyToOptions={[data.user.email, "esa@fsmpi.rwth-aachen.de"]} allowCustomReplyTo studyPrograms={data.studyPrograms} trainings={data.trainings} onsubmit={sendMail} /> diff --git a/src/routes/admin/tutor/mail/+server.ts b/src/routes/admin/tutor/mail/+server.ts new file mode 100644 index 0000000000000000000000000000000000000000..8b8d5063d9199ed4c3f7b4e23389ee09c5c86b0c --- /dev/null +++ b/src/routes/admin/tutor/mail/+server.ts @@ -0,0 +1,35 @@ +import { Permission } from '$lib/perms'; +import { error } from '@sveltejs/kit'; +import type { RequestHandler } from './$types'; +import { Tutor } from '$lib/server/database/entities/Tutor.entity'; +import type Degree from '$lib/degrees'; +import { uniqueByProp } from '$lib/utils'; +import { sendMail } from '$lib/server/mail'; + +export type Filter = { degree: (keyof typeof Degree)[], studyProgram: number[], training: number[], trained: boolean | null, mentor: boolean | null }; + +export const POST: RequestHandler = async event => { + if(!event.locals.user.permissions.has(Permission.SEND_MAIL)) error(403, "You don't have permission to send mails"); + const data: {subject: string, text: string, replyTo?: string, filters: Filter[]} = await event.request.json(); + const { subject, text, replyTo, filters } = data; + const allTutors = await Tutor.getAll(); + const tutors = uniqueByProp(filters.flatMap(f=>filter(allTutors, f)), t=>t.id); + const responses = await sendMail({ + template: text, + subject, + replyTo, + tutors, + }) + return new Response(); +}; + +function filter(tutors: Tutor[], filter: Filter){ + return tutors.filter(tutor => { + if(filter.degree.length > 0 && !filter.degree.includes(tutor.degree)) return false; + if(filter.studyProgram.length > 0 && !filter.studyProgram.includes(tutor.studyProgram.id)) return false; + if(filter.training.length > 0 && (!tutor.training || !filter.training.includes(tutor.training.id))) return false; + if(typeof filter.trained === "boolean" && tutor.trained !== filter.trained) return false; + if(typeof filter.mentor === "boolean" && tutor.mentor !== filter.mentor) return false; + return true; + }); +} diff --git a/src/routes/admin/tutor/training/+page.server.ts b/src/routes/admin/tutor/training/+page.server.ts new file mode 100644 index 0000000000000000000000000000000000000000..07d962542d9b945a7aa9990ebf80e0aa0aef1196 --- /dev/null +++ b/src/routes/admin/tutor/training/+page.server.ts @@ -0,0 +1,9 @@ +import { TutorTraining } from '$lib/server/database/entities/TutorTraining.entity'; +import type { PageServerLoad } from './$types'; + +export const load = (async () => { + const trainings = await TutorTraining.getAll(); + return { + trainings: trainings.map(t => ({...t, participants: t.participants.length})).sort((a,b)=>a.date.localeCompare(b.date)), + }; +}) satisfies PageServerLoad; diff --git a/src/routes/admin/tutor/training/+page.svelte b/src/routes/admin/tutor/training/+page.svelte new file mode 100644 index 0000000000000000000000000000000000000000..bd6578c0695c7a49f30cafe8f02372fd1e719313 --- /dev/null +++ b/src/routes/admin/tutor/training/+page.svelte @@ -0,0 +1,44 @@ +<script lang="ts"> + import { locale } from "$lib/i18n/i18n"; + import { Permission } from "$lib/perms"; + import { Button, Table, TableBody, TableBodyCell, TableBodyRow, TableHead, TableHeadCell } from "flowbite-svelte"; + + let { data } = $props(); +</script> + +<Table> + <TableHead> + <TableHeadCell>Datum</TableHeadCell> + <TableHeadCell>Ort</TableHeadCell> + <TableHeadCell>Anmeldungen</TableHeadCell> + <TableHeadCell>Anmerkung</TableHeadCell> + {#if data.user.permissions.has(Permission.EDIT_TRAININGS)} + <TableHeadCell></TableHeadCell> + {/if} + </TableHead> + <TableBody> + {#each data.trainings as training} + <TableBodyRow class={training.internal ? "text-gray-700 dark:text-gray-300" : null}> + <TableBodyCell>{new Intl.DateTimeFormat($locale, {year: "numeric", month: "2-digit", day: "2-digit"}).format(Date.parse(training.date))}</TableBodyCell> + <TableBodyCell>{training.location}</TableBodyCell> + <TableBodyCell>{training.participants} / {training.maxParticipants}</TableBodyCell> + <TableBodyCell>{training.notes}</TableBodyCell> + {#if data.user.permissions.has(Permission.EDIT_TRAININGS)} + <TableBodyCell> + <Button href="/admin/tutor/training/{training.id}">Bearbeiten</Button> + <Button color="red">-</Button> + </TableBodyCell> + {/if} + </TableBodyRow> + {/each} + {#if data.user.permissions.has(Permission.EDIT_TRAININGS)} + <TableBodyRow> + <TableBodyCell colspan="5"> + <div class="flex justify-center"> + <Button href="/admin/tutor/training/new" class="w-20">+</Button> + </div> + </TableBodyCell> + </TableBodyRow> + {/if} + </TableBody> +</Table> diff --git a/src/routes/admin/tutor/training/new/+page.server.ts b/src/routes/admin/tutor/training/new/+page.server.ts new file mode 100644 index 0000000000000000000000000000000000000000..04a3a9f98575ca527c4d535d87587e7b9896d552 --- /dev/null +++ b/src/routes/admin/tutor/training/new/+page.server.ts @@ -0,0 +1,24 @@ +import { Permission } from '$lib/perms'; +import { TutorTraining } from '$lib/server/database/entities/TutorTraining.entity.js'; +import { error } from '@sveltejs/kit'; + +export const actions = { + create: async event => { + if(!event.locals.user.permissions.has(Permission.EDIT_TRAININGS)) error(403, "You don't have permission to create trainings"); + const data = await event.request.formData(); + const date = data.get('date'); + const location = data.get('location'); + const maxParticipantsRaw = data.get('maxParticipants'); + const notes = data.get('notes'); + const internal = data.get('internal'); + if(typeof date !== "string" || !date.match(/^\d{4}-\d{2}-\d{2}$/) || isNaN(Date.parse(date))) error(400, "Invalid date"); + if(!location || typeof location !== "string") error(400, "Invalid location"); + if(!maxParticipantsRaw || typeof maxParticipantsRaw !== "string") error(400, "Invalid maxParticipants"); + const maxParticipants = Number(maxParticipantsRaw); + if(!Number.isInteger(maxParticipants) || maxParticipants < 0) error(400, "Invalid maxParticipants"); + if(typeof notes !== "string") error(400, "Invalid notes"); + if(internal !== null && internal !== "on") error(400, "Invalid internal"); + const id = TutorTraining.create({ date, location, maxParticipants, notes, internal: internal === "on" }); + return { id }; + }, +}; diff --git a/src/routes/admin/tutor/training/new/+page.svelte b/src/routes/admin/tutor/training/new/+page.svelte new file mode 100644 index 0000000000000000000000000000000000000000..3c9d6eee99766010a331bf737a75329d67ddbbd6 --- /dev/null +++ b/src/routes/admin/tutor/training/new/+page.svelte @@ -0,0 +1,35 @@ +<script lang="ts"> + import { enhance } from "$app/forms"; + import { goto } from "$app/navigation"; + import { addMessage } from "$lib/messages"; + import { Button, Checkbox, Input, Label, Textarea } from "flowbite-svelte"; +</script> + +<form action="?/create" method="post" use:enhance={()=>({result})=>{ + if(result.type === "success"){ + addMessage({type: "success", text: "Schulung erstellt"}); + goto("/admin/tutor/training"); + }else{ + addMessage({type: "error", text: "Fehler beim Erstellen"}); + console.error(result); + } +}}> + <Label class="mb-2"> + Datum + <Input type="date" name="date" required /> + </Label> + <Label class="mb-2"> + Ort + <Input type="text" name="location" required /> + </Label> + <Label class="mb-2"> + Maximale Teilnehmer + <Input type="number" name="maxParticipants" min="1" required /> + </Label> + <Label class="mb-2"> + Anmerkung + <Textarea type="text" name="notes" /> + </Label> + <Checkbox name="internal" class="mb-2" value="on">Intern (Tutoren sehen die Schulung nicht und können sich nicht selbst dafür anmelden)</Checkbox> + <Button type="submit" class="mb-2">Erstellen</Button> +</form> diff --git a/src/routes/admin/user/+page.server.ts b/src/routes/admin/user/+page.server.ts new file mode 100644 index 0000000000000000000000000000000000000000..4610d7ca3080d349d3e8f170bb8c93fce02da2d7 --- /dev/null +++ b/src/routes/admin/user/+page.server.ts @@ -0,0 +1,37 @@ +import { Permission } from '$lib/perms'; +import { User } from '$lib/server/database/entities/User.entity'; +import { error } from '@sveltejs/kit'; +import type { PageServerLoad } from './$types'; +import { Config } from '$lib/server/database/entities/Config.entity'; + +export const load = (async () => { + const users = await User.getAll(); + const defaultPermissions = Config.get()?.defaultPermissions || 0; + return { + users: users.map(u=>({id: u.id, username: u.username, permissions: u.permissions.raw()})), + defaultPermissions, + }; +}) satisfies PageServerLoad; + +export const actions = { + update: async event => { + if(!event.locals.user.permissions.has(Permission.ADMIN)) error(403, "You don't have permission to edit users"); + const data = await event.request.formData(); + const id = data.get('id'); + const permissionsRaw = data.get('permissions'); + if(!id || typeof id !== "string") error(400, "Invalid id"); + if(typeof permissionsRaw !== "string") error(400, "Invalid permissions"); + const permissions = Number(permissionsRaw); + if(!Number.isInteger(permissions) || permissions < 0) error(400, "Invalid permissions"); + await User.update(id, permissions); + }, + defaultPermissions: async event => { + if(!event.locals.user.permissions.has(Permission.ADMIN)) error(403, "You don't have permission to edit users"); + const data = await event.request.formData(); + const permissionsRaw = data.get('permissions'); + if(typeof permissionsRaw !== "string") error(400, "Invalid permissions"); + const permissions = Number(permissionsRaw); + if(!Number.isInteger(permissions) || permissions < 0) error(400, "Invalid permissions"); + await Config.update({ defaultPermissions: permissions }); + }, +}; diff --git a/src/routes/admin/user/+page.svelte b/src/routes/admin/user/+page.svelte new file mode 100644 index 0000000000000000000000000000000000000000..e56fbbcee0ff05e4924aa801f836e1f6845dd916 --- /dev/null +++ b/src/routes/admin/user/+page.svelte @@ -0,0 +1,106 @@ +<script lang="ts"> + import { enhance } from "$app/forms"; + import { invalidateAll } from "$app/navigation"; + import { addMessage } from "$lib/messages.js"; + import { Permission, PermissionDescription } from "$lib/perms"; + import { Button, Table, TableBody, TableBodyCell, TableBodyRow, TableHead, TableHeadCell } from "flowbite-svelte"; + + let { data } = $props(); + + let canEdit = data.user.permissions.has(Permission.ADMIN); + const allPermissions: [string, Permission][] = Object.entries(Permission).filter(([, val])=>typeof val === "number") as [string, Permission][]; +</script> + +{#if canEdit} +<form id="default-form" method="post" action="?/defaultPermissions" use:enhance={({formData})=>{ + let perms = 0; + for(let [permName, permission] of allPermissions){ + if(formData.has(permName)){ + perms |= permission; + formData.delete(permName); + } + } + formData.set("permissions", perms.toString()); + return ({result})=>{ + if(result.type === "success"){ + addMessage({type: "success", text: "Standardberechtigungen gespeichert"}); + invalidateAll(); + }else{ + addMessage({type: "error", text: "Fehler beim Speichern"}); + console.error(result); + } + } +}}></form> +{#each data.users as user} +<form id="form-{user.id}" method="post" action="?/update" use:enhance={({formData})=>{ + let perms = 0; + for(let [permName, permission] of allPermissions){ + if(formData.has(permName)){ + perms |= permission; + formData.delete(permName); + } + } + formData.set("permissions", perms.toString()); + return ({result})=>{ + if(result.type === "success"){ + addMessage({type: "success", text: "Berechtigungen gespeichert"}); + invalidateAll(); + }else{ + addMessage({type: "error", text: "Fehler beim Speichern"}); + console.error(result); + } + } +}}> + <input type="hidden" value={user.id} name="id" /> +</form> +{/each} +{/if} + +<Table> + <TableHead> + <TableHeadCell class="py-2">Nutzer</TableHeadCell> + {#each allPermissions as [permName, permission]} + <TableHeadCell class="px-2 py-2"> + <span title={PermissionDescription[permission]}>{permName}</span> + </TableHeadCell> + {/each} + {#if canEdit} + <TableHeadCell class="py-2"></TableHeadCell> + {/if} + </TableHead> + <TableBody> + <TableBodyRow class="border-b-4"> + <TableBodyCell class="py-2"><span title="Berechtigungen, die Nutzer beim ersten Login bekommen">Standard</span></TableBodyCell> + {#each allPermissions as [permName, permission]} + <TableBodyCell class="px-2 py-2"> + <div class="flex justify-center"> + <input type="checkbox" form="default-form" name={permName} disabled={!canEdit} checked={data.defaultPermissions.has(permission)} /> + </div> + </TableBodyCell> + {/each} + {#if canEdit} + <TableBodyCell class="py-2"> + <Button type="submit" form="default-form">Speichern</Button> + </TableBodyCell> + {/if} + </TableBodyRow> + {#each data.users as user} + {@const isSelf = user.id === data.user.userId} + <TableBodyRow class={isSelf ? "bg-gray-200 dark:bg-gray-700" : ""}> + <TableBodyCell class="py-2 {isSelf ? "font-bold" : ""}">{user.username}</TableBodyCell> + {#each allPermissions as [permName, permission]} + <TableBodyCell class="px-2 py-2"> + <div class="flex justify-center"> + <input type="checkbox" form="form-{user.id}" name={permName} disabled={!canEdit} checked={user.permissions.has(permission)} /> + </div> + </TableBodyCell> + {/each} + {#if canEdit} + <TableBodyCell class="py-2"> + <Button type="submit" form="form-{user.id}">Speichern</Button> + </TableBodyCell> + {/if} + </TableBodyRow> + {/each} + </TableBody> +</Table> diff --git a/src/routes/admin/user/+page.ts b/src/routes/admin/user/+page.ts new file mode 100644 index 0000000000000000000000000000000000000000..2bbc757fa263a216d63912499510dfea9014da2e --- /dev/null +++ b/src/routes/admin/user/+page.ts @@ -0,0 +1,9 @@ +import { PermissionSet } from '$lib/perms'; +import type { PageLoad } from './$types'; + +export const load = (async event => { + return { + users: event.data.users.map(u=>({...u, permissions: new PermissionSet(u.permissions)})), + defaultPermissions: new PermissionSet(event.data.defaultPermissions), + } +}) satisfies PageLoad; diff --git a/static/eswe/0a8b0d3c-6b2d-4601-8ed8-145c0c689ef1.JPG b/static/eswe/0a8b0d3c-6b2d-4601-8ed8-145c0c689ef1.JPG deleted file mode 100644 index 84c0d79b2da8f6b52c3ef14669135ae53fe83b71..0000000000000000000000000000000000000000 Binary files a/static/eswe/0a8b0d3c-6b2d-4601-8ed8-145c0c689ef1.JPG and /dev/null differ diff --git a/static/eswe/0f5c2fa1-92a9-4c65-a4df-87fa7c63c6f4.JPG b/static/eswe/0f5c2fa1-92a9-4c65-a4df-87fa7c63c6f4.JPG deleted file mode 100644 index acca65237a18aa67dfb02c86dcc17154d4cff1a0..0000000000000000000000000000000000000000 Binary files a/static/eswe/0f5c2fa1-92a9-4c65-a4df-87fa7c63c6f4.JPG and /dev/null differ diff --git a/static/eswe/16777d3b-8e2b-4350-8a2a-404ae1e26301.JPG b/static/eswe/16777d3b-8e2b-4350-8a2a-404ae1e26301.JPG deleted file mode 100644 index 5392b1b90aa94c9deb21da31b6fce7c2defa07aa..0000000000000000000000000000000000000000 Binary files a/static/eswe/16777d3b-8e2b-4350-8a2a-404ae1e26301.JPG and /dev/null differ diff --git a/static/eswe/22983672-610f-4460-b372-57f61ed97629.JPG b/static/eswe/22983672-610f-4460-b372-57f61ed97629.JPG deleted file mode 100644 index 5059825ec659864fde8c7206f8491ee308dd4d25..0000000000000000000000000000000000000000 Binary files a/static/eswe/22983672-610f-4460-b372-57f61ed97629.JPG and /dev/null differ diff --git a/static/eswe/44425363-690c-4974-8f27-0a8fcc96375e.JPG b/static/eswe/44425363-690c-4974-8f27-0a8fcc96375e.JPG deleted file mode 100644 index 6ec308716367271636b2b0eae0552a7c77681ba0..0000000000000000000000000000000000000000 Binary files a/static/eswe/44425363-690c-4974-8f27-0a8fcc96375e.JPG and /dev/null differ diff --git a/static/eswe/4ec6de7f-d575-40b9-a880-f30d394ba65d.JPG b/static/eswe/4ec6de7f-d575-40b9-a880-f30d394ba65d.JPG deleted file mode 100644 index 0a21c5a962bc7aea78e26818e25026c623d4b51a..0000000000000000000000000000000000000000 Binary files a/static/eswe/4ec6de7f-d575-40b9-a880-f30d394ba65d.JPG and /dev/null differ diff --git a/static/eswe/9f08f05c-b6a0-4fa6-8049-976da3828376.JPG b/static/eswe/9f08f05c-b6a0-4fa6-8049-976da3828376.JPG deleted file mode 100644 index 453ac4d0d407c20eb313ea0b213d816b17c711a4..0000000000000000000000000000000000000000 Binary files a/static/eswe/9f08f05c-b6a0-4fa6-8049-976da3828376.JPG and /dev/null differ diff --git a/static/eswe/c99c0398-9fe3-4f4b-855a-2612906bf0f6.JPG b/static/eswe/c99c0398-9fe3-4f4b-855a-2612906bf0f6.JPG deleted file mode 100644 index c65146002760d950cb4923229c84291a36bc15e5..0000000000000000000000000000000000000000 Binary files a/static/eswe/c99c0398-9fe3-4f4b-855a-2612906bf0f6.JPG and /dev/null differ diff --git a/static/eswe/cc9ad1ff-2ea8-4a1f-bd4d-09d8d042a3b3.JPG b/static/eswe/cc9ad1ff-2ea8-4a1f-bd4d-09d8d042a3b3.JPG deleted file mode 100644 index 61946cf61a5de63dfb63dd8aecfdf16884738e95..0000000000000000000000000000000000000000 Binary files a/static/eswe/cc9ad1ff-2ea8-4a1f-bd4d-09d8d042a3b3.JPG and /dev/null differ diff --git a/static/favicon.png b/static/favicon.png index 825b9e65af7c104cfb07089bb28659393b4f2097..af7d87451918e3bc106d089a476bf18324a2d55d 100644 Binary files a/static/favicon.png and b/static/favicon.png differ diff --git a/static/flyer/AIX-eSports.pdf b/static/flyer/AIX-eSports.pdf deleted file mode 100644 index 1d8eaf3b7914ee9dd4a4a8c7a673f04e7ea034a6..0000000000000000000000000000000000000000 Binary files a/static/flyer/AIX-eSports.pdf and /dev/null differ diff --git a/static/flyer/AIX-eSports_hq.jpg b/static/flyer/AIX-eSports_hq.jpg deleted file mode 100644 index fde24c4b2aa03ebe89ebebdac411bb0242dcc9b9..0000000000000000000000000000000000000000 Binary files a/static/flyer/AIX-eSports_hq.jpg and /dev/null differ diff --git a/static/flyer/AIX-eSports_lq.jpg b/static/flyer/AIX-eSports_lq.jpg deleted file mode 100644 index 5a17ac8d1c3aeb5a52cd68bba16dd988b6d289db..0000000000000000000000000000000000000000 Binary files a/static/flyer/AIX-eSports_lq.jpg and /dev/null differ diff --git a/static/flyer/HOO Flyer Code Start.pdf b/static/flyer/HOO Flyer Code Start.pdf deleted file mode 100644 index 4daacf6745d621edb1da6e626dd75619719e3f98..0000000000000000000000000000000000000000 Binary files a/static/flyer/HOO Flyer Code Start.pdf and /dev/null differ diff --git a/static/flyer/HOO Flyer Code Start_hq.jpg b/static/flyer/HOO Flyer Code Start_hq.jpg deleted file mode 100644 index da12bcd2fcb644755fc7ff9fb423c13a2d7588a3..0000000000000000000000000000000000000000 Binary files a/static/flyer/HOO Flyer Code Start_hq.jpg and /dev/null differ diff --git a/static/flyer/HOO Flyer Code Start_lq.jpg b/static/flyer/HOO Flyer Code Start_lq.jpg deleted file mode 100644 index 1d99a2bccb3bab747964c1e2d6cfa51f50d872e0..0000000000000000000000000000000000000000 Binary files a/static/flyer/HOO Flyer Code Start_lq.jpg and /dev/null differ diff --git a/static/flyer/Uni.Urban.Mobil..pdf b/static/flyer/Uni.Urban.Mobil..pdf deleted file mode 100644 index 2417a6bf97a4004b920cc46718bfc497409b8dce..0000000000000000000000000000000000000000 --- a/static/flyer/Uni.Urban.Mobil..pdf +++ /dev/null @@ -1,1038 +0,0 @@ -%PDF-1.4 %���� -1 0 obj <</Metadata 2 0 R/Pages 3 0 R/Type/Catalog>> endobj 2 0 obj <</Length 58145/Subtype/XML/Type/Metadata>>stream -<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?> -<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 7.1-c000 79.a8731b9, 2021/09/09-00:37:38 "> - <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> - <rdf:Description rdf:about="" - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:xmp="http://ns.adobe.com/xap/1.0/" - xmlns:xmpGImg="http://ns.adobe.com/xap/1.0/g/img/" - xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/" - xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#" - xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#" - xmlns:stMfs="http://ns.adobe.com/xap/1.0/sType/ManifestItem#" - xmlns:illustrator="http://ns.adobe.com/illustrator/1.0/" - xmlns:xmpTPg="http://ns.adobe.com/xap/1.0/t/pg/" - xmlns:stDim="http://ns.adobe.com/xap/1.0/sType/Dimensions#" - xmlns:stFnt="http://ns.adobe.com/xap/1.0/sType/Font#" - xmlns:xmpG="http://ns.adobe.com/xap/1.0/g/" - xmlns:pdf="http://ns.adobe.com/pdf/1.3/"> - <dc:format>application/pdf</dc:format> - <dc:title> - <rdf:Alt> - <rdf:li xml:lang="x-default">Digitaler Flyer_mBus</rdf:li> - </rdf:Alt> - </dc:title> - <xmp:MetadataDate>2023-08-22T01:58:55+02:00</xmp:MetadataDate> - <xmp:ModifyDate>2023-08-22T01:58:55+02:00</xmp:ModifyDate> - <xmp:CreateDate>2023-08-22T01:58:55+02:00</xmp:CreateDate> - <xmp:CreatorTool>Adobe Illustrator 26.0 (Windows)</xmp:CreatorTool> - <xmp:Thumbnails> - <rdf:Alt> - <rdf:li rdf:parseType="Resource"> - <xmpGImg:width>256</xmpGImg:width> - <xmpGImg:height>228</xmpGImg:height> - <xmpGImg:format>JPEG</xmpGImg:format> - <xmpGImg:image>/9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA
AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK
DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f
Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgA5AEAAwER
AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA
AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB
UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE
1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ
qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy
obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp
0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo
+DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9U4q7FXYq7FXYq7FXYq7
FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F
XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX
Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY
q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq
7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYqp3Fzb20LTXEqQwpu8kjBVA9yaDFSWIav+cX5c6WWW
XWYriVf912ga4qfDlGGT72xcWesxR5y/SxHUP+cmPK0RIsNLvLoj9qUxwqflRpT+GLjS7VxjkCUg
uv8AnJ7UWP8AougQxDt6tw0n/EUjxpoPax/m/b+xLpf+cl/OZA9LTdOU9+STt+qVcaYHtWfQBYv/
ADkt555DlYaYVruBFcA0+frnDTEdq5O6P2/rRkH/ADk35iU/v9Hs5BXpG8qbfSXwU2DtY/zU4sv+
cnrFiBfaBLEP2mguFk+kKyR/rxbY9rR6xLKNL/5yA/Lm9IE9xcaex2pdQMRX5wmYffi5EO0cUutM
10fzP5d1pA2k6lbXu1SsMqu4/wBZAeS/SMXLhkjL6SCmeLN2KuxV2KuxV2KuxV2KuxV2KuxV2Kux
V2KuxV2KuxV2KuxV2KsW83/mX5P8qApql6GvaVWwgHqTnwqo2SvYuQMXHzaqGP6ju8Y80f8AOR3m
W9Lw6BbR6Vb7hZ5AJ7gjx+Iemvy4n5406rN2pI/QKeY6x5h1zWp/X1a/nvpa1UzyM4Wv8oJoo9hh
ddkzTn9RtL8Wt2KuxV2KuxV2KuxV2Kro5JI3WSNikimqupIIPiCMUgkbhm/lv85/P+hFEXUDqFqt
B9WvqzCg7ByRKPobBTmYtflh1seb1/yj/wA5DeVtVZLbWom0a7bb1WPqWxP/ABkADJ/sloPHF2mH
tKEtpek/Y9Tt7i3uIUnt5UmgkHKOWNgyMD3VhUEYuxBtfirsVdirsVdirsVdirsVdirsVdirsVdi
rsVdiqF1TVdO0mwlv9SuEtbOAcpZpDRQP4k9gNzixlIRFnk+e/zB/wCcgNW1NpNP8r89O0/dWvjt
cyjxX/fS/L4vcdMadJqe0idobDveQySSSyNJIxeRyWd2JJJPUknC6sm+a3FDsVdirsVdirsVdirs
VdirsVdirsVdirJ/Jn5i+afKNwG0u6LWjNym0+arwP4/DX4T/lLQ405On1c8R2O3c+k/y+/NTy95
ygEUDfU9XReU+nSmrUHVom29RfluO4wO/wBNq4ZRtz7maYuU7FXYq7FXYq7FXYq7FXYq7FXYq7FX
Yql+v69pmg6RcarqcohtLZeTnbkx/ZRAaVZjsBiwyZBCJkeQfJ/5ifmTrXnPUjJcMbfTIWP1LT1P
woOnJ/5nI6n7sNPN6rVyyn+j3MRxcR2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2Kq1neXVldR
XdpM9vcwsHhmjYq6sOhVhuMWUZGJsc307+UX5twebLZdK1Rlh8wwJU0+FblFG8iDs4/aX6RtsA9F
o9YMoo/U9Lxc52KuxV2KuxV2KuxV2KuxV2KuxV2Kvlr87PzDk8y+YX02ylromluY4eJ+GaYfC823
Ufsp7b98Q892hqeOXCPpDzbC652KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KojTtQvdO
voL+ylaC7tnEkEyGhVlNQcWUJmJscw+wvy985W3m7yxbatGFS5/ur6Bf91zoBzHU7GoZfYjA9Tp8
4ywEgyTFvdirsVdirsVdirsVdirsVdirD/za8yv5e8h6leQtwu51Frat3Ek/w8h7qnJh8sXG1eXg
xk9XyDheWdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdir1n/AJxz8yvY+bZtEkb/
AEbV4iUXsJ7cF1P0x8x92Au07Ly1Mx730ri752KuxV2KuxV2KuxV2KuxV2KvF/8AnJy7kTQtFswf
3c11JMw7Vij4j/k6cXVdqn0Aeb55wuidirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsV
dirIfy7vGtPPfl+dW4gahbo7eCSSBH/4VjiXI0sqyxPm+zcD1TsVdirsVdirsVdirsVdirsVeM/8
5NWLyeXtHvgCUt7t4WPh60fIf8msXV9qx9APm+d8LoXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7
FWVaT5GhvTZQ3Gs2tnfX6o9tZOsjyFZN4+RVeILDcCuC3Ox6MSq5AE9EbdflrBa3ElvPrcSyxHi6
i1umAI91Qg/Rg4m09nAGuL7ChNT8maLpd21nfeZLeG5VVdo/q10xAdQ614o1CVINMNtWTSwgalPf
3Fbc+T9Ctra1uZvMtssN6rSWzC3uWLKjlGNFQlfiUjfG0S0uMAEzFHyLZ8n6ENOGonzNbfVDKYBJ
9Xua+oF5FeHDn070pjafysOHi4xXuKe/l95I0688y2F7p2twX40y6trm4t1gmjZlSUNxHqqg+LjS
vbvjbdpdJGUgYzvhI6F9Jya16QDSQFVJpUOh/AHF3yZ4q7FXYq7FXYq7FXYq7FXYqxb8zfLLeZPJ
Op6bEvO79P17MdzNCeaqP9ehX6cWjVYvExmL46IIJBFCOowvKOxV2KuxVdEAZUDCqlhUE8aiv8x6
fPBLkmPNld1o3lV5J2W6SHhEphggmjb4iWqWeWRlalBsrA+2c/i1mrAiOEyuW5lGXLbkIxBHX6h8
TzdtPT6ck+qtuQI/SfuQgj0RddvoZRBc2ixu8EzP6a80j5KF9Awp8TbUpmQTnOnhIcUZ2LFXsZb/
AFCR2DSBi8WQNGNbHl0/o0FaDTfLgskulu1W4lhkeSEtAY1coxEapLzkBVgFqevY1yvJqdT4hgY+
kSjR9VkcQ3JjUeW9dOopnDDh4eLi3IP82uXKjZ/HNuTTvLd5rGqmS6WCGNl+ppbtCiMCN2BcqhC+
ANcEdRqseHFUeKRHq4hIn7LNnvO3epw4Z5J2aHSq/sbvNI8sMZH/AEiBIUJVkeDgCiRndI1WvLkR
t4eOHDq9VsODa+6V7mXUnpQ+bLJp8Bs8X+58ugUNW0jy3a21xJa3jzOgjECerAxZmLBmolTxHEGm
xGWaPWanJKInARu72ltVUN+u57wa2as+nwRiTGV924Y5m5dc7FXYqmWg+X9Q1u4nt7EBpLeB7hwQ
7EohAoqxq7MxLAAAYktuHCchodzP/wDCevQ+fLPU5rU2+nWNxptqHmPp8iIY0VYlfiX+wa8ffI27
TwJeOJfwjhH2dEw1r8lbnUdYvtQTVUjW8uJbgRmEkr6rl+Nee9K4bZ5ey+KRlxcz3N6x+S1xf3aT
pqyoFt7aAhoSSTbwJBy+3+16dcbXJ2ZxG+LoOncKX6h+TNxdadpdquqorafDJCzmE0f1J3mqBz2p
6lMUz7NuMRxfT5eduP5M3B0BdM/Sqeot01z6vomlGjCcac/8muK/yb+74eLrfJkP5Z+Qn8m6tNqE
12t8ZECJGqGOmzAmpLfzYt+k0fgkm7t6LqGtrd2rQCEpyIPItXoa+GLnMjxV2KuxV2KuxV2KuxV2
KuxV2Kvmj89/y6k0TWn8w6fEf0Tqblpwo2huW3YbdFk+0Peo8MQ6HtHS8MuMcj97yjC6t2KuxVdG
vN1WtORAr88BNBIFmmU3PkVYzKkV+JpoJoop4xCwKicgK32qHZq0H35z+Ht7ioyhwxlGRB4h/Dz/
ALfsdpPsyrAlZBAO3e6fyIIbg11FDYpA1xJdiMtQIaEBFY1+/Dj7dMof3Z8Qy4eG+/fmR+hZdmUf
q9NXdKlp5W0a90y3EN2EnmuZYobsxyH1goJUenyomwrU5DN2rnxZZXC4iESY2PTfPfqzhocc4Cpb
mRF0d/h0Xf4PsLjT7BI51ttReOdnHF3EzRNTrXigX+OR/ljJDLO48WIGHcOHiHzNpPZ8JQiAanR7
96+5Rt/JkaRafeS3Amjmmt1urQqUZVnYChIYnv7Zbl7ZJOSAjwmMZ8MuYJiPd+trh2cAIyJuzGx7
1fUfLOhxWUk6TehML57eONld1NGIWLrX7I5cq+2VaXtPUSyCBjcfCEidgeX1fPavi2ZtFiEbBo8d
dT8P2uufJKT6hcs11FZxGdbe2SOJirOYw9Kcm47eJwY+2zDHGoymeDilchsLruF/JZ9m8UzuIi6G
3l70HN5MSDTnup9Shhl/em3gkopkERI2JbqadADmVHtoyzDHHHKQ2sjpfw/U0y7O4YcRmAd6HfTW
q+TUsbS8mjv1nmsvTaaD0mSiSminkSRX2x0nbRyzhEw4Y5Lo2DvHnsufs8QjIiVmNbV3s38t3s2j
3cVvY6ai6JdR2Ym1gW0bx/o5oy1/LNdU5F/V4/u2JUFeBXsd03YjwGoj07b106kl6h5b17yJe2DS
2DWf1maOO5vLeMwSSKQo+NgW5H0iFALiq4uyx5McvpIRd7dWix+u8kUYjTlcsGVVSm9W7J8FDi3W
Em/xf5T/AOr3Yf8ASVD/AM1YWr8zj/nR+Yd/i/yn/wBXuw/6Sof+asV/M4/50fmERY6/oWoXSWlh
qVrd3UlfTt4Jo5ZGoKmiIxY0AriyjmhI0JAn3pl6E/q+l6ber/vuh5fd1wNiqun3xYD6vIKmlSjA
fqwqzHFXYq7FXYq7FXYq7FXYq7FXYqhdU0yw1TT59O1CFbizuUMc8L9GU/Loe4I6YsZREhR5Pln8
z/yo1TydePc26vdaBK3+j3lKmOp2jmp0bsG6N89sXndZojiNjeLAcLguxVdGHMihPt1HGtBvXbrt
gly3TG72ZC115y/SV5bFyt7RLi7FIVoIApR+WygAU6HfNLHFofChID93vGP1fxcxXPfz5OxM9Txy
j/FzPLogz5t8xG5W5N63rIGVW4pSjEEgrx4np4ZlfyRpuDg4Bw/H9dtP5/Nd8W/wVYNV8z3MMt7F
LyjsJPrMsnGIcJJqryoQK132pleTS6WEhjI3yR4QPVuI9PgyjnzyBkDtE306oZfMutK8Lrc0aASL
EeEewmNX/Z3r75fLs3AbuP1Ve5/h5dWsazLYN8r7uvNFXWseabW1sluZytuyxzWYIiaqxkFG2BOx
H7WUYtFpJzmYx9VkS59eY/sbJ6jPGMeI7bEcuiFTzLriLcKt0eN05knUqhBdupAK/D9GXns3ATE8
O8BQ3PL57/FrGtyi9/q58k50PzmLVp7jUJrqW5mk9Ro4lhET0QKOQIqv2eq5rNf2L4gjDGICMRVn
i4hv033+LmabtDhszMjInpVfj3JS/mjWjDcW0dwUtLhpC0BCsAJCSygsCQN+2bEdl4OKMzG5xrff
o4h1uWiAfSb296lceYdYuBcia45C8CLc/Ag5CL7HRRSntk8fZ2GHDwx+i+Hc7cXPr97GerySuz9X
Pl0Q1nBe3cqWNtyd5m+CHkFUtT3IXtmTmyxxxM5GohqxxlM8Meqmwmt52WpjmiYqeJ3BGx3GTjIS
FjkWJBifMK19a39lMYbsFJJVWRl5BuQbdSSpOVYc8Mo4oGxdfJnlxzgal1QuXNSM0jR9T1jUIdO0
y3e6vJzxihjFSfEnsAO5OwxZ48cpmoiy+pfyr/Kyy8mWLT3DLda5dKBc3IHwxr19KInfjXqf2sD0
ek0gxDvkWe8VrWgr498XMdirsVdirsVdirsVdirsVdirsVdirsVU7q1tru3ktrqJJ7eZSksMih0Z
T1DKagjFBAIovCvzB/5x5cNJqPk81U1Z9Jlbcf8AGCRjv/qufp7Yuo1PZl74/k8RvrC9sLuS0voJ
La6hPGWCVSjqfcHfC6ecDE0RRUMWLK73zHYS+XyyMTrVzDHaXXwt/dRliW5U4kuKA75z+Hs7JHU7
/wBxGRnH+senw3drk1kDh2/vCBE+4frTO4806FJBYLDNFFbwSwP9UNvJ6kXBwWIkU8Og7Ka5r8fZ
WojLJxCRlKMvVxxqVjb01f27OVLW4iI0QACNuE2PjyULLzTo/wBc1dr9lmtZnhS0hEZAaGORyNgv
7IavxdemXZ+ys/BhGP0ziJGRu6kQPPry25c2vHrcfFPj3iarboCfxurWPmTQra3u4EuoEklnd3le
1kdJo3qQvBDHQitPiyvU9m6jJOEjGVCIFDIAYkdbPFfw3Z49ZiiJAEbk/wAJ3H2KNv5p01bS2s3u
F+qjT3iuIvSYj19gi1Klqde9MuydlZDOWQR9figg8X8PXrX6WMNbARESfTwUduvyah80WCX2lKtz
GlpbWqfWHaBnInCcD0CvWm1QaYy7LyHHluJM5TNeqvTd+Y/SiOtgJQ3HCI77dVWHzZosV1fyvM9x
6UoudPd0oXkMJiYfCq0A96fflc+yc8oY4gCNx4Z0eQ4uIcyd/n8mUddiEpG7o3H31Ta+atAj1uH0
2rYQwyehM0bERzzSeozcKK1B023wS7K1MtOb/vJSHEL+qMRVXy80/nsQyivpANGuRJvksl81WEQv
pYLuJruWS3eKWO3dA3E0kbjJ6lDwJFa/LJw7KyS8OMoy4BGYIMwefIXHh2tEtdAcREhxEj+E/Hnf
RGQea/LkV5cSQXX1ZWuBLI4gZvXQxAFa05LR9/8AbzFy9k6mWOIlHj9FfVXAb59x2/GzbHXYRIkG
vVfLnsg7XzLoi6PcWqyxQySPOZkmt5JROHZipBQpTYj7XTMrN2ZnOojMiUojhqpiPDQF8wb+DTDW
YvDMbA3POJNsZ8wXsV5exyxyiYLBEhdUaMclWhFGJO2b3QYTjgQRw+qR53zPk63V5BOQIN7BkvkX
8ofNXmx450iNhpLULajcKQrKf98psZD8vh98zLZ6fQzyb8o976R8kfl95d8n2PoaZDyuZABdX0lD
NKRvuf2V8FG3074u+waeGIVFkuLe7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FUm8y+TvLXmW
2EGtWEd2FFI5SCsqf6ki0dflWmLVlwwmKkLeO+aP+caZ1LzeWdSEi9RZ33wt8lmQUP0oPnjbq83Z
XWB+byzX/wAv/OegFv0ppFxDEvW4VfVh/wCRsfJPxw267JpckOYY/i47sVdirsVdirsVdirsVRWn
aVqepTi3060mvJz/ALqgjaRt/ZQcWcMcpbRFvQ/Lf/OP3njVSsmoLFo9serXB5zEf5MUZP3Oy4Lc
7F2Zkl9Xpev+UvyO8kaAUnmgOrXy0Pr3gDICP5IR8A/2VT74u0w6DHDerPm9CAAAAFANgBi5rsVd
irsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVQ0eqaZJz9O7gf0lV5OMiHijMyKzUOwLx
uoPipHbFWPav5b/LTV+T6hZabcSFog8yiNZa3EpgirJHR/3kylF33YEdRi1TwQlzALGdY/IH8szB
Nck3GlW8KNJLKlyBHGijkzM1wJaKoG5J6YuLLs3EeQI+LG7n/nHPyy4BtPMrxhpTbp6kcU374VHp
/A8fxAg1Xri0nsmHQlBRf841rdRiXT/NMF3BuDKtvUch1FUmcfjjbX/JH9L7P2qK/wDOOHP0+Pmm
1b1pZLeGkVec0XP1Il/e7unpPyUbjia9DjaP5J/pfZ+1Gr/zjZpkJ/0vzSCPVSAqlsqESyU4pvM/
xMGFNvfFnHskdZfYmem/kL+WY1NtNn12a91SEcptPS4t0kCgKxLQqrSgUkU9f2h4jFtj2XjHOyzH
Svyh/LLTpRHFpEFxcIA7C6drhqEkBikjMtDQj7NMXIhosUeUR97MbWztLOEQWkEdvAv2YolVEHyV
QBi5IFKuKXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq8SnuPyYm1yTU7r
VpzfWAntLmVzC36Pi+sSOIn4RMUUyzOFYknejtsAFVur2n5K6j6lxqGvXktbeOOK8e15xJbvdLqK
NE7WTQsoaNuT/FRC/I0HwqrbeH8mY7rUL2z13UNRvdZlhtLoxoJpUj1ESQAVa3+CCRblpKsfiopW
p4gqt3vl78nZEtL7Tbq9l02+E62p00QNAp9bjJHG0kfq1DyEiNCabyceXxlVVki/LLVLXS/I2l6t
qs1GuYIb+zRQ9vKqpaSRyyPAqgkS1NBUD4tgVOKrPKll+U9nq+gNpd/qmp31oY2sprm0AYm7lY+t
LJPawyv6rXpLFWNAAduIxVRsND/JmFdQ019Sv2iu5k9NJoRN6qsls8T2yrasUTlcxqgUDn8J+JQh
CqeapYflT+lrbzDeapcqt9Imo+l6Be3mfSgr+rN/ozOvAXA+045V2qRsqx+60T8lbi91Ka+833/1
jXboz201y5QW8/rOq/UpZrf4WR5DQhjRaHpuVWW+XfzL/Lby7o2l6KNbmlsbWzYnU9QSaNgY5IUW
OYyRxsJX+sq4TjUJ8RopUlV6Fpuo2upafbahaMzWt3Gs0DOjxMUcclJSQI61B6MBiqJxV2KuxV2K
uxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV5F9Ztf0tPBN+VkI/SMrC81AW7SRlKoqSz
kWXMlhK9QivQjf4SWVVDWOtaxZ2YF3+W0c8FpplkUhtrRlczSLIsyRobZthE4Ux1qh5L8Va4qgbb
zhNpMqyav+XEH1y+tIJT9Vs3gkmmhBpboksLh3jQgqvP4R3rtiqaxzXdx5QsNV0f8ubS1KC/aHRr
y3ZZoYQhPIRCBP3k0hakX7a9HqaYqhxrmrpd3Nzp/wCW8FpPbabFcadI2nys63POSZ4lZbeAmkgj
qvJW5qWXkpV8VX+W/MWu3Os6Lp975AgsTcRRyXdyunypHbRJeFIU5NH8DIscTgbgH4jxA+FVF+Xd
bt9Y1eFm/LT9H2M7H61d3dkI54pI5yOVFgdZQSiP9teNP2tsVWBn/wAR6Xaz/l9GdJhur60tClmy
ww2d3cWix3fBUkiVn4O8iuqsFFajcMqoT30DyzG7/K2KYKWkslWxcn1IZI1+OQWrDi9GkVqV4pTi
Sy1VQd/rM99Y39qfysivdTsrSD99cWDLbyGf05I44VNvzdYpWUyRsybKSaEUCrKk88+b0d9KtPKc
tq1uiC1uGjuVtWSqKsaCO3dU4qWB5OAAKiv2QqnvkTzlqHme2vJL7QL3QJbORIjDfKVMhaMOWjJV
KgE06fd0CrKMVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirzI6H+YmpQ6l+jfN
VvcRC9kW1ENwKWyrKC0LSR27PzUbMrlqdAVGKr5vLH5yS6leXaeY7W2VpoxYwrWSJLZBMCskTwAN
IfXHxV34LUmlMVRKWv5tzRw2Nzrem22pG6vZpWtlHJtPKhLXiksEoDpI9ST7A8t8VSGLQPzlN3a6
AnmjTWs7GxH1hEnddREqwGONmkWHkyPLxbkybDrzZasqmmgRfm3c3msx3/mPSnuokmWxs7NVeOAu
ZRayzgw+pX1Y+DDn9lTty3xVF6H5e/Ny0sdV/SfmS2vtSngCabII0jgSYcV9Z4xASvwpXirFSzNt
9niqvttD/NR4rme91e2XUktp7fT/AKvKfq4kmkiZJpY2tgpMaxsFBVvn8RIVUZdF/OUX9xdw6vYM
m8dvbNIyoYw0bK7/AOiOqt8MoICk/EPi+GmKpda+XvzVYNe3fm+CW+sPRhdlkWO1Ev1GZbgSxJbR
xN/pc8bJyX7CivxDFXJ5M/N8a1FIdeiXTkl+tPyuJZZPVcXQliUPBxEX+koAtNgg48SMVTTy7a/m
9Hr9sNTubZtAhubtL1ZjHJPLb+kv1R7doY0IHqV5CU8qdScVRuofmfosfnXT/KNgReX9zKyXsqGs
duERnKkj7Unw0oPs999sXGlqo+IIDcn7GZ4uS7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY
q7FXYq7FXjV9pv5M6prSRvqN2uoXEk0MVnHayI80/qmZ3QG19SXi1yHFCU2ElKpzCqTWlh+Tc13a
XDX+o6VY2cQ1a4sGiCWUn6UlLG3nRbco6x+mvEdCpUqWG+KqF5Y/k9rtrD9Z1/W9CpZ2k7yNxiLW
0ECWitLIkMqEUhXlybjzPw9SMVZdeRflB5l05riDUpEsrYwMJ7WFowIre3nSNYWa3PwCO2lo8W6s
p4Mp2xVjGoaV+TFzpupSNreqaJZw/VYobmGFrYtG9uLuFrcJarKytHOwZDWvw1X+6xVVHlj8nbrW
oLPR/NF9Fq0dyx+oUlmiSSd0higurcxKEhilZUWKUqgLFGHxEFVR0zy5+VT2S2k2sX1vqVqsun2M
t1HDIAxZJZTEtuknKNpLn44pJKkEo444qj7nSvyvl0mH6zqmo3v6Cs7bRbqOG0aGMsfq1h9ajSW2
Zi8fqxSD03YLsQDtiqbeXPJ/5f67cWllo+sap/zrslx6lowMMcvrIsBL1gSKXj6asGWpBPxbnFVa
8/JzyBoV0uv3+rXdpp1gg+r2sssC2cEnN5Fkij9L4ZA0jcOO9SfHFjKQiLOwYP8AmT+fl7qqy6X5
WL2WntVZdQNVuJR0pH/vtT/wXy6Y06XVdpGXphy72I/k2a/mboRPX1ZP+TL4S4ug/vo/H7n11gem
dirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirznV/NX5UWEV9dXujxvc2SXbzRLpo
ldhp8s6kessZgUu9hI0QklWvGu1NlV8nmf8AJV9Uu9AeCxfUoI4YrzTv0a7SCMKPSRk9A1WNWAK/
sdDTFUrXzF/zj9rMMd7b29rdw263AL2unXJVYrONTMJlhgoY1SReIkHE1otTiqKn8yflTHb3XlzR
dN0qXUbW0jvH0K9tZNPiW3UmVfV52kpVh9YZ1j9MvV+nxE4qn82k+SWs7Zrzytam9voIJH04WMMs
q+jEqRpJ8HH9woEa1NFAoNsxdRq44iBRlI8hEWfx70gOj0rQ7fUZdWbybHFdzlWmvooLV7hijrKp
bgfU+GRFf5gHqBlH53IN5Yp1/mn7BK1pENo/5ealbtdy6VptyjskMpltIWcMSERHV05ChoKHL8et
xTjxCW117iTVEdFpMLnRvKlpbTTXVjYW9r/eXEksUKR1Uo3JywA2MMZqf5V8BmUxJrm8u80fnX5E
8vtLH5S022vdSPKt3HCsNurN1JZQrye/HY/zYuuz9pQjtH1H7Hifmnzn5j80Xn1rWbx7gqT6MA+G
GMHtHGPhHz6nucLpc2onkNyKSYtLNPyb/wDJmaF/xlk/5MviXM0H99H8dH11gemdirsVdirsVdir
sVdirsVdirsVdirsVdirsVdirsVdirsVdiqQ3HkXypcw3UE9gJIr0yNdI0ktHMzXDyV+P9o3s3T+
b2FFXW3kfy5b3S3SRXDyKQ3pzXl3NCXCen6jQyyvE0jL9pyvJt+RJJxVQH5d+WBDbwgX/o2hdrWP
9J6iViLx+nWMfWKLwX+7p/d9U4nFV9j+X/lWxDLaW88UUkaxTQC8uzFIECqHliMpSSTjGqmRgXIF
C1MVTKx1mCdLgXK/U57M0uopGFEB+y/LYFWHQ5i6bVDJcSOGceY+4+4pIpjuu/m/+Xujcln1aK5m
X/dFnW4avhWOqA/6zDMpxcmsxQ5l5DrH50abqmuPcrbzW1tVfSlCIr/CaqZFVmqR48jnN9p9kZ8s
/EhKN91Vy5b738WiHa+O6INMT1tfzO83ub6aDVNbsGd/qs8VvM9swVivKJY09MbihoPbOigSYgkU
XVZvGym/VKPTbZK/8A+ev+pc1T/pCuP+aMm0flsn82XyLv8AAPnr/qXNU/6Qrj/mjFfy2T+bL5F3
+AfPX/Uuap/0hXH/ADRiv5bJ/Nl8inv5U6ff6f8Amtotpf20tpdxSv6lvOjRSLyt3YckcBhVSDiX
I0UTHOARR/Y+s8D0jsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirFvzH88f4M8vrq/
1L6/yuEt/Q9X0ftqzcuXCTpw6UxcfU5/ChxVbzH/AKGi/wC/Z/6fv+zfDTrv5X/o/b+xQn/5yfvm
r9X8vxR/y+pcs9PnSNK4KR/K39H7f2JVef8AOSvnSQEW1hYW4P7RSWRh9JkA/DGmuXas+gDHtR/O
38yr4FTq5toz+xbRRRfc4Xn/AMNjTRLtDMetJTdarq+reX7+TVrqe7ngnt5I5rmR5HpKrilXJPGl
CM106GrgR/FCQP8AmkfrLPjlPBLis0Yn5sbzZOvdiqN81NEun6PJ9Wtb28i0CM2dteTvCnxa3qAk
ZQk1vyYL74Q9N2f/AHMfj95X6Zqtxp1jNNpd9Hpa2mo6Vq01lbTPcwl7HTor66VIWaQXBjeORvTa
6jHL4eXguYmmg63r1p5t04HznrNt5mkvrKDzbZziOeK8Cy+kj2cyOiGJFZI3Wcow5GnI/BirBIpY
Tbwo1hYTW8lpdSXWoy3cq3Kzgz8TxF0ihlKpxX099tjXFXsnkVmb82/KjMSWOm6USTuSTokOAulP
+Ofj+a+ocDunYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXlv/ADkb/wCS/i/5j4f+
ISYuv7S/uvi+Y8LzrsVdirgCTQdcVZz5jn8ux6LBpj3LC8hSISi2VZKvEhUCQkqu3I/tVzlezceq
Ooll4f3ZMq4jWxN7c+4dHeauWEYhjJ9Qrlvy72DZ1To3YqivPOuS2Ol+VUhCO0OletLbtb2k8k8c
mr3yFYnntrhoigQkkll3+zWvJem7P/uY/H7yu8z6/Jba3p7+XpZx5OmawivJI9O0xpv9Jto47mqL
AsUdy6O6mIs1a7sAeILmIjWNOh1fQJfOGhadepr8y3fmC9tYrmK8itoo5wI5XWKOCQPHM6v6ICqq
gSPzC8Sq1pnnDTL/AMn6jrVxZaTbapbSyItpNb2CsWLWskUfpGzLuZEhuaOGAUmjCrLir0jQ7+LU
Pz30W/htFsIby3sJ47FKcYFl0iNxEtFQUQHj9kfIZEulP+Ofj+a+k8XdOxV2KuxV2KuxV2KuxV2K
uxV2KuxV2KuxV2KuxV2KuxV2KvLf+cjf/Jfxf8x8P/EJMXX9pf3XxfMeF512Ksuj/LjUW0DTtVkv
baGTU5Ihb2khcFYZ5fQSZ5ApRRz6itQN/bNd/KUPHGIfxX3/AMPFfSq9JF3d9K3dgOz5+GJ3zr5H
8fjkmMX5L+Z3t7h2ubRLu3to7o2BMwn/AHxYRxFTEB6jMjIFBJ5UFN65sLUdnT7xdKx/JDzK6cLa
8tbm7jkljuokE4ji9OGOZazNEIyzrKNvfv8AFxbT/Js+hF/FD/8AKm/MPGUrqGnM0aTuqepODILU
hJuJMIUcZDw+Iip3Hw/FjaP5On3x+39SIm/JfVbe2ee41GFVisLm/lURzKQbcFhGomWEkstOWwZC
aMo+Hk2y/k6QFk9LQOn3mn2cOmyNc6vBqUvl1RZyaLaS3Ulu8Wt3zxzTcCEaNmqpRtjhdp2f/cx+
P3lhEFgNMtphf2l95ltJHjtL6wvIZbe5iurpo72SRfriXAilu05RRSRBZG5Aj4juXMTfXNIW8kn8
6+XruG2l8zzmOCOVptKu7OxluY7SG5RLeS3F1GJpYxI5V+TryNfiOKpZoltJH5i1nTfMf13WrrVo
ovWvLeK5N9BFJzklki09DHIZDJawOZW+EBRy/vBir1bQUWP89dERZZ5lS2sFE12pS4cDSIhymViS
sh6sD3wF0p/xz8fzX0pgd07FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq8t/5yN/8A
Jfxf8x8P/EJMXX9pf3XxfMeF512Ks4jsvJy+S9Ne91eaW4lvU9e1il+KCIFfWpbHnT4ZH4uQPiU9
Q1BpMXjfmzLgAiTKJND6YgVLi5kyJAAugARRIsbUxw+BEGZJsbXy79vLff8AXSvFpH5PC3uIZdbu
zeC2j9C6CymA3Tlg9E+rLII0KqxJqSrEAchm63ahj03848vt+SsdJ/JeVPRTWbq2EMkv+mOJpJJl
MMZipEtrwVVlLD7QO3gwKO6eDTcuI/j4If8ARf5OFZR+l9RVik5hanIK0ZCQ8l+rDl628mxXiPhO
/wAWO6PD0386X4+CImsvyaitnMV9NNcJYXIjUm5cPekEw8j9Xt9lJ4qaAMB8SrT4ndlw6YDnvXnz
+QY1q3lLUPMOh6UkFul1Yy6QlrNJHqOm2s0VxBq17ccWivJomoY5lNad8Nux0E4jCLPf95QOqflf
5m1CxEKWVisMMsBjtbvWNN5PHZ6WlspZ7e52MksXwgHYkV+GpxsOZ4se8J5L5D85/pu8eKbRbry1
YfWZtF8satqlvdabwQ0SGGAzv6E0xPJGBRVHIMy1oWwviR7wxW4/LLzdJcyR2mjwaVpJiJFpZ6/p
gMtysciQyz87wqeHryLUCvBivc42F8SPeHpfkuIwfnH5btmeN5bWz0+3mMUiTIJYNHiikUSRl0bi
6EVUkYC6iwdZt+PS+n8XdOxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KsH/OL/AAn/
AITT/FP1r9G/Wo+P1Lj6vq8X4/b240ri4us4OD1/S8T/AOsf/wDtff8AJDHd1X+Cf0nf9Y//APa+
/wCSGO6/4J/Sd/1j/wD9r7/khjuv+Cf0nf8AWP8A/wBr7/khjuv+Cf0nf9Y//wDa+/5IY7r/AIJ/
Sd/1j/8A9r7/AJIY7r/gn9J3/WP/AP2vv+SGO6/4J/Sd/wBY/wD/AGvv+SGO6/4J/Sd/1j//ANr7
/khjuv8Agn9J3/WP/wD2vv8Akhjuv+Cf0nf9Y/8A/a+/5IY7r/gn9Jkf5df8qa/xrpf6C/TH6W9R
/qv1r0vR5em1efHf7NcDfpfy/iDgvifQOF3DsVdirsVdir//2Q==</xmpGImg:image> - </rdf:li> - </rdf:Alt> - </xmp:Thumbnails> - <xmpMM:InstanceID>uuid:973d6b06-b751-47b6-bd4c-5caf806c97f0</xmpMM:InstanceID> - <xmpMM:DocumentID>xmp.did:7c961dea-c85f-5642-a9b6-ce9c36455452</xmpMM:DocumentID> - <xmpMM:OriginalDocumentID>uuid:5D20892493BFDB11914A8590D31508C8</xmpMM:OriginalDocumentID> - <xmpMM:RenditionClass>proof:pdf</xmpMM:RenditionClass> - <xmpMM:DerivedFrom rdf:parseType="Resource"> - <stRef:instanceID>xmp.iid:7e154ddc-da90-1e48-990d-fa1af0ffddd4</stRef:instanceID> - <stRef:documentID>xmp.did:7e154ddc-da90-1e48-990d-fa1af0ffddd4</stRef:documentID> - <stRef:originalDocumentID>uuid:5D20892493BFDB11914A8590D31508C8</stRef:originalDocumentID> - <stRef:renditionClass>proof:pdf</stRef:renditionClass> - </xmpMM:DerivedFrom> - <xmpMM:History> - <rdf:Seq> - <rdf:li rdf:parseType="Resource"> - <stEvt:action>saved</stEvt:action> - <stEvt:instanceID>xmp.iid:e4f01fd1-e829-9746-8ac4-a34b41fed565</stEvt:instanceID> - <stEvt:when>2023-08-16T19:47:56+02:00</stEvt:when> - <stEvt:softwareAgent>Adobe Illustrator 26.0 (Windows)</stEvt:softwareAgent> - <stEvt:changed>/</stEvt:changed> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <stEvt:action>saved</stEvt:action> - <stEvt:instanceID>xmp.iid:7c961dea-c85f-5642-a9b6-ce9c36455452</stEvt:instanceID> - <stEvt:when>2023-08-22T01:58:51+02:00</stEvt:when> - <stEvt:softwareAgent>Adobe Illustrator 26.0 (Windows)</stEvt:softwareAgent> - <stEvt:changed>/</stEvt:changed> - </rdf:li> - </rdf:Seq> - </xmpMM:History> - <xmpMM:Manifest> - <rdf:Seq> - <rdf:li rdf:parseType="Resource"> - <stMfs:linkForm>EmbedByReference</stMfs:linkForm> - <stMfs:reference rdf:parseType="Resource"> - <stRef:filePath>C:\Users\simon\Documents\Arbeit\Uni Urban Mobil\instagram logo.png</stRef:filePath> - <stRef:documentID>0</stRef:documentID> - <stRef:instanceID>0</stRef:instanceID> - </stMfs:reference> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <stMfs:linkForm>EmbedByReference</stMfs:linkForm> - <stMfs:reference rdf:parseType="Resource"> - <stRef:filePath>C:\Users\simon\Documents\Arbeit\Uni Urban Mobil\Logo Facebook.png</stRef:filePath> - <stRef:documentID>0</stRef:documentID> - <stRef:instanceID>0</stRef:instanceID> - </stMfs:reference> - </rdf:li> - </rdf:Seq> - </xmpMM:Manifest> - <illustrator:StartupProfile>Print</illustrator:StartupProfile> - <illustrator:CreatorSubTool>Adobe Illustrator</illustrator:CreatorSubTool> - <xmpTPg:HasVisibleOverprint>False</xmpTPg:HasVisibleOverprint> - <xmpTPg:HasVisibleTransparency>True</xmpTPg:HasVisibleTransparency> - <xmpTPg:NPages>1</xmpTPg:NPages> - <xmpTPg:MaxPageSize rdf:parseType="Resource"> - <stDim:w>14.800000</stDim:w> - <stDim:h>21.000000</stDim:h> - <stDim:unit>Centimeters</stDim:unit> - </xmpTPg:MaxPageSize> - <xmpTPg:Fonts> - <rdf:Bag> - <rdf:li rdf:parseType="Resource"> - <stFnt:fontName>Catamaran-Black</stFnt:fontName> - <stFnt:fontFamily>Catamaran</stFnt:fontFamily> - <stFnt:fontFace>Black</stFnt:fontFace> - <stFnt:fontType>TrueType</stFnt:fontType> - <stFnt:versionString>Version 1.100</stFnt:versionString> - <stFnt:composite>False</stFnt:composite> - <stFnt:fontFileName>Catamaran-Black.ttf</stFnt:fontFileName> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <stFnt:fontName>Catamaran-Medium</stFnt:fontName> - <stFnt:fontFamily>Catamaran</stFnt:fontFamily> - <stFnt:fontFace>Medium</stFnt:fontFace> - <stFnt:fontType>TrueType</stFnt:fontType> - <stFnt:versionString>Version 1.100</stFnt:versionString> - <stFnt:composite>False</stFnt:composite> - <stFnt:fontFileName>Catamaran-Medium.ttf</stFnt:fontFileName> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <stFnt:fontName>MyriadPro-Regular</stFnt:fontName> - <stFnt:fontFamily>Myriad Pro</stFnt:fontFamily> - <stFnt:fontFace>Regular</stFnt:fontFace> - <stFnt:fontType>Open Type</stFnt:fontType> - <stFnt:versionString>Version 2.106;PS 2.000;hotconv 1.0.70;makeotf.lib2.5.58329</stFnt:versionString> - <stFnt:composite>False</stFnt:composite> - <stFnt:fontFileName>MyriadPro-Regular.otf</stFnt:fontFileName> - </rdf:li> - </rdf:Bag> - </xmpTPg:Fonts> - <xmpTPg:PlateNames> - <rdf:Seq> - <rdf:li>Cyan</rdf:li> - <rdf:li>Magenta</rdf:li> - <rdf:li>Yellow</rdf:li> - <rdf:li>Black</rdf:li> - </rdf:Seq> - </xmpTPg:PlateNames> - <xmpTPg:SwatchGroups> - <rdf:Seq> - <rdf:li rdf:parseType="Resource"> - <xmpG:groupName>Standard-Farbfeldgruppe</xmpG:groupName> - <xmpG:groupType>0</xmpG:groupType> - <xmpG:Colorants> - <rdf:Seq> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>Weiß</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>0.000000</xmpG:cyan> - <xmpG:magenta>0.000000</xmpG:magenta> - <xmpG:yellow>0.000000</xmpG:yellow> - <xmpG:black>0.000000</xmpG:black> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>Schwarz</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>0.000000</xmpG:cyan> - <xmpG:magenta>0.000000</xmpG:magenta> - <xmpG:yellow>0.000000</xmpG:yellow> - <xmpG:black>100.000000</xmpG:black> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>CMYK Rot</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>0.000000</xmpG:cyan> - <xmpG:magenta>100.000000</xmpG:magenta> - <xmpG:yellow>100.000000</xmpG:yellow> - <xmpG:black>0.000000</xmpG:black> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>CMYK Gelb</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>0.000000</xmpG:cyan> - <xmpG:magenta>0.000000</xmpG:magenta> - <xmpG:yellow>100.000000</xmpG:yellow> - <xmpG:black>0.000000</xmpG:black> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>CMYK Grün</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>100.000000</xmpG:cyan> - <xmpG:magenta>0.000000</xmpG:magenta> - <xmpG:yellow>100.000000</xmpG:yellow> - <xmpG:black>0.000000</xmpG:black> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>CMYK Cyan</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>100.000000</xmpG:cyan> - <xmpG:magenta>0.000000</xmpG:magenta> - <xmpG:yellow>0.000000</xmpG:yellow> - <xmpG:black>0.000000</xmpG:black> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>CMYK Blau</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>100.000000</xmpG:cyan> - <xmpG:magenta>100.000000</xmpG:magenta> - <xmpG:yellow>0.000000</xmpG:yellow> - <xmpG:black>0.000000</xmpG:black> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>CMYK Magenta</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>0.000000</xmpG:cyan> - <xmpG:magenta>100.000000</xmpG:magenta> - <xmpG:yellow>0.000000</xmpG:yellow> - <xmpG:black>0.000000</xmpG:black> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>C=15 M=100 Y=90 K=10</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>15.000000</xmpG:cyan> - <xmpG:magenta>100.000000</xmpG:magenta> - <xmpG:yellow>90.000000</xmpG:yellow> - <xmpG:black>10.000000</xmpG:black> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>C=0 M=90 Y=85 K=0</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>0.000000</xmpG:cyan> - <xmpG:magenta>90.000000</xmpG:magenta> - <xmpG:yellow>85.000000</xmpG:yellow> - <xmpG:black>0.000000</xmpG:black> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>C=0 M=80 Y=95 K=0</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>0.000000</xmpG:cyan> - <xmpG:magenta>80.000000</xmpG:magenta> - <xmpG:yellow>95.000000</xmpG:yellow> - <xmpG:black>0.000000</xmpG:black> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>C=0 M=50 Y=100 K=0</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>0.000000</xmpG:cyan> - <xmpG:magenta>50.000000</xmpG:magenta> - <xmpG:yellow>100.000000</xmpG:yellow> - <xmpG:black>0.000000</xmpG:black> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>C=0 M=35 Y=85 K=0</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>0.000000</xmpG:cyan> - <xmpG:magenta>35.000000</xmpG:magenta> - <xmpG:yellow>85.000000</xmpG:yellow> - <xmpG:black>0.000000</xmpG:black> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>C=5 M=0 Y=90 K=0</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>5.000000</xmpG:cyan> - <xmpG:magenta>0.000000</xmpG:magenta> - <xmpG:yellow>90.000000</xmpG:yellow> - <xmpG:black>0.000000</xmpG:black> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>C=20 M=0 Y=100 K=0</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>20.000000</xmpG:cyan> - <xmpG:magenta>0.000000</xmpG:magenta> - <xmpG:yellow>100.000000</xmpG:yellow> - <xmpG:black>0.000000</xmpG:black> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>C=50 M=0 Y=100 K=0</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>50.000000</xmpG:cyan> - <xmpG:magenta>0.000000</xmpG:magenta> - <xmpG:yellow>100.000000</xmpG:yellow> - <xmpG:black>0.000000</xmpG:black> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>C=75 M=0 Y=100 K=0</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>75.000000</xmpG:cyan> - <xmpG:magenta>0.000000</xmpG:magenta> - <xmpG:yellow>100.000000</xmpG:yellow> - <xmpG:black>0.000000</xmpG:black> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>C=85 M=10 Y=100 K=10</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>85.000000</xmpG:cyan> - <xmpG:magenta>10.000000</xmpG:magenta> - <xmpG:yellow>100.000000</xmpG:yellow> - <xmpG:black>10.000000</xmpG:black> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>C=90 M=30 Y=95 K=30</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>90.000000</xmpG:cyan> - <xmpG:magenta>30.000000</xmpG:magenta> - <xmpG:yellow>95.000000</xmpG:yellow> - <xmpG:black>30.000000</xmpG:black> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>C=75 M=0 Y=75 K=0</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>75.000000</xmpG:cyan> - <xmpG:magenta>0.000000</xmpG:magenta> - <xmpG:yellow>75.000000</xmpG:yellow> - <xmpG:black>0.000000</xmpG:black> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>C=80 M=10 Y=45 K=0</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>80.000000</xmpG:cyan> - <xmpG:magenta>10.000000</xmpG:magenta> - <xmpG:yellow>45.000000</xmpG:yellow> - <xmpG:black>0.000000</xmpG:black> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>C=70 M=15 Y=0 K=0</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>70.000000</xmpG:cyan> - <xmpG:magenta>15.000000</xmpG:magenta> - <xmpG:yellow>0.000000</xmpG:yellow> - <xmpG:black>0.000000</xmpG:black> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>C=85 M=50 Y=0 K=0</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>85.000000</xmpG:cyan> - <xmpG:magenta>50.000000</xmpG:magenta> - <xmpG:yellow>0.000000</xmpG:yellow> - <xmpG:black>0.000000</xmpG:black> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>C=100 M=95 Y=5 K=0</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>100.000000</xmpG:cyan> - <xmpG:magenta>95.000000</xmpG:magenta> - <xmpG:yellow>5.000000</xmpG:yellow> - <xmpG:black>0.000000</xmpG:black> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>C=100 M=100 Y=25 K=25</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>100.000000</xmpG:cyan> - <xmpG:magenta>100.000000</xmpG:magenta> - <xmpG:yellow>25.000000</xmpG:yellow> - <xmpG:black>25.000000</xmpG:black> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>C=75 M=100 Y=0 K=0</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>75.000000</xmpG:cyan> - <xmpG:magenta>100.000000</xmpG:magenta> - <xmpG:yellow>0.000000</xmpG:yellow> - <xmpG:black>0.000000</xmpG:black> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>C=50 M=100 Y=0 K=0</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>50.000000</xmpG:cyan> - <xmpG:magenta>100.000000</xmpG:magenta> - <xmpG:yellow>0.000000</xmpG:yellow> - <xmpG:black>0.000000</xmpG:black> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>C=35 M=100 Y=35 K=10</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>35.000000</xmpG:cyan> - <xmpG:magenta>100.000000</xmpG:magenta> - <xmpG:yellow>35.000000</xmpG:yellow> - <xmpG:black>10.000000</xmpG:black> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>C=10 M=100 Y=50 K=0</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>10.000000</xmpG:cyan> - <xmpG:magenta>100.000000</xmpG:magenta> - <xmpG:yellow>50.000000</xmpG:yellow> - <xmpG:black>0.000000</xmpG:black> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>C=0 M=95 Y=20 K=0</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>0.000000</xmpG:cyan> - <xmpG:magenta>95.000000</xmpG:magenta> - <xmpG:yellow>20.000000</xmpG:yellow> - <xmpG:black>0.000000</xmpG:black> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>C=25 M=25 Y=40 K=0</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>25.000000</xmpG:cyan> - <xmpG:magenta>25.000000</xmpG:magenta> - <xmpG:yellow>40.000000</xmpG:yellow> - <xmpG:black>0.000000</xmpG:black> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>C=40 M=45 Y=50 K=5</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>40.000000</xmpG:cyan> - <xmpG:magenta>45.000000</xmpG:magenta> - <xmpG:yellow>50.000000</xmpG:yellow> - <xmpG:black>5.000000</xmpG:black> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>C=50 M=50 Y=60 K=25</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>50.000000</xmpG:cyan> - <xmpG:magenta>50.000000</xmpG:magenta> - <xmpG:yellow>60.000000</xmpG:yellow> - <xmpG:black>25.000000</xmpG:black> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>C=55 M=60 Y=65 K=40</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>55.000000</xmpG:cyan> - <xmpG:magenta>60.000000</xmpG:magenta> - <xmpG:yellow>65.000000</xmpG:yellow> - <xmpG:black>40.000000</xmpG:black> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>C=25 M=40 Y=65 K=0</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>25.000000</xmpG:cyan> - <xmpG:magenta>40.000000</xmpG:magenta> - <xmpG:yellow>65.000000</xmpG:yellow> - <xmpG:black>0.000000</xmpG:black> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>C=30 M=50 Y=75 K=10</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>30.000000</xmpG:cyan> - <xmpG:magenta>50.000000</xmpG:magenta> - <xmpG:yellow>75.000000</xmpG:yellow> - <xmpG:black>10.000000</xmpG:black> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>C=35 M=60 Y=80 K=25</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>35.000000</xmpG:cyan> - <xmpG:magenta>60.000000</xmpG:magenta> - <xmpG:yellow>80.000000</xmpG:yellow> - <xmpG:black>25.000000</xmpG:black> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>C=40 M=65 Y=90 K=35</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>40.000000</xmpG:cyan> - <xmpG:magenta>65.000000</xmpG:magenta> - <xmpG:yellow>90.000000</xmpG:yellow> - <xmpG:black>35.000000</xmpG:black> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>C=40 M=70 Y=100 K=50</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>40.000000</xmpG:cyan> - <xmpG:magenta>70.000000</xmpG:magenta> - <xmpG:yellow>100.000000</xmpG:yellow> - <xmpG:black>50.000000</xmpG:black> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>C=50 M=70 Y=80 K=70</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>50.000000</xmpG:cyan> - <xmpG:magenta>70.000000</xmpG:magenta> - <xmpG:yellow>80.000000</xmpG:yellow> - <xmpG:black>70.000000</xmpG:black> - </rdf:li> - </rdf:Seq> - </xmpG:Colorants> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:groupName>Graustufen</xmpG:groupName> - <xmpG:groupType>1</xmpG:groupType> - <xmpG:Colorants> - <rdf:Seq> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>C=0 M=0 Y=0 K=100</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>0.000000</xmpG:cyan> - <xmpG:magenta>0.000000</xmpG:magenta> - <xmpG:yellow>0.000000</xmpG:yellow> - <xmpG:black>100.000000</xmpG:black> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>C=0 M=0 Y=0 K=90</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>0.000000</xmpG:cyan> - <xmpG:magenta>0.000000</xmpG:magenta> - <xmpG:yellow>0.000000</xmpG:yellow> - <xmpG:black>89.999400</xmpG:black> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>C=0 M=0 Y=0 K=80</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>0.000000</xmpG:cyan> - <xmpG:magenta>0.000000</xmpG:magenta> - <xmpG:yellow>0.000000</xmpG:yellow> - <xmpG:black>79.998800</xmpG:black> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>C=0 M=0 Y=0 K=70</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>0.000000</xmpG:cyan> - <xmpG:magenta>0.000000</xmpG:magenta> - <xmpG:yellow>0.000000</xmpG:yellow> - <xmpG:black>69.999700</xmpG:black> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>C=0 M=0 Y=0 K=60</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>0.000000</xmpG:cyan> - <xmpG:magenta>0.000000</xmpG:magenta> - <xmpG:yellow>0.000000</xmpG:yellow> - <xmpG:black>59.999100</xmpG:black> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>C=0 M=0 Y=0 K=50</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>0.000000</xmpG:cyan> - <xmpG:magenta>0.000000</xmpG:magenta> - <xmpG:yellow>0.000000</xmpG:yellow> - <xmpG:black>50.000000</xmpG:black> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>C=0 M=0 Y=0 K=40</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>0.000000</xmpG:cyan> - <xmpG:magenta>0.000000</xmpG:magenta> - <xmpG:yellow>0.000000</xmpG:yellow> - <xmpG:black>39.999400</xmpG:black> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>C=0 M=0 Y=0 K=30</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>0.000000</xmpG:cyan> - <xmpG:magenta>0.000000</xmpG:magenta> - <xmpG:yellow>0.000000</xmpG:yellow> - <xmpG:black>29.998800</xmpG:black> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>C=0 M=0 Y=0 K=20</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>0.000000</xmpG:cyan> - <xmpG:magenta>0.000000</xmpG:magenta> - <xmpG:yellow>0.000000</xmpG:yellow> - <xmpG:black>19.999700</xmpG:black> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>C=0 M=0 Y=0 K=10</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>0.000000</xmpG:cyan> - <xmpG:magenta>0.000000</xmpG:magenta> - <xmpG:yellow>0.000000</xmpG:yellow> - <xmpG:black>9.999100</xmpG:black> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>C=0 M=0 Y=0 K=5</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>0.000000</xmpG:cyan> - <xmpG:magenta>0.000000</xmpG:magenta> - <xmpG:yellow>0.000000</xmpG:yellow> - <xmpG:black>4.998800</xmpG:black> - </rdf:li> - </rdf:Seq> - </xmpG:Colorants> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:groupName>Strahlende Farben</xmpG:groupName> - <xmpG:groupType>1</xmpG:groupType> - <xmpG:Colorants> - <rdf:Seq> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>C=0 M=100 Y=100 K=0</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>0.000000</xmpG:cyan> - <xmpG:magenta>100.000000</xmpG:magenta> - <xmpG:yellow>100.000000</xmpG:yellow> - <xmpG:black>0.000000</xmpG:black> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>C=0 M=75 Y=100 K=0</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>0.000000</xmpG:cyan> - <xmpG:magenta>75.000000</xmpG:magenta> - <xmpG:yellow>100.000000</xmpG:yellow> - <xmpG:black>0.000000</xmpG:black> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>C=0 M=10 Y=95 K=0</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>0.000000</xmpG:cyan> - <xmpG:magenta>10.000000</xmpG:magenta> - <xmpG:yellow>95.000000</xmpG:yellow> - <xmpG:black>0.000000</xmpG:black> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>C=85 M=10 Y=100 K=0</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>85.000000</xmpG:cyan> - <xmpG:magenta>10.000000</xmpG:magenta> - <xmpG:yellow>100.000000</xmpG:yellow> - <xmpG:black>0.000000</xmpG:black> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>C=100 M=90 Y=0 K=0</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>100.000000</xmpG:cyan> - <xmpG:magenta>90.000000</xmpG:magenta> - <xmpG:yellow>0.000000</xmpG:yellow> - <xmpG:black>0.000000</xmpG:black> - </rdf:li> - <rdf:li rdf:parseType="Resource"> - <xmpG:swatchName>C=60 M=90 Y=0 K=0</xmpG:swatchName> - <xmpG:mode>CMYK</xmpG:mode> - <xmpG:type>PROCESS</xmpG:type> - <xmpG:cyan>60.000000</xmpG:cyan> - <xmpG:magenta>90.000000</xmpG:magenta> - <xmpG:yellow>0.003100</xmpG:yellow> - <xmpG:black>0.003100</xmpG:black> - </rdf:li> - </rdf:Seq> - </xmpG:Colorants> - </rdf:li> - </rdf:Seq> - </xmpTPg:SwatchGroups> - <pdf:Producer>Adobe PDF library 16.03</pdf:Producer> - </rdf:Description> - </rdf:RDF> -</x:xmpmeta> - - - - - - - - - - - - - - - - - - - - - -<?xpacket end="w"?> -endstream endobj 3 0 obj <</Count 1/Kids[8 0 R]/Type/Pages>> endobj 8 0 obj <</ArtBox[0.0 0.0 419.528 595.276]/BleedBox[0.0 0.0 419.528 595.276]/Contents 9 0 R/CropBox[0.0 0.0 419.528 595.276]/Group 10 0 R/LastModified(D:20230822015854+02'00')/MediaBox[0.0 0.0 419.528 595.276]/Parent 3 0 R/Resources<</ColorSpace<</CS0 11 0 R>>/ExtGState<</GS0 12 0 R>>/Font<</T1_0 7 0 R/TT0 5 0 R/TT1 6 0 R>>/ProcSet[/PDF/Text/ImageC/ImageI]/Properties<</MC0 13 0 R>>/XObject<</Im0 14 0 R/Im1 15 0 R>>>>/Thumb 16 0 R/TrimBox[0.0 0.0 419.528 595.276]/Type/Page>> endobj 9 0 obj <</Filter/FlateDecode/Length 5136>>stream -H��WMs��ﯘ��F;߳�T�������q*�P�IBZ����t/�b?�S*�}�zz�������wG���oF���ߘa��D3��}���Ӈd��y;�w��hF�d��&M����q7��7���Fn�?��Ln���Ϳ�I)Z�#EK�u���������!ک���Z��D�l��BoGz|�s5l*��RX=��L�KY�Zn���\-�y��j!IGn�}���o�P��泷%�l��8����]�dl)���R�؉�nBmj;�;�T*T�1��*�Y��*�ܙ�.*^,�ZEr�[�B?o�������J{���K��G�m���f�R4}��L��S��ë�������f�?�_m���.��ʩ����6ך��6���9�XW�.�no/�O�w�CcQ-����:�?vǻ������p��C{~d�獻�q����H�� �ڐS���t�۟��������5��ӧ�y�_ݚ�_B�ǹ&���8���zxu���h��igޞ.�O�a2����.[F��9��ͼ���K���V��O�����t�;���W�/����?�/N��v��x�۟v�q4O7��������x����������@Ϸ����r�q�����<�������<����/��-%�]��º�m�)υ��p�wT�?�v:�,��=i�V&O�p�5����h'�[��Y`s�ٺ�n�j���B6�8�?�j�er�`4j�p^�Ը Ǧ.�1A�::��A�3�-!�_�]�pm?U[?[�lqµ�DC��k��3b��u����a�L�\s�;� ��5�8N�6 ��uQc��-���dK����['8����/lL��8YL^��`)9����L3���Zh��-��B�^���EN}�EN��ȩË :L�m.���"�J>���z���[������Dm�r���*�R���:*J�(������:ʍ���L:**��XDҹ����DzʋB�BIX�3�i���txQI����2 ,���g�T�Y+������tx�KO\�r噜�g�����!�`.� -~6�zMʽ��^*`) -Hl) -�b|ib>�!�=����x�rl��t���`/r�li��\zl�K��0�,��/��`K� �O� ��/W|����w��� -�m�-����t��a�z���}P1� -^w_Jvl%�e_:,F�/f���� -��C�ҹ�T����Ň�->l�!�ه ��l��0[)C0UV��f�5������.�z`��Εb��x�Oޖ^�+����5��:�Qe8�TYG���� ��~�(���� -J;���P�OG�� )֯�)�\+t=X�e��V�y�.���>�@`VP�B�=��y�Ю�Y�(����MT0�퇫��l�OO�+�й��+��QQWGE@�.�h��h�sE#�� "��_ԨC~�� @a�*GP�������b�� -jo1��)@�� 07B�T��M��:�E`���gT9�.�@�������eq��,���Qi0}�E?������W����b��V�APp�*�pY^�� 2w�r�5�#� -:WT�\���*���u�=�MΠ��� T�(���_zXn/��ݥ��� -�4ښ�=*sFeJ��5��J�N%"��K�B�n#����� wsr�����I������p�h��J����G�r��\)�)~Bh��e�����\���!��jѭ�-Z�,� -UC������"@y� 2[=:�}Ef^����G*����*@��zd�+u��� ���S{���r#��J#�.7@E{=e�>d�Z��c��+��3�&T� @���&Bh?xBn�PYz@���HP�^_V�\��,=HY�w6L�{Fqu*w@E>��|��\e��TYG��j��:W���Re}C\e��o�2�M���$��ٺBו�� �<=��\Vp����7�r�j�^Ez��@ ���J� ���$+�e������"���#���:��P�OGY>=)He.��ʝ�/+��ܹ�gDYl�ro�7Ç�{[K*��f��K�Mv�Ŝ�z�y���z^�0�o��������:9s������{��C���k��ƛ���0���nx�u��3ۛ�M>K���s���!�]�ۇ����W�h^]^�_��������Cf{���nKM����A����� ��H���qv�k�c4��h�����J�0Ç���K����iR�:�!��T�d6�ٱ �`3�� ՇJF$�! {5��i�߭עY�c�Lm�:^�L;��c0��t��?'ڜ�3F��T�j9ձ���{�O[(erE�L�:�&Z�j_��dJ-���������B��%]����Y�ލq��Pt�#�P����M"7E*-B[�;?���s$1]j�֞��J��m&����Z�%Q8:XT�aT�)����j#m�tnC�~���h��b3���4k���M�=5z�ّ���[�v�Ly�%=�%��S�US�vruU -[i�"�;�B)�%�����1W�I�*�Ң�n�QEK��(iz�^�c�l6��I6"8�e+�;�����ZeM��B�N���R�~ճ�> -�2�/|��Z������7�#+���5F���W�]�yGa";Qn-��������.7��3hC�R(o�x��i�Zl:�K��D�=��L��x -M���.���uq��ȥ�&]OS_���.�Gv��ƺ�@8�Uh:��#6:�\\�R��'���=#�P�a��$��b�O[w�ޕ6��v��ճmej�sضN�?f����{�sAkN�P�X>_@� ��B��q�xo�g��Й���;+��Lp�1��z�V�¦���=�CP�E��M����3�EG�!�d�-��uF��3�e�=�8v�=�s����~����o� -콞�Ei������������[ME��z�]\����i�qtȺ��v�i<�����xqu�E����W�;��L���\L;l�s��S�����5��>��~w#e�a��ai��(�X�vm���}�7�R.' ���i�t����t��y|>k�qo��� hb�q�y�G���Fu�#r���L)�B����&���<F�(c����l��bMG��B�-<�+-�e� �D�"�õ$Yt0�����Ƈ��=�o*�y��o�.�E�@�� Ne���s�� S@����F<�y:�$�=�\�a�`�'w���8�H1Z���L��� ~@(8Azt����l�����_I��Q��N+.�N����i�������A���^Q}�*g���\�Z�x(�C�-��l��s��O�r��u�1�~j��Y~�k��.��|�HE]���pk �8HE��]����6뱬��`;�D��|_Q�k)w�.&�����uq�H����ӿ#m��fv b�^׀b2�˖��V;IŘ��<��[P��q�H�����t� -$�E���/�]��� "*�� ��S�cW�;b����q{H���,��� p*����0��-f�(�y��� -�1���o��y��/~�!���n���d?�5v�o��R����=^('�3�~����K~l;��=贗��u�)e�h��Hl�ۋԀ���r�t� $[�p��F����v�D��6,yLzjc���?5 ��5��kʰ5Փ���3c��q:����ې�^��+�7�Cә� iZ���D[%�To���D���63_{�{ٔ���ƈ�Z{{]�b�5��nl�>+[xj�����P��ChGu�DB�i��^�ܱ"[R�U2�S��[h���q���C�v��Q�W����F�kD��#�j�P,����u��n�#V W�J�@�\2��j�SI#��{�����W�$h�WU�M4e%�tM"�\n%�4�m�ȅ$c�e�Ą���M�.�[�K��j-�-5�g2s ց�hGoЎA3�D.]���|�U5Er���X�i�������*!氃�<G�hY��\��J���� -%"T��dQ�Ee����-���T�ـ�4,�@���Y��VC�uy/$Ŋ�NT��2�h~�$��&���R�bYE��A����G���c;�#x�3b�-I�%���ޢ0���pn�<�nQ�1`ѹF�-�$w�6��+�Xn������9BCf��L>%�H#H�]�����o���d��:�Ս�����(a%�����Φ��A��Ӧ�A��Ng&� ���mL�m&�����z٢�KQ����R�5�������xc����#zd���nqZ����F��V���x���)��I��֒]b�q�D%��M:j`���X<ct�f�����*�R�PL�+�&SBa��D�H� �/��}<�z������?���e����V�ו��I���D�eQ���t�s3�\������@�����N��>?�x�~���s�; �ш�)ڔ�@��eh�u�{*��yd��[��k6���� ��[�:~������ڊ� ��x]�X�[;�uV��g҄$�1�$�29��E������n0�^��|� -�20���d~�-62��k>t�������+*/k��6�V���j�}� �/g$wغ�Ԟ�廩`���u�y��⮌�}�{��k��{�&�g%�J�xUu�%�\~�$����,�ű���]๖b�a�k�f�w�Jż�l���L��I|.��Y��H!���[QU�Hb -�N�482�=ɫ���q)���zTx ڔ$��!+[{h�Q�N���'�}�����*ލ$�2Tg����_\�¤.�:BU ����ku���C�����lT&2M��Cj��z��U'o��%��|Χ��Q.�2�Z�,=%��s���ϭ�t������jѴ�`����*�_$FeS��������t��w0�>6�����ˡ����zs2�SΌ� ���ء������>� -�d�G���s������T5^�L�) -(3���z�����):�c��h1w���"x��ݬ#��ɉ��d��Դ[tzo���x:G�8)b�����Ǘ�_�~{?��?���7�������s -endstream endobj 10 0 obj <</CS/DeviceCMYK/I false/K false/S/Transparency>> endobj 16 0 obj <</BitsPerComponent 8/ColorSpace 17 0 R/Filter[/ASCII85Decode/FlateDecode]/Height 74/Length 997/Width 52>>stream -8;Wj<ad(p0%.s`';>A/U-aMdn=0^,uq'_f]L(K_l]*;HX@R0ed@d+kRaBqP!6@:F& -VZUSG(7B4%m.^>P`9[8)q><MdIq#7;lhUH+Ot5R_*5bq)'$Ck^XrW3hWS\7mJgd8! -e6$_`UXZW)asG5i#2fQI!>"I*_2OhaDAajN85H_',=6K2<++s*eh[A/j-H?rKG`L7 -UG/F+&94[9rVA)Y8f73ag<m%K,+6,:piuFdJ'mZt\\[/F/t5ZVH(u#F@#9L><PBM! -N&3-_4U9+9,1&MA1OSB",LB',7^R3u`A72JQqZ(#7M?&!DMY$[b(<.q7R;kN7#EbE -Z"VW%oj-fZW8iK(W5=/m7pVs)#g/K%@;PmO@5&^&Mon_X8-m4DplEmSomCm+bl#,= -YKd7)7Gf5-dJXO]gsS!9qfk>JZ@>"e-'hj&4!^tKJ[2KAD"r:N%%%^9+LToMPPOO; -B>`i?Pk8GeC?$E1diA0Il2b*68P=A7^Q*A]+N_4,lIoC(p1#,+oKhd62mqNsJ_%4< -g4I.+oj)Wq+DY#^=mAFhK>t=3*lZaG,XElF_`jBDTV6/"dU_:sVl/#qL;@>78fr-8 -=*Hjo*buU_=2>:a\1o7`Y4G*[lk>8PXYf&V7gM$lN_(8VU<TEn/WfM0>J':\,=#Je -/+GmHp6:JfDLt86N.f9O0ZO3MNmr4f\;_UY"$f66:l.GXFg4S7LS]LOl'F28Q23LR -JSkr?D1(YGbO0V)egWi+$1e4`LVm8g]aMjO%XPK.NN/RH)s_`kk,BE74lQ4d[]s*G -h0&IUH_mAn:\hJOPLZ2_0Yg/7_@<%\od([ZG,'&,#(]@,>3oQUZF3Q5^2MdWB/O=A -A'/S%^ba8=gm[m#l!-nq<6sq2iGF"lK=C:V9m,p5Kl)bBJ\7Npn_=]+TtTIK[s,Z( -I_olLCr]a5Kul,8:fe8+Nh:<ph">o@MOe,JauNM2H%*d550UGor/@p`jid["7K*bs -,54=C~> -endstream endobj 17 0 obj [/Indexed/DeviceRGB 255 18 0 R] endobj 18 0 obj <</Filter[/ASCII85Decode/FlateDecode]/Length 428>>stream -8;X]O>EqN@%''O_@%e@?J;%+8(9e>X=MR6S?i^YgA3=].HDXF.R$lIL@"pJ+EP(%0 -b]6ajmNZn*!='OQZeQ^Y*,=]?C.B+\Ulg9dhD*"iC[;*=3`oP1[!S^)?1)IZ4dup` -E1r!/,*0[*9.aFIR2&b-C#s<Xl5FH@[<=!#6V)uDBXnIr.F>oRZ7Dl%MLY\.?d>Mn -6%Q2oYfNRF$$+ON<+]RUJmC0I<jlL.oXisZ;SYU[/7#<&37rclQKqeJe#,UF7Rgb1 -VNWFKf>nDZ4OTs0S!saG>GGKUlQ*Q?45:CI&4J'_2j<etJICj7e7nPMb=O6S7UOH< -PO7r\I.Hu&e0d&E<.')fERr/l+*W,)q^D*ai5<uuLX.7g/>$XKrcYp0n+Xl_nU*O( -l[$6Nn+Z_Nq0]s7hs]`XX1nZ8&94a\~> -endstream endobj 14 0 obj <</BitsPerComponent 8/ColorSpace 11 0 R/Decode[0.0 255.0]/Filter/FlateDecode/Height 700/Intent/RelativeColorimetric/Length 10534/Name/X/SMask 19 0 R/Subtype/Image/Type/XObject/Width 700>>stream -H����sTgp�!.݅8����d ���@Ҙ`wq�ނ�����N�V�L�H�B�R����c)�mk�PE�����}�9�{�y������9��p���������������������������������������������ʗ#"�YBX�}�$�YBX�}�$�YBX�}�$�YR�P�F������^���"t7�K����F}���j�u�n�x{�>6����Z���Bw�Y;<Qk/f��۵��� ��"��r;{l�1>��꪿:՜��yb�]>K;j]�'w��<�t�����b�a��.4�!t�h�1�I�]�8/1Wt�������m3�W��Vr�5�������%�d��Ζw-H��EV��^!<t�ɉf��d����+���ך��`��~`Cz�`��r�WϤW(�[��� -a��%����ڻ#��/�;B�ѕ^���Ʋ� -����t&��+Pt�Q��h~����y��Aw���ހ��1� �@w,ե7�V�Ho�������_�]��Cw?gmLz�Vf�7���n�Ԃ��M����}�ĸ�AdM� ؠ����+�A8�%� x����l�?��&�7������#����e� Ȓ��D&����)� ��nj_�0�!�E��=9-���/��nw���phNz��R�n�/��+3�T�fw'�P�ߒޠ��ې^@��%�����;&��R��?F�u�)��f��p�Vw���K��H��x�������ݖ�Ft�!�AY�t�@_z;Vf�7('��Ȥ7�eeYz�2R��ƴ���OKo0ZݭK/`Ә�#E�ݦ�f�[��yw��'�ʒ�C�������(��1w�&�@�K/0X�ݝ�^ ݎ�����S���C�߯H����6������\_z�ش�(cwU~��MH/�U|�����Z=+���b��sx�z3&��"��W������7��������g �6��� -ND'�Io�x�{ � ��<Mwg�H�!�������D�s"��.K/�_Ϣ�nKz���I/����)�RT�^ �^ Q+��70��1�ҵOx�����Ր�I�,:��%�����pxhVp���NΆ��ɍ��ݳ��h�d��d�ݝ�O�N - ��ݺ�\(�]f���NɌ�b2O���D��@m���;+0��N��i���Ï���?Q�u�|"���b��"߬��F�yƺ�뇝.&Î����A���n�i��� -9*Z^K�8*:n����Í���M��ݩ`���j���.�T�@s�tw%�`P3�Fw/fA���0?MLt��1�� 1�Bw{f��?Otw��`�����>|X�>A}w��=�<������y��?���ʻ{����U����M���o~˫��-��C�^˫���<� a�,��1��;Zow罝!�z;Ymw�ԍ���j�s!<__�v���X���X������k{9Ugw=�8 �>U��C>Iugj쮟�0 ������ ��'�������>��uw��@�b��<m����<�{����x�U���)�.D��z����fuڜ������@��Y�����,P��w���2��f�N���9��@��\���%��@��� 5݅d�������s���1J�;�s�p�����r -�EEwy��]�CTt�3�p����wZ���#t�F?��O���~� �&���8R�Am���ŗ�dQ�,Jw�N�;J�]��i�C�d��Ɣl�}�����`�.�͢ݥ~Y�z�{%�;G�@��0^�V��NU��1W�N��*� �Xw�s����Uo�7Bl�T�O��͊�A�6��&��^�����埼q�����#�<���?���w�y��7^{�s�����NV��&��#�nK���_��_���w���g��_�1L���2ݭ� '9��o�#�mr��[W_=sN�c�S�&���9s�/m���h)�J��ȟ@���UnJ�+�|���ѮK}�Q�r� -3�U�'!�o�}�n��t��z��H���Y�;�y�𭁜�}�N�[��0��ɻґ?3�|9��ħ��H�k�d(����Co��z9���)X�����=w�7�ӻ��ސ������*nI'��������:^���JX���f�ד�;�[u�>~o�X����u�����Ƶ�.�����n��t59��ix��~$ʺ��W���i���ȝ�;C�h�u7��r19�˰5� ��C���u� 9�˰E_)�9C���u��p-9�ìY� ��P<���s���_��]a�}�m��������{�8���:���(}%9������9. -��-}%9}�I;+��[�c���y����e����G��1�������:�3��}㤲�_(y9}�9�U�D�%���R����r��ӗ\�_9HD^gh/��=R�2r�rcZ�sD�C٘��n�T�*rz�)"�k��qS�݅RW�ӗ�Ҡ��m���S���[e."���"�ы���/s9}�!5Z��e(�Z����5���SD�C�|P��n�k��K̨S��w��f^��n�o�%���S����������K��G��F�a�e��y���yk����CXw��c�(�n6� -r������P1ow7�?�r�)L�+Cż���l������ؽ��04w7�7�}r� )��C�<R������G���c��~��������郄0��~����#���Ïo���C��������w���0�C��R����w�釞>���ry����w��IN?ttƙÜ��L{w�{��~���A�y��[�i�n�6�=r�a��0��C�|S��ڐ��釜}�;�)?�7�?����~��;C�P��Y�����o�ӇKa�M�f������o��>����b������w�����.C�0����!�x�A��C�B0����Ao����%�`�U�[�9���O� b��Za����:x2d -m>`�U&��}����AS�"C��0���������Ä�� -�Dw��/��7��JJ����,~����؎�\e(U 6�;�9}��aS��ЩP�t���9}���ЩP�tw��Ur��S[s���R��n�Lы���$��R�X�n��Er��C/� �ۯ*���"�/:�:�&� -�Lwg^#��B�s� -�Lw�������D�� -�Lw����9<�"� -�Nw�o}��>| -�l}e0�ݕ�/��o=��ku2n1* ;�-�&JN�uH���d0�)$C�mny��^ �b}�>�d��ٖW�鷜������o�w��@����j5��M��)��d�A�c"P.�c��Kՙ1�IըesLL����{{{{�|���s����}^?|���圜�� -r��+�h��+�?���}�|%A�zD�S�"mw��pzE -�s�T������N�{�T1��H�S�"m��K���^�(�K7 s�T����y��KRx��n���z���=��*�K�5U+�v��ݯp����+�y��ueOe��N�I��5��?�������٫0�� -7�}�\uA���ħ��{Wՙ+u�� ���� -��CX�S��VW����E)\"r������� .�봅 -s��J���ܽ���\c�i#�>T������2�;_�"��^�2�;w�܅5uN�������NUě�����R+�/p����4�;�e��}uHG�2;�Q���0͝�PgT��x��T���5�6wpS�Q�`�3\g�YG��ˏ �����}���8k�,�����#uB^�z��8v�� �|H��n�^�[�Y(�c�<����B��p�[G�>�+�yn�:�;p�[G��R�@��vΨ�9�j� �t�К �'�}n���x^l}�;��N�6~������'���I�Pq�"a����9��>iؿ�^���w���8�����>rYď���HϏ���Te -W�j�L�Α����̓f%9Y'L�tK��#��̓�*Sx�G�DMg��<Ym��V7�1>hh�#Ltǖ�S�{���0Q���p����p��s�� -�Ƨ��Z>P�se�u�km�sH�Ï�����\k�/ISx�9a����p�ݸ�����ɂx��������� �U���D��ݙٸ�j=��a�;��ZV6n�Z�T1� Lt��S7R���&jy���͉��u�ea'�|����3�+ܫ8���Zy�B� -��<�e'��N�͛z� -��<�� N6j��:�Op��SF�)< l�r]�͝���k�i��v{n\�^�G�(s8Aب%��k�q�{U�p��QKn�W�=ܫ:���Zr���^�|[���F-��>��v'�A\ lԒ���͂���� .6j����rA��8���Zr�}F�v�� .6j����.H�MEn�Z������ .6j�햂{}rƜ:���Zr��.p�;���>QSn��g�c�.6j�����lwQ���F-��~#����F-��~ӹ]�F-��~��]�F-��~u�v/�c��OԔ�-�Zs�>6j�햸���:���Zr�%�2��G���F-��S��N�S�@ب%�[b���%u -��vK�2�[S����QKn��c����QKn��|n���QKn�D-�KCب%�[ -�XUGp��QKn��k����a���n�+p��u'��v˜�{-&�� lԒ�-���Z��38Aب%�[f�Qgp��QKn��a��b�:���Zr�e&�^��� lԒ�-��XRgp��QKn����bE�� |���n�1�ע���a���n��p��n6j��y �U�� �F-��2�p��n6j��Y�{U'p��QKn�L�U�� �F-��Rp�� � lԒ�-�z\���F-��Rp��R'���QKn���:���Zr���^s�-��Zr���^g� � lԒ�-�:�N�a���n)����^6j�햂{��:���Zr���^�� � lԒ�-�z@���F-��Rp����F-7��|�{�R'���QKn��k���Bب%�3��{=�N�X['���u�F-����m�o���9u� ��Yu� Ê�mu� ��Yua�����0�N� ���~\@n7,u�(��� L�> -.�Uua�q�K�> -n��:����>T�����N� , �uu��:܀:����~�nT�P'��쌺}�܀:�������A/� �@�K]>jf� , .u���x���#u��Q������ L �;��Q�pŢ:�0��n��G=WP̨3�kM]>�0\A�_�@�@X���� -�u�aa�����+��Ƥ���W0��� L ,u�8��C����z��wP,�#���u�8���:����n�����@�ꪺ{�rnwX�Ww��flwE�bp� DUWw�elwI�bp� D�����݃��#l *u�� �vG�)G�@P��æT�aA�A]=�c�s��#� �k��aW�]U�aA�QWO����!� �s��Q���R7[��+u�i�v�9FAL_����p��_�c`�����y�$g��#� �uu��v��9FXALo�������eu��V�Eu�p��� -b��n�R�v���x�tA�nMd`�Ĥ.6Z��;&20� -B�T]<�ق��g�AF�AH����f�v�is�0��~�.�Z��j�iM]<\�:��0����.�ܸ�54��I��0�����Qc�\C�}���BR�{�q��0! 28�BR��m\85�P��f�#u�p���"���w�u�a]W���i^�6�vћ��Q��+�C�a�v�yu�#�+�C�a�v��vة�u�{ � "u�$��a�kG-o�X=��c����>Qڳq��h�5�������\D븣� �zG];ju��M"!�7Ե��ElTW�a�S��n��"6|ABB@��Qϵ�"��@CH�:�X�N+�(�0��ԭ�w�!�s_�:hf�n�}�$���x�խ�&6�&�G�H�`K�����A�o�p�#�H�`K�猺u� u�a ��P�:f��7�uhMDXB<��A��'b�����t������_x K���i�*� �)��Wu�ŭG���c�*�"L!��ԥc���R�;��A���Oեc���W�q��ʃ�S笺t� -u�a -�\R�9��W�y�L�A`�)���3��L�b��8��Qw�C��*ŠS�決s�J�܅}t��-D�'u�������G* �#l!����!':_�.��>Via��L�9܅:����O|L�� �2���ol��^]�9b_�\k��c����0Q˻�l^�u���v~�� �6j����ܫ:���Z���91�� -��s�beA�!lԒ���|�+�k��SU�p��QKn��k�y��6j��6-��ý�~�|EA�!lԒ�m��y�{����* -�a���n)���/(Rx�OԔ�m������#z���F-�݆���p�}1QI��vK�����*H�a���nQ����C^� �C��Zr�O�� ��n>d(6j�햂{Up��Q���/�ި�+ -kRӡF��[�`� -ِb��b()v�����O�Fʑ���(M!���=w�}�y��q�=�e���̵�����\;�l�/�#G������\;��/�#G��vB��@6E\�v:>�s�j45^$K�"�� ��ѹv5zn�H�E\�� �kg�Y�-r@�(���]ҹv�ڶ�#S�"��=����5��"pѼ����ҹv7[5\$S�"�w�v�t���:���F�(�yw��su���&pѺ���?�s����������=����5�#pѸ�s�9�+j8��#_�"w���s:W���h�l8�h�]�+�|�p�8�h�� z@� -[>4Y$_�"�v�?��k�-�F�(�iw��'t����"#pѴ���7�k�-�F�(�ewg��kOۻ�ErF�(�ewo�Gt�=m/���QD���@��W�I�-r@�(�aw'=��\�-^$k�"v�U�3:���s�y#pѮ�[}�\{[oH����v����!�k�%�G�(�Yw�>�s�o�&\$s�"�u���)�k�+�"�#pѪ�'���\��������V����5w�R�H�E4����t�����#�"uw�s:�e�k�����6ݝ-{��u���E�G�(�Mw��@�t�$���QD��.���p7l -)����&�}�� :��C4��+pѢ�Ko�I� Sz�8�h�ݥ��4��H/RG ����й�r�"8�h����C�:d�W�"E pў����D�:h� �HE�������\��(���������a։E -A�(�9w/�E�j�E9E������\�sg�"� pњ��s�hf�EE4����ѹ�l�"� pј�C�s�i�%�X�"�r�/C_�s�ܢ(�"�rw1�M:��CMF,RGM����oҹFL5�/�D�"Zr�p��t�c�E/RG-����W�\c�z�GQE4�n�Œ�5f���E�B�(�!w�"ޥs��l�HQE���b^�s�ڢ8�"�q�4�m:��^/ ���f��.�m:������JB�(�w#��t��㽎|��"qw�>�k쀓�� pш��"ߧs��p}��"�pw7���k���'�@�(� w/cй��x+�H E4��Z� :W�-�D�(�wO�й���3�#pт�/�й���֘C�#pр��7��;������=s��uԨۣN��QD���9D�:n��q�rF�(�zw����:�"jww6����q�<�/G���ct�c�;�`�ET�����A��QD��>{��u��OG��������q�A:��3O��������r�I:Wb�q6?�"jvwk�Q:Wb�����8���]:W���j�8�����,�+5�.u:/�"�uw�9L�ڢx�"�u� u�Ε��;�Gպ{A��s%�����A�(�Vw�s��\����� -ET��<O�*٢�"�tw��s�W����Gu��[�Ε��|���T��]��U�Ŏ��?G5�;�Kй�#��P�F�(�Bw�Wt��¿E�8����SE:W�Ꭴ�/G���FQ��U1DSMO�"�sW��U2E��:~E����f� -8����G�:t��947 O�"�rw�*D�$�*� pQ���Bt��ABXו�@�(�*w�e��\e���"������ܽ�+E�%���b�8���݉���p���ji8����-e1:W�0E#pQ��3i5:W�4\J�"jqwq%-G�*�&�q�tE���sm9:W�8!l��B�(�w��Kt��yB�!�����:ܽ�.H�(�U}�ET�"��|�v j�#pQ��k��t��� -�W�(�w�5�\ f -afR������ݱ(J�j1T���E��?R:W��JD�(�tw۔�s�+\յC�(�pw�Fu�\�� -�V��8�(��S��t�V��3��6E��ٿP:W��BxiW�����ݝٕ�s��0y�"Jv�g��t�v���oY\��QD��6�M�j8[�յE���+��t��Åpd[^��QD��^�V�s5�.���uE���ܶ>���x�`8�(�ݙu:W���W�(�Hwg�Xw�s�0�� -�"Jt��[�wCX�h�"pQ��O��sM0��[�.G幻�� �k�!C8MӆA�(�8w�&�B�d�v���QDi�n�iC�f�S5��QDa�N��sM4g+�:�C�(�,w�S5�sM5h�t�� pQ����:ѹ&�4��(�"Jr�<]+:�t����b8�(���/ �ѹ&�5�����8�(��Y�nt�I� g���"8�(�ݻi�ѹ�7��� �"pQ��+��ѹ&�7�i��8�(���� �\S�F��C8�(�ݽ��\�O���" -pw�"}O:��#�e�ҵ�����=�hJ��1t[>m�8���]�� t�.S���M�("wwo���s�;�K��QD��.��ӹ:���ܭu'GY�{x�ՙ��k��8������ݝ���s�=� ��"p����W7:W��C�iu!p�����=�ӹz��G��? p�������\}�a�{��Ed��s:W��C8��GY����t�������"p�����p7���Ed��߽���̽'h��C�~���{�w��8���ݛ��:W��s�=��QD^�>;��t��|��'��QDV�y�t����=��G�;����t��|��W��QD>�����t��|���G��{r�=�Gйz/�)�^��"2q7�/�P���k��GY�;{�=��йz/���KW����]��s�\���l��T�(�����t@��@77ӷ8�pww�{�N�\��\=I�Q�(���M��:W� ��z�"\ݝyv���{��I� Ex�{ϱ��\��㛔��?wW�:���{�~~kEx���ʩ�0�\�X�$U#��'w|����{��\m��#p���G�(�\���$�^��w���"�s�^`/��=�"��{�C�c�s�^` ��"�{����X�\����GIݝ}������6�.p����_��b�s�^ �5��G���c�N�\��dݬ��QD*w7�A��@47�� -E�q��Je����#��9E$pwn�S��:W�Fa�#pa����u�\���B]Q�(��ݓ�����s�^`43m=��Sw�[V����{��-e5��Cw�xnW�:W�8Vt��"�ܽqeU9t���l�.�G6�ηMʦ���{����2G�n|Yx��� -Χ�"Grw��йz/ �Ό� p!vw�;m=/�\��q�ɝ8�P�;[���{%_��8,p�swz&+����Z.�F8��{����t���9��M�(B���{�\��`�ֈCG����7l��s�^����'�"Hw'?q�s���{;��D�.pA�;�����йz/`�Aķ��Q�Xwg7j�*��������zYi��0lI�B�ډ(EN���H(8�L���"T���\�����QҤ��X�6��ݨ�Q�^�-�>�q��<x>�iq��F]�o7�v�98�^�x2I��~6��m����!�j��j�ﻬ�Q�ۍgҘ�!��:�_U�����Q��۽(Z9Aܫu����]�6�Ҽ��iQ�' ��W��6ަ���C -uٿ�2����$��:��E��8Sب�s�u>������j��S�,��f�Ӯ�>&G�c�Hܫu� �'Q�}�0�-�ެ�>8��N"q��¥0�M�ǛW\]Fكe��hT��ZϗI<��}�`����������������������������������}0� �� -endstream endobj 15 0 obj <</BitsPerComponent 8/ColorSpace/DeviceCMYK/DecodeParms<</BitsPerComponent 4/Colors 4/Columns 700>>/Filter/FlateDecode/Height 700/Intent/RelativeColorimetric/Length 38085/Name/X/SMask 20 0 R/Subtype/Image/Type/XObject/Width 700>>stream -H����R[g��h:�� �@� �b����f�k��U�ꤝĘ�a=U��'e���b���I�$I�$I�$I�$I�$I�$I�$I�$I�$I�$I�$I�$I�$I�$I�$I�$I�$I�$I�$I�$I�$I�$I�$I�$I�$I�$I�$I�$I�$I�$I�$I�$I�$I�$I�$I�$I�$I�$I�$I�$I�$I�$I�$I������)�z����Sc��ؼ�����~�����,���g�>��!�7ӥ����Ƽ|ݧ�G��l��Ά�������~i^����N�Ɲ���䨴�~���z����`^��;��|�{��'�7齙.�������]n�o��m���}a�+���Z��oG�u�TW����ݧ�m��K��t����Y�q}�Ю_��lX��ۦ-�^���r�4g��Y~��;��y3}���{3]����p�[4n�6�KwvZ:����`�-<=)��w�d0��z����ޛ�����o��>)�q/߱S���������I�&`���f���9g���d0�f�R]�f����[ezR:����I���f������Cs:���m�w���3=.���:���aK��f������ܸ�?v���r�4����.vKc�/�����𮕾x齙.�����ϛ�๚��θ����w}XZ��{y`��&H��t�����Oo�vzR:�[�_����ѽ�k(�7ӥ�����Kc2�W��������ҮG��x�L�|_zo�K�?�Ǯ��[�a�.v���3�F=(��t�Y~����Wzo�K�?��Yoј �����ӷ����T��Ѣ;ޛ7ӷ�_��f����6�<�k-w��~��H�^iJ����w��f���öy۸�R]��o��6=.��>\zo�K�?l��ן�ˍ{���V�r�^۾�!�{3]��aS]߷g��I�������'�7ӥ�6���]�vT��;`�͆��8�k��M�ޛ�������Y��˽�L��f�}/m_�Q齙.����f×�j�ƅ�2{��<�}���(�7ӥ�����}��*}l���ҪG���;`���f������}j�����f�ҹ:�o��U�ޛ������ع>,~����+�z�\��U�ޛ������ܨG���N��������η*�Nzo�K�?��������t�w��������'�_ï�n��!i:�\]����~�z����; %�7ӥ�f�?��w�w�������Z�;ࣥ�f����GY����*}�79*���c'}|���L�~�ٮO�ׇ���X=��Q>W�;�gK��t����e2�WW����X;�1�ؽl���L�~xo���j�����z����;�=��f����{��v.�nnG��ec��f���Ï�G���N�`3գE7}����L�~���v� -�l�z���em��f�����5�ع@�l��{Y;齙.���WՃ�j����T�^�^�Fzo�K�?|���]��V�t��J��ߓޛ������Csz\��;��k�4&�����J��t���?b��h�+��������I��t����n��5=)���"~|�ޛ���K��J����f���6VBzo�K�?���讕��g�G_���n齙.��l�ݧ��~i����(;�q��};~[)�7ӥߟ�3�*}@�䨴�7�}�{3]����������Q�o`{��f����������n��Ut��o`��f�����f�R�o�Xu���J��fK��t��g3MN�q/�:�G�U�6Szo�K�?�gz\�������O��l���L�~6G=z��7�l�Y�t�7�9�{3]����{O�˽Ҍ��������X齙.�����h�M�����A��7���{3]��YO㽧��~i����&7��N��Szo�K�?�-����U}Zl^����L�~��x��9�����4&�s���,�7ӥߟ�P��t�7���f����/I��t��g�M�K;}�n2�W�Xm齙.�����鼓���_?�m�u����?+ m�tI����4��k�Є��41D�8N�8���i�Ip�8� �#BH�!q����m�I(u��͏zSF�%i��~?��%MS;}����K��tg��lm�ޛt��+�:��$I�h"���W�x��7���W���=w�$e\g�v��t��&��J��l��(I9�=*t��7���o~�V�5JtI��[8���.�n -���4s�Bw�$ V���b�{�}q����O� -���Azoҡ�/F�JtI��W�t �7����p���$%�Z����&���$�[�;H�ҡ�����ޛt��k8:��$I�2?yO}�D����{�} ֥�;������Z��Y�;h��I���g�ً���lX�C�����&���$��{K��H�f��zoҡﯽ�i� -�A��Q)�=G�M:����N�$Iٷ��Et�zoҡﯽ�:�R�;H��;{�Jw�ޠ�&��z|��۾�$I���A��ޛt����$��!I�@u!�;���{�}�^2*tIR1��Dw���{�}��Ia~��?D�M:���h.M���O�=$I��n�"�� �7����#�����$ݓ�]���s�ޤC�_;�n�"��$I�[�C���&��z����*�A���tg��Q@�M:����$�[�;H���N3T���7����֖��֕$e��7��I���6ם U��$I�b��A���&�����"��$I�2���zoҡ�OkO�2�A�$��7�������Dw�$i�\�\�;�c�ޤC�_s�J�r��*�A���?(:��$)�N�����{�}��s�J���5�R�;�7���/��t�$iH�fnV�EE�M:��˭+I*��̪�@�M:��%IR�tfoW�EC�M:��g"$I�-��Q�; �7���/�V=���$��b�FDw( -zoҡ�_�)��$IZj�ܼC@�M:���`ѭ+IҖڍ7��{�}��k�u%Iz�֩���yF�M:���=|v%Iڡ���!��I��^%��$Iʒ�I�C^�{�}�<JΆ*�A��,Z8�w�#zoҡ�7�8���$eY�T(��ޛt���I��u%I��F��yB�M:���3�Z�;H��'ݙP�;��7���σ�37� *I��t���!�I�����WKtI��,�תt����&���$I�n�"�C��{�}�,���;H�T�S�Jt����&��Yժ�9I�� �ޤC�?��gB�� IR�O���ޛt��gM����$Y{:��YC�M:���$9�V�;H��0���!K�I��V,���[R��I�u7��{�}I���j7{�!�I����Dw�$I��4��wH;zoҡ�vI3��Q�$e�7���O�$�[�;H����4BDwH3zoҡ�V�f��F��Y�C��V�ޤC�_�$i�tfV+t�4��&��i�p�� I�v��Z�;��7���O�N3��P��,�HA����&��i��wjtI�����P�;� �7���O����%��$I�;�x�FwHzoҡ�/I�4(��2�! �I��\>|$IRn�{�}Z����$ip:��h�ޤCߟ�~�F�?��$�rjt�7����$I�֩�2݁B�M:��)�F���$I�@�M:�� I|�Fw�$I��i���@��&���6?u�Dw�$I�$�[�;�7����$I���[%��0�{�}�aJΆ*�A����P�ɹp�ʋa���7�^�����������Zz��G[_�������o<��A��~x����/��ݱw��v���?����?��~9�|:���I��D -:�7����v�����T(�����>���??��W~9��s��_=��ҙ�/���9|���r�����h�_Y&%�D�'���\�?�}O�?0r������3#_�>_���[}륟}n����v�?�����na�Ew6T���.R��R�\8x��?�_z�W���?7����G�����t��7�V���w6���;�� ����?������;�|����z{�/�W_�3�x2��gUŴظ��a�4���0t��e��t��0��{��o<���g�/�����T�,n��%�IC6>��(�d�n��l����w��� ��g������+���O�2�<Ky2��A�C�Вx�JwP�uτʵ�����m��{�����z��ol���w������n|w�����s�`���;�:�ߙ�������9W�u��Bw��{����tir�|�Άꕋ�x��_z�6y�ԧ��w�a�{�d=[m���v�������}�F^k�^��ñ�/�����峷�t�Aڳ<���?H�z(���c��b����<ϙK��qb�$�ې�A�,��NH(����"��%&$6��m�*u+U�V�Jժ<�݇�/ժ�S�����>�T�K��Vj���Va�.�Wǚv�$��g���>�d��=�e����L���Ѓ��| ����}w�Y��.�4x Xk��y�{���μ��'��}3C��vk������j�9x����h��_)3��5�Э�b���K�w��@w�n��IqFD��>��3[�q��y��M�➫���-�ک�+P�;�n�*e��E��%�-o���c�����G���q٫���A�U`�r�u����8w��A���fv>S�c���u - t�+a:Be�Pg�rV֦�����;$�(k��VA���r��ɐ/&�\�,<��OPT$�T��_n3��5�P�9����k�<M|m��"yt#7S�˟� xB��]Յԃf���:�L%�1�[;kTD��_N�#�e�Pg9�|�paߴ���z�s�(�+�[g,� 0�-V��~Y=אz�h�Y�Bꟸ���rj�:�rjg}�t��i:Bq�EKr���Ƒw|M|-�]��s&� �Z�֣��͟{5�4�#��k��?( -�m�)(��/��� :T|g����<�| X�u^⎋ �R�>���=߅�lHg�\��6B�#T|���@�P.m+JA��9?��c@�v"L�C�� t��NT�(J��BAħ�;(�gC�����W��~#(]I���F�j�� ���e:�rX٪���é8uCǀ���п��b�ev����-�Y%$_�y��Xhv"�j�/�\���� -�H�����0��{*��!��ae*�9@��Nw?Q�c@�t��c<D6rg��?�D�z1��ǻ�P���}� �AǰYKM��Y��{Z^o��{���ۣ����R74��s/h����]R��}�d���{*��'2x���D:��1�MU�P����֜�Љl�η[YY�֟�N\.�D�g���qyq��&�~��e:Lq�u0��ߨ�G�>wh�]H=h��s��-�q=���� �K�^�v_�'8>= M�����b9��T��a��Yc"���:�n�P1�vRr��uuZ w��~�`3��03��"�0��3�?��v������a��s(�Ym/2����F���*���G G;κ�g��-&�{�{A�y�e�|�5�zG��t;��f�~��af'U�c؈v֖�@�C� - -���?4G�+�3�k��/��<C��`}��̨���� �N��|��Z;��{ -�x����A�Kp��^��t(��!꺚|��7��3�w�b;.߁A�?��`���X1??��:�C �>��:�n��ke�J|���qf/�I��A�zI������z�#�z��h����>�������hJV �����r��溈47�It_D�R��h��_���2�U����M���A$QI%5Rv���tD��$^h�1�*t�@��R�ǀ�87�q�O�Ϋ��M�A�ރ�Uq�ҕ���}E��l���)fv>S�c@a|k�7Ml��=���WAHX/"�"ɒ&]���(��.J�c(E�z�:�(Z��G?h���~�Vp�Esb�LťK�MSq���G��1�%`�t��2�x�Aǀ�oz�^n�e���w]��@b�ADQ���z&��T��NF)�}�����?w>ͻ.���^A��ˮ�{��=W��8u��R�V';>�=�X:�����:�^'~֨��X�l�b��W���wJ��8�^�)��ʿc~n�=v�w�/�~S����[U� -�*B�4��/���3�P{�~�F��.�uQh�3֚��~���wDdV;�:QK��z�#�N�!�ފ��t�S:�bB�4��/f��*�1��9��(X�rg� s�P���s���~f����=�}���C�sW����&��%;s�,2��ֳΞ�3��5`=ۅ>���1�}��۵����Vb("`It�~"�ǀ��\��AC�+:E�c);�u�[��� )��������31�vˍ�E��6~���ۿu�y��!Zw{?��~�zo��( -&;�/�";�w���W����Ϟ�i���ﵾq䫵���(�m1��֗���USV����{�q*.]�z�wp�"3�t�.p��_�k��n��4��`cMk�<��S��_��������6�ԁc�ԉ��~[|�6L��G��m/�g�N<��������}ٻ+�]�5��(��:a�]�7Ȓ&e?@��@�[�r�:�Vӱe7t�=ο����iw�,���N�s�8�����Pc�4�7�Όދ�zd{�t�QJ�~7�ױ�<;^���w�Bu�^`����+ِ�?n��=j�7T���*P9t����T����3{�~8>,C�����X��|ֱ9���ź�3��g:)��9��({��{��7Som�u�X��^������j��H���6A��N�K������j�#�1�������f�y�t|�e����}������E5�<�����R�ΩDǒ�}�`�+�e�sӉ�����[�I�Pt��ʛ��*tf�jA��ov<Zy�&��˒}�K�<��=Q��a3��5,M[ -.�N��[��j�>��h␛ךJH���gN؉Gcd"Le���%���:����e:���L������_6W�O��+���ݮ���tR�[��]l��gw�������]6^bL\����� ��IӴ4RB@��x_66���})�P���*oU�*�6/�ڠ�}��Tj�T Uj�T��@�͝�.��C˽gl����+������3�Vei�vn�}���<�������S��t ���x�-T��:�Ђ��3��[�Ş�js���?�Xe=�:���kq,��,�)�l'J���ۯ��n>���E���YNť\��$ -�)�}��<���gL5~�A��t�����4���ҏtB�{��B}N�{�Vn<�)�lWJ��d:�{��W+v�##H)����D�Rl�:�й�����$����by�c�y%#Q���w����A���8�qvw�������ͦC�*�����:�Ђ�:_��"���?�|��r�tB��l�}��篽�5��4�#� k��Qub�z��6�g�Mt���L���P�{~��K����{���-��D>��8.�D]{���9�����_�=Ddc���$��t��M�.�$>���w��N�CdkwpAz?�]Q�����E��x�Y�����|�w��z��C����^�gx6����!��{��&J��C�Ql�:���q��S)��"���O��PT �1{Z��{��&�y����k�e��C�g��_ҭ�U�N�y��ި6�ۋu_o�N��;d�"-�6���q�����>'�-��->�>m�ʾv����x�w�����:�9�a�6�)�&�5��k b{�x�b�?wB{��c��������TP��o�t�q�{�fh����+��X�c����σ��F��@��h�y���W���5�M�u�� 2�_�z=�<�M���7d�!5�6�瓙{*��#��{o����ifƬ�����T[�gx��߮��Aq,���e��5��.YkW�?d^g�~��G�����Ԑ�P����7D� �8�4��|.w9k}�K����{�cB^�2?��,�g���4t7��חk��61ɬM��?�D���/S�g� �0�J�M%����N�!¸���(˕N��/^������LaP��ȯ��~!"��}�זmvX�C�{Y���j�U:�}�~!�-|���L���P����C���B��\�}�Ǽ���9�2������_���r5�z �:!f��]�x]�벫�V��z��~!b�JS��5�܆�>sd�0��A Ѵr��u\�*����Ji�n -�+A�(6Sr�˙���!Z�5����t��M �+DӍ+R�Wx�m.�^����a����Cy3Sn��d�������y�v�R��� -Ax���S�Py��u�r]���P�'�,�1�"���]����x>_��t���C!���~����R���� A�0�N��[�3�ʚy�|}(B�� �x�S�ZR�kc��yf�:W����X e�IV/�Hֹ;�Q���*�� �c����z����"s�e�ne���Qh�1[���HJpm,Y>Og(��nQNw��,�S��?V����~�nC�<���G�ؗ�#+�H�f��QӸX=۟�s�'�}B67`!��dx<�]�Yw�2�A�ɚ3K�� �c��ͼG��=B��,���!��u�ͷD�V�H�}� �k����H��t��%�,���D�����!�#�xo`�v�Ⱦ�nh�<��W_9�BԳjs��� �Dq9�H��~�Ȧ���d"Mf����3e���Om�f��V��.� ������]�'�zVm-������;1h1L�V���H��$����C:G��}�",C�~�&:X���^|�G) ޏC�� ��c&G��t��b�U{2&@̻L���} -��e6O5�k���F��D>Gȋg*M��x��^�����4�a�AVs��K#�s�.F�Z���ҍJ2U�JK2�t�|��v߹ڹWo宴S/�^KCK��A���Y��M3�oB��� }���& -�~���H"��lX����0?�i��r��d?��7��A�'���z�;��pd���6_����]o�K��d��}���IUs����?��j����l����\K���&J��a�� �3���Y��s�=[������A��F�o.��gw�zQm��� e�s̳x��e�"I=����N�n��Y9�H5��E��R�kc��/���r��G��f~���${��C�F?�B{� u����ݝjXU�s�z5�� �����אׯ��;���_��������U�H�7_�we���#b8��/"껥�����l��D�=�W�y��!7��둶^a5A�g��w���g�ϯL:!&VU�>�U���Q.�0%����������ޜ���=���F����9#b8�H������X�|�LQ'tn��T�:[�fv*�p�{Q�� �����c�� �v�ͺ^��j^�b�oT#���o،��}i�Gi��i:K��t�S�-Cֵ}�e�� B�7"���GB�,�6�O'3�T� bX��V�!���4��V�d���,�{�p���>�Z�7�!֙��9{��K��k���G>�Zk���V/�z>ĺ�E�-|��`ᵉDxz�@�����j9���D����%��mԀ�A�3�y��N5��A�lV�}������:!k�O�v�r���e���=�k7"H�W���y�=�~�&�b�WV~�*�sE6����]Use��P.�������A�;�ף|.�|^E��S���4�ה�r���rm+9���t�.��|�Ŷ�|��8���ZN,��Erp�lH�M��&���#'N�W�e[ -�-,�Bi���e��%Ч�tY�>���K`a钖R -��\��4#3Ή"'�tf�����ɖt�����6�'}M��E�z������oJ���Ä�j��� �j ���ņ� -t� |���B�Y�J�9[-�HS�{O@d��L�Ⲕ{��*���x�C�ȹ� �&�s��"t��E����r�X�����4����R-����Ù��<Z3�{:S����=A���T� �f{��6{�u4+9ו#�^E� �w��f������4���G��Ĵ�מ�/���Y^ߚ����:O��' -�/��ٯ�ʡ=�e{Vo�1h� -���FZ�m�'�Q�V�4����z�A�,���r���>>,����VgY��9J���[rC{� ;�x�QE��)�'P�K�7�~�:��/�tl*��}J�h/�Lu���>�ꈬ�������U�].�u���d�˸��Y(]o%�О �N�z�����v�������^����u��<_�}Ey��|[��d{ �0 (�|���Y�5&�0,��Mv~$�t"h?y��/��=ɞM�D�=�x#'�D�{.zK�(����L��z��~�]b�5�^�-TH�5�� -]h/-u��"��k�b��j�V�U � � $�x�I'ۣP��o�ny`���RQT�ܼ��9\��zﺡk�äJ�z]�8 $�|������6Ųq�����Ͷ���~ �Ρ=�V�-/Ѭ�l�-��h8VB�A��Y :ﲱ��~>t- �8�]��:B�Ӏ���1u�� b?�s����f�]���A�9��k��6���={C -����������j���H�[2t- ��[��H�Hzxx9HtYĘ���8�Dg^��ٲ�I\�~ �Ω�h�v��̻fy�E��A����BC�#�c����J ��ח�Jb��:������1/|= |�TKd_Q���B%:���M�2��<c��n�� I4_��:W5�w_-KΞP-��W]G*f�y�����cU�uy�@��B��e���� -!6���/���@�����M���#�Go�`���o��fɹLl�K�~[]?b�o��^�H �l��>�z �r9��RZ'��B�uz���s���knh?y�L�����U��(��~\�A���{��t�c��m������*b����`�ֿq| �8��y��^D�g��h�ۋ�� d�m8��WE���=:NW��=�3'\�z�h5yۤ�z@8�{���A -�@�ƻ�ʇY?o �&�0,k�Et/g��3�A^dn�c�a�KfY�qz9�*��/E? @e\�J=� -�X�QV�]�����'��^c��(iL� D?�(���x�s�A^d�Gr�k�9�{��� ���r�=�n�%^Y��^��S)]� ��f[���Y��}�P�X����2h?~�[EL��^XqT�x7�'f��G�q��8l���ET?��kz��u�Bh?���d51�4������>����U-Gn�C ,�9��d��������V�2���Ȫw���f���^c�?��B<rG����=L�k��V�Y>f�����7c�ڐ��%�G&{�<���rh/�½0 �L};�]_;�/s�wݴF��բ(B듎���6��2��b�Gr�HI|�+��������#'�ޔ���Z�O*�˗ -r��J�fiH�G���g�;�}�;��T -V�C{:䑶��gˮE姹x1D��=@�`4Nw�+b���mS����fO��s�5��o3���.��7o%��|��Q>�_�v=�d�[�ipe��ņ� -��#|��CYr�ds~e*�7��@��k'���O㽋��͇�����,HdS�-Xfի>��Jr~��f�P��|�Иkn�cu���R#�/&���V����dM�zjE�G�}�4�B���z����$u���Ѧ�Vh/��v̻*Sߦ� ��לw:?(��M�;b����@�0�}�o;H���/�O�Fh���2Y���6�f��#�so�kh��inΝ�{߽2������� ���)�G��t�/��~���_斖{U@�3��#�^��%R�1��P̀ �ĸ-N -�ĸ;0aݿ�E{�r�3��� D�G>W��̔�Ww% -���T��͡�ʭڬ����}�}���6��)u�J�X�w�я��-$�gQ�畮`��y��y%5�s�ݏˡ痊�f�(v�2�tD=��^��,M���/c�2z�^�S&��Vq?��B5&��,t8%��qH��4�r�z����5n��z����L������_�m�`���l�3��@�nzSNdߧ���LVә�.�bIkm��(��oF:N�]�M���zM��[�����*��kH.�_�w?�h/�����g�`f^#��z�N��[W�4�'=�"�#��������2w�L��L�*����<�����T*/tIG[����?b�D�^��=��?l1ۭ���ٴ�L���V7u�"�i3?�:�\�^ |�e�;a�iPe�٘~����!ֲ<�s="��7�ì�͙k'�S�7�mu{s,�"���*��j�>�l�Ji���z�x�Wu���[\&�Kb��z�"ˉgܑ�㎥���@�w:���K��%R����������XՃ��F��4���1&�5G�=�<���=h/�̈~����xc=�y���EѽvhO���4��n<���f���-uz�C�h8VB���c�l�#�J�X���3�| k����=�(���O,����Y�FP�~]j|��c0z���%��-��I���m�+�j.�/�X���_~1m�g?���6����?@����� ��mY��jsѪ��8B���b�zS)Ҵ���N�4M�E�����H�&u���&uZ/v�HU�jM�˾�u���c��{�c����9��@N��9�<���'�dW�|�h�Z�����CY��&a��W�,��S�wNL-�����x��v�����Ml��/z��ß>�'c�S�l/���a���@w��w7>�R��I_t^PGkL��y�}��ٰgKlO>�A37�^� ������|� l� 9Y>�~�Yf�:̣ҕ��ql/��<ܳ�ƿ���.��̎���)K*W�o%rq�2�߈84 15������� �p>��}'�cg[;�m���5���մ�����] -������a���[��Ks����-�'�r�_n�����;�KY>u��IYd�&Ozf�>������\���;95��QzKX�ˣag�ݫ�w��.}����g}��vMU�s#=���\�S�˭�|���6�����������cĥ(�6���7��fڙm'磆S�ī�;���.W���|��,F���̭$��}��z5�V����ݪυQF��r��~�=���u�+gG���;�}�Brav���7�|߉�!'B�)���'���_7cw��P��+�����N�;vl�U� ����ƕ���$v>V7�ʹ�@�����nmq��?�J�NIU�O3�;v��� ����nm��3[�ď[�Źh�*<$c94�p�d�t-�F��2s]L�5��=+���|�Q߭P��4�{��b��/���P߭=�����~���t �?�\v��p���k���K -;7�J.�쩖(�]B(ӱ��L�U�O��aϕ��^��*����J�x$��3�⽊�L�;�Q�˭|w�Y�|7Ӱ3Tk�����6{��NNI��?!'SG_ @>0z.�]�\��\O̺�s��[�� f¿E�v�w)k�ű�TK�r��o�9G}�Ƹ��yE߷��)�*�h:�nÞ'!��?�cg���X�&oǓ�H�ӂ��� -[�T�w�S� �U�)�,e�Nb�V�%����.��|�9*9OX8%U%��ޣ��֠�[S\ɼo���7{�n�F'��������s�.�c��vx�uP�%83m�V�yB�)�*�h�����$�2u������P�0�(�{~t���Tu��|�v���ȡi�S@2��^|>p��NWa;�gH[:�|�y�fr f�����]b����������Bsf�,"ޝ�:�z��-la��튴)�ѻa��U+�$�S�%���nw��pJ�m}�m�ϑ���fe����m�����C���~�)��3�Hb���K`2�{o۹���Ο��]� �P߭^��둞h\)�/�|K��4U-l�<���^��|�F -;���]��N�nw��pJ�����a�wk������x4��C� -^�0�Mz�Ⳅ��X�ݲ��R�v�<V�w �NI��?SQ�⡾[}�O~�ko�XS��>��/�z�v����q���0����Q�\V�w tbeׅ�SR�χ����j��Tc��t��}�j�?�F0K>k���1�����\ɼ��g5@}��f�{C+�.,����73�;v��8P߭�'>i64�����.�3&=z���p�\�N^�b���P�%���f�r���)��y3׳Y�;�z��[�M~�C[|�=���>��u��������m�˭�?��6�Z�6ӱ����)��y3��4��8Pߵ>���H�?X�:�+�(w�cf�F�ct�v��w+�+ �[ߋ!+ٛI��Z껄���*�O>�w�͵��ۖ*�=�������F�whږ58f�qK�+�� ��R����u��.%�� ��_+B}�������lMX8%�l9��k]滙vl�U量3��G�e�UK;*d�����y��J�ϯ�קx\��Zi?���R���X]F��7)���c��jP�%d`�g�a�&,���̗��;��<��Z��:��x�3�-?&�̺'\i7�4�(�R�l���s�0�r������[�ձ�-�#롕���������X`Mp�VG?/���ua췡���3�f���5�,tb��)��r�\F� @�=�Jǒ����β���K��|�Q�5��g�5���~��Z���|�~n��-3����Y,�Ш�O�=sp����?��B{����)f_>��_�:�����V�y����[EF������=uSQ�L[껄̴3�ٚ�pJ*3_f;��=7��c%{3��t��b����@Jy�螋��w�����(��Ŵ��F��QS_to��u�vt�e������SCg�sm���#,���ԛ�l<��Z���O��M��w�W4U-��-?n�~k�+O���/O���d*��_�����g�_nJ��{��s+H�n�k���_�=X�w Y�NܱNI�=BN��Z������dOqP|f�3ZMj�S�ɨ���i���4l+e��ٖ'�9��> }�����@�����^��?�a;���C}�����>7|n -��2�d:�n�m@��w�Ù�O5��m�Vؚ]Wi� -6�芫����=}P1߹�W�!_��}�������ޡ�Y��껄,\�e�NIe��l��{^.�w��R����ޤ�YA���� :�3���`���Y�c�#�l� -�������}UnM���<.�rvd����0��K�d�踰pJ*#O�ӌ�n�C}W~�w[��P�/Y{�^�k�h>���� nV}����3#?��c[|��~2/�����O�Ha{)+�w Y��NIe���vfÞ��]�������a��ȡvQ�U*������ks�e�c̶:v��G�,����wέ�9�x%����/�ض�3 -_�w(R�(Q/�Q�l���~��Z˒%Q�-����� ����-Q~5�,&�� R�-�E�^u�Rd�E�P ��M������8~��ա/�i����K���������g��t'W$����~���#軀;Ɔ����<A������$��p�Jή���S���:?r-J�5��O�^J�M�g��;*9gJ�]�8���#軀�;�<36�LE� ` �._��k�4�|�q�IZ�ǩ� XKX�N���2L��:�wj��H�֒g�/g���Aw��:����.��춛�����d*�ө�^��=�<�NH�d߱��+����Tk8n��S+�w\Y�}�M����yvR��{�W��9��Q{� �]��|��G���p2�ۏ3;��9z�wy�r��Nw���P X�wv*�=J��n���~�W�~��E��'�P*�ǽ���+���Ծq}p"�-��gƆ���~��H�+�}�!��;����h����t/l��X�:�g&8�%}Ծ�H������y�����YF���|��Ԟq}pb�]z�ό 'S��X��?�:'@�.?fr�(u&N9���U=5֙��7S���ri�x�>Q��\4�� z�8��X����d*�3I9r�wyQ�MMu E_8�������{���C-���_ۨ�z�Y=x��'�-�\��!{%��¥����軀;Ɔ����<A����/��'\��SzFb���2���W����Po���-���Ν�|Z -j��A�ܘJ�/�ll8��:��]>���H���(�p�/D���OX���Ĩ��h\����T��[�Zz?&�����]���;n���Ɔ���^L�n{��<@����O����C)���Va��(�O���;�Eù�]���Jm�wJA�%軀����Y06�L��b����:��]���Hm ֲ�n�=5�����'1j�6:v��ݩ~���ړ���{�>Q����>�N�rz��|��A���d߱��C�K.�7X�j�:���Gqj�6K�e�'�����Js��[�t���f}pc.%���Ɔ���^��`�K�ڑ��C�`��,�k:�C"l]�s�ڣ�Fa�Vk2�����w -�.�R��ĕ�GT���N�rz��]��y������������,4Ֆ_�ҚNk�����j6+����꼡���J�B�uլ���zU����]�c��TN/��y���Ҳ�=��T�P�"皺O�g'��|-N��ffa�j���dF�w�ګ���E�軀;Ɔ����<Aߥe��1���s����9�U�zbY��.��S��w6��H壠zg���젠���]����ҽ��d*�,�K�w���S�]%�,Ԛz��P�U������Ŭ�N���Q�H�SL͆[�@������R���}p���/m}ol8�J�0��G���.+�tjߝ}Su�<B"l���L� (�����7�̄s.������FjoL��8�����{c��Tڇ���>�\��w�� -���J&�P�;5�=aS�*35�n�/ɜ����3���Ƭs���]軀#����d*����;�:��]�|��T�P���7T��L�� �0t���P�ٔ���z�cr.�k�s������;��1 �.���Y�vƆ������#��. g�ߊ��ܹ�㺵����j_��)�|�V�oxl����� -j_L��82��^}ol8�J���#�Թ�>��Ұ��ǘ�:�z��������i��zˋʩ��!a���*��O:�TC��Vk��l���軀#Ӊ��Ɔ���ۥM����y��3�Z��:���ڞk9�a�����t@�[��G2��t�U:;q%E�)�ww� 'Sifۥ�:��]�>��&�����A�������=O����w��^��K��43���cl8���E�>��]�������0��י����;d���h��<�i)�������ڴ"[��J�l���軀;Ɔ����<A�5����hc]�q�������N jO��#?��w����Yjy�Ԟ��}����W]� 'S���X��k��WR~!��[ضq�U�����6Z����'�۱���'�-�J���Ӊ���=1�.��\�M��N����}�,}���)��?�>��<S�DH�z�wU��6�'�}pe>}[����d��ɻ�y�^��#��"�5�;[�Y���0N� x6^�'C����6�P h]>�� �����3��3al8�Jy0���K����9�Ư��m��g����Q���Z�]�G� xv~��t��rJ�����:A�G�A�\Y�<@ߵ���\ח�e�����[q�}U�4����(������|X��3�%Xkt�ܳ��QGv�����6軀+g3}�Z��ݷ7�ox:�w�1�7pvS�;�Q���k�_6P��3��ۛ�32KO�-���1k�K��=�&軀+�i)���p2�� �����@�5C��H�{M+�sҦ�<_^x��r��B��uy�f�ڏj����ϚƆ��������]3����Upzm�s����kQj?��0�E�/��y�$շ/N^���2�.��l������d*��b��S�x��k���k��PI���ZK�e���|�NHO��/ـ�Wj�:� =_軀+�w@]� 'S)��軠�]3L�.�Rx���C�6��:�:�v��r7%��@��G�E5A�l��_� 'S)�K�ׁ2�w͐� -*�պz�١�5Q{�Ca������|9՝�wJA�G�@��16�L�<@�n�w x���ގ�d�*�l���*����T��YLF��ΘҖ`�د��6�3W�eY���H�d9V��21���4U%7��4KS� Rp��qb9K����F`7�b��f��-Ka0���1��ґ�]M���r��츲�t[��<����������z�;[�wA:��::����p�Ȃ�k�l� -��_Ș"��3��b~�>ݲ�3.�n��u�>/��>ȝ�-� �r -��X��@�]�Vr�1���|��n_s�˝�u~���T�U|N��kC?o����]��l9��f1�{�Y�w�[�C�0c��y���`/w`�b��ǎ��k��:�,lA��ȖS�p�2���we�$w�z�r�����V�*%ێ�?�=�����]��l9�w� ��}�e�u^�l3c��y:�������&���� ݱ�]3����ƞ�-� �X�=/�r -�;���w�;�s��|W�{Mޔ�'������{6=�R�V��Z㍎���Y��M���d�)t���dBߵo��˫{�ɘ���iOt���4�y,_��7b��.G�5��N����]�}}JCߵ�xs{g�z2�]�����US�[��j�ag�[����}$C�E߅��w�H*O[C�-[3�t] pg4f���ܿ�����Jf=� � �%�]�]( }�!�k��ز5������ُ�����sVN~�����]�����>��:�������5ӡ�u�j�l����~�,��҉����9�i}��Н&�,l@��.z���Xc��}�nT�ٲ5s=�~�;�1߿Qýoz�Gn����]�l*�VN��B�BÚ��@�]�f�TU��ǖ����;1�,�ƍc*��)�Y�m�;�wA���:���<c�P��]���wΌ�rw�ww��GԞp ȶkf��G~�ĝ� � Y�u}}J@ߵk�[�n��q�������ww��)���cٳ~��;�wA2�]�]( }�.�w}�˚����]��]b����ì���-��&ŝ� � ���\H���ȃ�k�l���ߏ;���G��Y���GԞ@��l�w�쩷�w-拾�L���w������ڥ��O�Ý�B���]��1�t]�q{�0�.��e\j�w��˭�w�ȃ�k���@���x&{+ʝИ��W���Oa�}c��M�Y��]:|�G��B��@&�]�����Hx3b��͙S�_���h,e>�p���y�Y��]jYuɖS�p�Ȅ�k���IE�W���с�a�,��B�z�[j�W��Gw6��d���P���|�,�hr�26g�|�R%w@c&{+J�k�ӯ͟7��g�����]�}}JCߵ�xs;_���ރ�y�s������I�N��?ׅ���n����]��bꞗl9���L���8��rek�5]��������vN�uߥܯ⩯�s��� -�,l@���W=d�)t���dBߵ丹����K��>�.T��t(?w`�@ˠ�j��9��j�7:� �,l@���w�w�4�]�^�|w_a�^�ѓ*s}���̧u�Y�]�Sʛ�I���Dž��{��Ý�-� �X����"� ��}׆~�X�})�6�Zϵ�{Q�,������>QN��8���g�Y�ґ-���L4(�=�,��-��>X�})�.�"��,Vqgv����~��y����E���}�#[N��3�<��ۡ�ڷ�Y����9�>��ל�rgv��}?D�[�����;M�Y�ґ-�����B1�]�f;U��*Ē��-�:7�7��t(?w`O6}��wM�k��Z��A�,lA��ȖS���-���}��ѦV������b��w`ǵ��R����-���LMp�3ߧ��y�ґ-����[U9�=�,�4Nu�9�-��с�Om������Ĺ_z�"1'H���}Ċ=z�-���\iSx?`�]��/�sd[|�G�]w`Ƿ��p�X��};q�����&�]�j<��I��BGg0���l��Kci�w)�|���ghO��ؿ^Ý쬉���;�Vls��=;wb��/�]���g��'�r -�A�m���>@�]��<�W�d�X��cm���j���{}��Ǿ[z��i���&�]�j�Y��I��BGgp�u }�Aߥ1�Pem )�dM�w�s���^�<`g�ڳX�ewn{̙�7�Jf=ʝ�M� ��a��O��::�ɖU��>@�]:/�[A�9�nM�~g)�I�;�S��m�v8^��t� -ǜ��xșlT�LlB������N�-���\L��r�Ȃ�Kg��?m�c�μ�с�a�<`g��5^|�\�5�u��;��wA�|�C�]�Q�o\�p�Ȃ�K����8g�-����T�r�3��.�>�ޭץ�͞M��8w���T�֍r�$[N��38�`��}�N�E�-����M��9��,��:ײ�E�3��fq`�6�7��Mߥ�/=U�Jg���I�LlC�������l9��=�L軴^�90ߛ���~k���c��������;�3|�>Q��m )g7��.Hu1��W?ɖS�p�Ȅ�Kk���)ʼ�9�>��g�w�g�w�wbܙ��R� -�W'���-C=��p�� w&�wA���}�~�-��1y�'���@ߥ5�YG ��y�����.w&�dFn���c����Y�}�; -� �r -��~W���@ߥן��P��ew������ܙ��g�W��F��[w\|ϔ�tm0�\�T�\(��td�)tL�iU�} �.�+Co%)�-�[�����G��e� �Ý���������[|ϥ�lsN����΄ -�.���3�r -�C��!��K��֣�@�<g�u�ܹ�L�f��B�:�8~tt��mɒ-��lǖ7u��#�cG�����@�vmI;Ǟ�trbe��WcЛ�f��rՍ2���r�1(,�B`�z�:�ǻ�J_�ձd�"y�G��::69G��9�OK��н�F��Fm�ޜ���)9cU���b�_�]#�ML�ʖiD��7�s!�@��H�c�NTϣ-ԥ-�f���K�4�~�v[�i= ��{��U��Y�d�8V��H#z���0Tρ��.+��X{����k�k>�^��yk��*G@�nlw�D������YL0�8V��H��K�-�x ߅!��y#�`A3��iNm-�y=t7Dq��aȮ���W������+��ݨ�|���B�6��ׁ���]8N�Ox���r�Zn�+��\�m>����[��=�����c��=��ng�I�me:t7*!�%02ӱa�ceˉ4г pB�����6�,�cDd�P�z�>^�[�AwC���5S��ʻ!G��ɱ�(t7�!�%P�xk�X�r"���d��( ����s�ӲK��/|B�Ldw�4lNmu������0x7�7���z�!�D��~�O[���G5�v�-'��]��z�waY�Es1�_�dL�ȿ:L��z]�m-u+�O�3���T�]� Գj��� /t?���Q��H#w������ p@��rsłu�<ճ�}[>�5����U2�����}�Q�#���u����m��@@�K`G�r"���B�.='D�]x��Y s]ș���e/t?��R�rئ��+P�0�{u�~� �%�1�� -�Geˉ4r��7��!p@�O.�B����^0�.����_�BwTi���3�4�����C߶����?ơ;��|���B7+p:eˉ4r��7tUs pC����So��N0�.���ԖG��Rȥ��V9� ���uoG�6���6�wvA�r" �<���� 7�jCU�n<� -,���5����G�Oz W�4�0�Ǟ��"�cT��J�K`c��!�W��H='�xx9������{�{�Zv�J�����ԭPwtOQ�Ē��]�lӡ���|��ƙ���-'�X��n��������a� �=�|'�a��C�u/��1m%�Q������F�9/�9t��/���?ǡ���|�����Dk��� =�]\���Y`�]��]p�5�����0tWO��k�ƚV��tN�x�.:L3�J�ӡ����.����sʖi�}|/��Y!�w����\ ��=q����*G@�z7�W���$��kv�A��{�����s��?k���&���S��Hc�c��='�]|�?��Vy&D���M3�o�_�ζ2;tg��������D1ϝ�����^辰@�K`b����sʖi�}L�\ס�D�C����8�v��rG�y1��5�ۆ����\����0�������ʿ�g���i�ݫ�� �&w�sʖi�gB��|'�������hv��\���@{���F�5�Zv�to�� _��u��X#vK����C�� �]��_�sʖi�gB��|/�:S���������n�1��Ӄ?���3;twY�| W�4̮�#�_o<a�nf:tw� �%��l9��X'���w��!����=���В��������U�8L3�*���mX��"�������S������x]����?��f���A�K`G�r"M�N�=�]�\<���� rF;E8�p8���N����`t�PL60�R�r8�o��NPymd*�!F�w ,L5?�l9��X'��#߭p�w�s*��j�I��[~?��L�ȿ��u m%���Lӡ{TIn�Z�p�C�$�Ot��c�}v����07t�!�%�������+[N�)��L|�=/�]�d�XU��_��9+,�+��G8�|����i}u��t�O�K�u�z�u�����Lt!z=a����8~� �K���XXH�)�ʖi��B��|�<�����41+�'K�N�P�x]�����ڑ�Is-�y�d|���ܡ�ډn�p�?�p\�nG����Gޮ��3���_ً�W��H='����Q�r��";7���p����̟�������eA��.��#�³�;�纝��ψa�݃���x�C�>�t3���E�.�eˉ4�z9����p���-L?�\�!w���}�#�P8��{�p_�߷���7����Q����^�]8���mC��wl��ބ����6hY��_���Q��HS����=zf*���b��Uwƚv�T8%�p�������>?�~���k�{��7��8zt�)G���Y��(�C���g��O۠{.�w �=,�3eˉ4�z��aBύ��|�����k->�'ߝ�k��X|K���_)��=F�6ؑ��I��&��oty7S�w�\3r���̌���;n�߰�K����R��!�/|��{].��XL0G��)[N�)��d�Mzn�����{-���.&Η�J��ޗ��?����4j��ƍ7��_�K�Z�fh��{<]������8�Ylr����ϼdv�%4�ӵ����~�Y���~C�9���'���\N����a�����D��8!�-_�d�a�/���>1Ex��ϋ���<V? U�}�mGw�r�ڷ�ឺ��M$��8za�� ��k�����������,we�;��?���j�����mX�>ͽVP�3X}V�g�gȑ�M���H�c>��л\N���Q��H�]7�ML���nys��+M���%�:�x��[����z~��&���a�����R�����߄��+yW�Mq-�Ǫ�w�� �%��l9�f�n��蹩P�w˛�8����b�Z���(�7�g��b�:�0~���cc;0��$4�h� ���]6i�&%RQ�`H�� �9�L[�I����v�I���v�E�&Mդ�bwKr�*�*U�*U�mB�-���=�؆���H?��-����R;xn��}M�x��߃�]�+Ab(v�[8U��\=���݃��b=u���f��rv�F��o}5�BkX��C��:��z�#\o�}�f%J�z�-����l.������}�:H�GO�h��K�{�6��3J_i}f�}�ip�Kj�a:��4�r����)���A��n��6D<�>f���jE=6)ʶm��V)2߭fˋ���0tN:�w��NA��|�:����Յ�l� -��~j�-�:E�֮��m*��&�N�9��j��.":��)�v�O*����n���m�{G� -�/ -V^�[R��й���"��:���9��)�v��|x��#���Iz�8_ -wՙEb�*����Q��[bՋz��9���> -C籚���@��zf��n�T�!b�}�z��K�/w����-�9Ӿ�|�������N�T������a��}�d6�İ�9��)�J���1B{���nuC�t�?n�Λ�_����B� %��w&E�|g>�m��7�SǶ����F��"��-������_���5F�m��&�]��86�[��g�n}�z�C�+����u\�V�� s��ء3W�`�E��o#�R��NAUʌf�OJ�%RE`߭V&�z����Y_�b=�a�e�z��y��rv��l�~�����内Pi�D�2��"P,wS)�q���� �ّ�<4���.?��pW ���w�h��v,�ҮS�n��)o&��W-�}�b�����SP�:υvb����Ȼ 4����yx �._n �pw���Y�Ś���S/�L���N�u����Q����҆�#�w��NAU�R�縆j�ّ�<��ľ˟�1��R>�ui��z�A�����=���%=@��y�%��"̷C��r��*y��Fh_~̏�«�I�{��¾���pW ��v�\_P�k��K���V&�B����@��ML���-�� -�+DLO�ƫ}�B�.,7z����o۩,�y@m��(���;Oԩ�ςй�U��",}��z.�p -�r�z%H��"|xc����ٴ[����Cг�u֦�.�9��Ŵ�aGީܙ�Κ��Wh�~�G�=�6�����{��C�ϫ�j��������"y�N�UάR�縎j��IQ���G�г@Hݍ^b�pz֣�r�m���-Y�V�i���a�ͯBsq�;�YSf]0�>6(�������]�!��!�rR�h�Ο��B��Õ 1�s>h8P9��m{Z�l�������%��j�Qz�ר����]u��I�,����O�˴]�ud��;.{/���N��gs�j����S��wA�H����A�������v�Y |HE���94��/DLV�?l���=���,��,��q%y/��l���a�e�� ��r� -��z/�#��kc���;�`� ���B�7����͟��g���J�sc9�S �;ߥN�@{���[���Y�w3ݜ�]zH~��I�����x�͊6;�����jU�����`QL�+S���hߑ�� �����mb�Y��z����S�;���M�g�����C���ZN�i��R5�����._}C�3m_��W�|]�V�����V�%�i�X�x���|��)}��g��\���z��,&r�׀�SA{��ɍ^bw���^?�&��<k���w47&E�zȞ��3��ֲ�}W;/�N@�40���ah_��|�}���?�г@�'}f*��lB��Y��{��˵nb�:�۞ɼE���3.�Y �q��V�ű9oGsk�G�X���� �����L�n�4՟����ۡ}D�cu�~*?l����'Mг@�g6��P�5��)�*�s*���5�3/�i�}G��)%�&0k�t/�/'��>����Bߩ�����k�����v}E���o�4K��ئ�7�2���� Q���tkp� =D_�Z+��W>EU%3�o�0B����P$n����H7���A�ͷ��ɾs�͑�U�����|�L�����[��9����|��E1ICݽ��+��,% -�O�0r�!7+<���_��%]?Ilг@�%}n��:��Pо!�r��U�lg�y?oj�J�#����!�YI�v�4x��m����u�|�v?s�[G������O�y�M� -���gҍ�g7�~ ��DϨ�7:��71E[z��vb���3�O+�"�"��y/c�z^������Ҿ`5[��g�5C����f�b������es������4�cj��R9�gǵ��x�4[���O����#���z�[l]0 wL�g���� -�J������/�b�o�a�����M���/�ؖ?=�~��^>oo��%�ź��i�q)�4��Ů�(�m�i��^�q��������j���\+���X=E\^���V*�;&��H���d���/���T��*���C���ć�4#��u���¹5C����1����ɻ�S� KGs��ڶ�>��|�דٶv �̶��F�ƹ���oO�����=G�j�v=�/��293��z�Y ����\��*��2��N�@{����S�e�X��>�y��ϓ}�;�0�AbPc��[S��3�e_�o�|�E�:�;/�i��Q�X�����ߤ��sH:�3�\���o_?��zN+�{A�;��(�����C�.�X{�]*�e�������39��s���f6�?Dh�ĩ�? �MYN�i�8:��OXbLJ�@H�4�f�/ӋV[Q|� �s[}~)�o�N��{օ�K ��wZV��G�'?��x����Kl[��/�{�Ћ�D��H�"%�]˵�X�+Y��q%�AR�@\;M[�[?"�E/��,�@�tQMwE�M6ZtQ/�0P�@Z�H��]�PDz�i��(W4��g�!u��R4tG>s��R���^�z��w�cUZ���V�6ڨ����g�n~_Y85���O�5�yQ5�����J��G�^���ċ)f-eM�[m���0���������Ll�og�71_.�Y��E��@�[�[J�'��5����J꡵��' �ڭ��N���+�,���j��g�2���˳&�����bV�Yk�)��[*����@d��F�@_��Sc�ֿ��c7�y\��>�ѭ��\�%��W�_�S{��ż��/��J��xW�����#�`��ރ:�Ԣ�C�7+c��\��p[�vfTμb��H��ř�����s��K �h�G��c������t��$�H�؇�(}��{��'vt��^��a�f�;��������L�#�z��ܖU����+D����j�Jx����C��@㩳�ȋ�N���;�V;��� �s�oq�4����wd��Ӹ�f8�k�\sV�>�A�@���';���U>]���P�#��s�����h�����k�D�9�� ���#���ȪN��~d;1��*˓�ݖ�ݱ�T��:b�z��ȏo������\�9��Ў�*�CUu䷇�j����pj�J�8߿��K ��̻�>Z�r�}^��Sk��~��@~�O 1oR������^�Q�{�Y�{��TQ�%�&�Ι"3��~8���ř��������/7�^R=���������Y�@WR�J�KI85V%�%ׅ9�V�2��V�����g���ﻩ����]n��@C�v'Y�Y���PU:}�[nj?@u�<����^ΧU�=����������S�Jͻ��������˵�?��g[1��G{7^�i+�@�)VљJi85�~�� c�C�c�����T����������0�'����\�9ĺ�;J��k�B��k�~�B� �C&����H���*�?�̤������z��~�k�9�~�d�P���o���ĕ�"C�7:�=Ձ�j�J�����Ї剿�-�$�)?K��n�2�[���㬙����b���S��"{G�Vf�j?�ҟ{*}O��L�{ -��\s�B��)��)>;��>�w���T>L� �`�p���W�U�����'����Rd����L�%c�d���=�p�m��y�xN��Ps�qs��S{������Tͳ�K-����`l�� �l�9e�Wzp5�O�����W���ɏ�K���3��2S�0�'���gm�n�>{���}�;6{��VjO�ӟyd�Wyx5�����~h�g��Bvx��,Sn�2�u ��R���]��1ؓ.�ʙ�\�6x��1�L��åȖ��*����_��n���9dF�k��q��U�X���L�'�������1�5|�,�{2��_9b��W����.jO��{�)��҃����TKGYS[C��>ۊ.Vq�5����y������/�����!�K�#ʺ�X�^�שּ߅�}r��zhɺ7Y�5��}[�Ϥ�������Ȑ�=�U��Z��ׯ'�W��E����xy��_t �U=Wn���~gk��!��J���dz���0����4��v�ǻ#K*�].qqY�id��aj_���C~�� �s�c��ͮ���4S�䰐��ȼ���k(��7���<�����~�p��\�3�� -44�S�tR{��~�1�<��T���8�:b� �B���O[��r�y,��J���d��b���:C@�?��3d�9�e�~���I;r ���Pݼ66�Z�7n�R�o��܅����j_�R̒����j*�=�˵4�D݅��sK�e>y����ϣ�L^o���P�,�~�44��1���P��q�fce��vjo�����5�WC����~���Å��S��~�����3�f�&���T�ǘ��=JUi���Gz�]���9d"j�!��VR��� <�5ʛϰ����B�ļ�B������>�0V��Z���T�8sN:Y(�������� -���>��/M�k���a!��V����j(U�y9Ɯԙr�p�m?ϒ�@�A�>�<af�;��W��h���Ox������d��k��1qd"����FYp5�*���}������V�' -y����UΠ�#��������� -%V�����-�WUrci�!j�����Hip5��}��i�r���~�'�4�e�>����8��N��S��З��{ �ǻ�S|uOu�J���k���qQ���|�T����k(���ؿ��=��ֳ�1ޚ*d��\('���-˸1��.j�������`S�l�X��?{F�Z�:��ɏ��9��0S�z��������� �ٲ�T���j��˳�NR{�Ї��V()������T�%��# �lbår=��Y���Z�y�3���~�>2\�NJϘ�$�]����p��+�LY������X�j�n��HE'�2��U�ͻ��w��Ace�~�O@�s�^Sy�5��}�>"�����߄xg�.�}�T!>ۖ���5Vf~��� �@��u���;�\oȒ}}1��Ϯ�S��з�V���pk(�}�e&uր$:�c���K��k�ɲ-fn~~�ϕ��F̼��Q���tg���#x'ٻB�ʭ!fݮ�~���y�V�YWz�5��ٮ.�i���o�{��g���U�%v�<���>w�o\��Q��+��:�>�L�#�]��K�Ib�T!��XW�$W���p�:�A��+ ���g�u�\SQ�w��a�a.��,2V<�Rͼ\�3�mYFf*���.����GG�S�lْ#Y_���CN����qb;�]'�Gڒlda�X�dRb�V�:��س��E(acW�m���c0���rS�`40���� P;����8ڱ"��=?�q�� zx�>'��I��{w����h����wM~_̝�c�{���kz�y����|�/�E=�6����% �ۓ��n�)�6�&=��;.ǭ������x���_�����$_V3C�����k츼�y��7�A�~�c���\���:_�uN�����.��)��=��=�:geQ;��j��{9ݭιn�C�$�/O�{��r5�A|�4������G�O��2���#߫D猴�+�U�w�`���>$��i�g�'t��c7�B���{�9#Ir��v1��!�&+�/�>�F,|��׃ѣ�L��y#�3�d -���g ��OG�g��Λ�0�@�3w��4k�� -ݕ���7}b��j���ܑ$�3gG~��Te��x�uk:��l���%���G�7�ZS�gй>�pп�Dr��CZ�����T��3'K�įE����t,yH\|F�$ɭ;s�U��<�6���j�S�||�M����;�Z'����x�m:��Ti�������Vh�(�w�<���*��Ρ��`:��I�;}�A�1}��uU���gڥq��v�sGZo*�,�c�y8��s��<S�1��� ��kz��Ϝ]�>�s^�w��8��tI�|ѹ�?�u����uk�����g(x,܀'*�v}�E琴�L3��1p�� :���0>�Z��<�%�I��_��j�{MVr��"�k������V���֢�H�$sL�1}���PMm�V��,�'9���ܨo; 94���� :����b�":.�̣A��p"���c ��v��@��G�,�<\�ޭ�gƽ��ZH�>�lb2:�$Y�^�1������c{�5i�q��B������;GP��%i��-�6� �̣A��l*�����+���EAE������h���fg�[#�O��Mw� -t>I��{�����x�èEsM��W�s�p�����2�S�zS�g����� :�f'��m�DZ+���ɯ��i���@�4�ޞ���ZtNI�e���L͜�i�&��-�}���Z����A畴�L3��1�EՂ]@�?�Ti�7_ajW|PD���"KRVe��F�ka�Q<��$K������ޫ5 {ꀕ�3̻�qϯ�ʘp�����sK�t��YT=�t��H>W�1��q�ؿk�rgQ����<+Js�nMCQa�����G�9&�Rr��ygG�ji}o��4Y�Ri�3U� -Arh������9&�c&�l�ˠ�� :���e�z'"�s��q��ɝGY�Jb��h�ꪖ������н��k_й&I;:{�y�O?H�ti�j����J�?���*L����k��F��c�\p��/�x㒄��,�3��)�+{YW����w��v`��'�a�B�깑?$f��t�{�,�SmL_~O .�IG��ZS+���P?��X*����]������N��LE�PH`I�t��s,Μ���9���@��G�ö��5�f��?>ǍgnM�7 g��y&��E����b�� �L��I�V�>7��*O������c�߮�����)X?�F -���5���[�`��b�wh�%m!�$l:��y-���2s��ǾR�yͳ���Ѽ6�%i���ܰ�N8oq�<4���P�\�7�Q����&�G?��w������W�$�r�o%�p���wG?IL���a���C{�ti]-�bk4!�x�ּ�z?��ՄQS�՜�ᱞ98���ky9ٺ��cXOtM�A�#���e�X�O|��B�Ŷ)4���܍��s�<� �>`�LH -����;������kʮ�OxG��#�#�.Рز������� -�:{>Y^���¬#��f��[��^OЮ[nN$WtY��#��o�V��c �ﵞ�j��@��ʒ��u � �@���{��[mimb��=�,��>��,v�� t�Ir=Ǐ�����j̘�YC������:���i*�,�c�Lt��A�+N$Wt$ƛ}��#�:��A�ޗ��CU�px����g��w���Ď��4��o�T�q���A�+�=�|���� ��i�"Ȣ.\:vϋ�$��ಁ���VM'�8�1�8���+X�kF�ADY���n���H���Tt[]3h��ߎ��v�2v��YMgC���2�l�(c��Cx��L���H � �m��4����v�jg���i�%F��]��}�G�C�xa;�� :��5Ӻ��c ����r��^�+�=ò$e�G�r^�9A� � F��8�½��?��]�jc:��P���v���l:�;2d�H���h�7�9��n�50�-� ��ך{��^������W�]�����Ꙉ�}� �>/�sP��)������ei��<�o|�i>�|Nqͻm�|#��� �a��H�^���� �ߑ�0�d -:��X���+����k1Vr�V�5f���v�����?{��n��s��/�L3�FDy�����j�*�/����f��*t�#�� -)4���NM�/ѾK����_� ���Οe����gA�^ KR�O�]w��g>t_#��DrEAǰS5f'��ߍ�$+�sGZ��v�z�-�ɢ^��ݗ ���A��p$1 ��=��i/�|�Pb���ݚ -�c �������ȑl}�5]p8�W꺪A� {�������^�a�nb��0�����7�Ǯ�x�q�dB�@җ�IlZ!�V����@�^o��)M[ljT !��CH���P\zA�ās%��� -��o���jݵ��y���?��G�hݵ�����g���E -3��+21�=�9���Y��V9w+��v�r�t�x�z�s�x����OBHg�sݍ���{��~�F���;]Λ�z��%<jVD��f(OW=�um���'���z�F�8�I/�q�o��[���Bڟ�Ϊ��s����ݑ��"W�>,f���W�N���Ђc���ߔkqt��v�0j�˳���Om<����w�D�NKH���t2u�g�u�}O��so�?�����}5g����0~#����~ -�z��Rɯ'�3�~���I��ށtf�����[�b_�v.����17�T�٣��gV��#د:e<�����hc��o�b�8�KO�^��v�n����p�����bO�;��w����,�ϳ�}��7����O���@���¨��L��M�.����k}/���$��9�^�]�O�%Ď��,�ϯ��r{"g'�[��=��ґZ\=C3��l������_O�g@�\=u����Ӄݣ��GҋGR���tB������{jc�w�<��[�������NO=C�������[�8j��7�e�g��;��+�O?�=W !�x��q&��̾Ñ�XN/�YP�O�����<������JL=��<a���><}��'ѯ>���s����㑋g����V�?l�ܭ�z�VP�Su��o�J~=���pmfe��O��3�?����?g�F��y����w�ؖ�^��\�����Hg"�}�Bby��Ń�Q�+|�C&���U���(����BF?£:e�7��>�������F��?��n�ɻ�=!6�~�ֻ�n���纛����n���w�����깿�_9f����K!�����<z��T<|�Uπp��L|i���g��O�����z��^�,}�tB�v�7�����M�Gf&��ξ?p������?©��<�����y�m�z���~�C}�B�:e�幏�~��/��gg��dj�gy��]�{���'��s{�!���u��w�]��������+�^��p�ʣ&�>���!S��j����r�G=:�¨q�N��+s|��[��F������mw�N=�~KlO��{k������\����ݓ��o��X)Y������k}��o���"��~�k���ި����*}L�o����Gï��f�3_�q���9g��?���簱��xk��=��K�H�m�w���5��۵'2������s�O�U��nx����Ń��4$*�Z\=C;��`s��o�����P����Y���ڹ�>����N�z��NJ�Ӈ�q��F�.�'յ�������Q�/kL��L'S�����X�T������.$�?�N}�����~��4nb�s����S��.�[�5Q��U���d�q�3]K'M���-�4\�}���?�_�y��g�����ɷҀ��O��S��ݾ���t�O�o��chy��\z��}��I�i����:��������UW=C;���:���[�îo�����I�gh7u�TG��j~��s��`��1SϠ���������&S�� �����R�9���@�\���gPQ�Mu��W*�jq������9���|u�TG���y�P����Z�Ҹ��gPS�Mu����BF?��h�Bv�QϠ����o�#��3����t�f\=� �}S��mQ�\u�3���湜7 ��P�Mu���I%�ι�� Jc&���&꾩�z��)OO=��@3���:��ۨ8j����ej�,����o�#��g�����ґZ\=���}S��mU�\u�3���{w9o�l����o�J�s� ��P�0�z����:��ۮ<i<���`,��r꾩�z�AP5�z����#��3�N�7�Q����A�&W]�A����W����K��zB=CP���:��I9w+�����h5gx&�u�TG�����. ��Ti�8��F�7�Q�?��S�S���@'Z��g"u�TG�������P�Mu���~��:E!��g*u�TG����ݔ&W]�A����t�k��Z�#����N�7�Q�?'Vx���*���z�0P�Mu���r�ƻ'��MT�6�z��P�Mu���J�s�A�h��q�3���o����T����C��6��o���U������ Z��g#u�TG����1q����f)u�TG��0+OW=����o����F�3���Φ���� ���Q�����25�!�����)2����M!{�Q�� �}S��; ���/�kt�6Q�Mu���(��~��,@�m/u�TG��NSȮr����䪫��Ө��:��w�B�&��БJt] u�TG������g������o�uE�}S��;Y!��r@?���V<t#�������:��w�#�х�~��Zeq���������~F�����Ċ���]���%:/� L��o�u-��������~��Q6�� i�9�\�>bB �:3�i��6t�_ZM_/��!�3H��Y��*�3�'zo�G��_=\�y%I5� -���ҭ[1�ޤ��_���� I�{L:�q���9�+zo�G����ݼ����� �A���&}t����(�tI�~gzb:�~�ޛ�������%�3H��wf����g�ޤ��_o3�S:�$I�m> -~�j�ޛ�������UDg�$i�nܺ5B�M����>��mc��sH��״�ؤ3�}�Iݿ>���A�t~&_����7���Ǖ���$I�a��3���Iݿ>g���� I:mW!�3�s�Iݿ>�nR:�$�4-���y�ޤ��_�Q��1�A�tZ��Π���&}t�گI�� I����c�Π���&}t�ڿ�u���$}L��w�ޤ��_�Q�/�A�T/e?4�:zo�G��ù�:�$�y��:zo�G��úmo�6�C�T]��UBg�a�{�>���:4����i���b�9tp�ޤ��_�S�/�A�T �~H�:zo�G���*.�1�A��*�MJg�q�{�>�1���$I�7�Z�t�7��g���� I:�i/�s��7���~�J����*�mFg�ޛ����7�XGE7DtI��M�<��˽[�U�"�A��� $tU�7��W�<ܬ|%����&�3�Z�Iݿ�izb:�$�}��иmo��zo�G���*��� Iz�E|��*zo�G��j�|y��uCD�$����l�Tm�ޤ��_�P�ی� I�_�~Hn�[<���ޛ����>����I��!I -�2ߤt��7��W�,��$�\ͮC���P��{�>��Ӵ��,�!�sH�9)��Πz��&}t���E|{%��fסyw����/zo�G����v�����Òt���:�3���IݿNG�o3:�$��Y?$t�zo�G���2�\F��ФsHRm�Bc���^�{�>���r�Iwo6�C��d> -)�A��ޛ�����!�3HR�M/C\��1�C��ޛ�����}�ZƓ�C���nR:�N�7����(�� IU1w���ޛ����ܶ���7^����B\��1�C�ޛ����<_�͢":�$�l�N�:O�ޤ��_�m6z��t�y��:o�ޤ��_�)��I'g~����!�{�>��/�������!I�5�Qq����_�Iݿ��n����:4���^�Nh�{�\U�7���^��fL/CD琤?j���p��9�W�{�>��O��w��sH��)�w�*�ޛ���KoU�Rw���(��� ��7���ޫ���I�2���v�Iݿ�Qe���$�w��ޛ���K��c�� �t0e���ڣ�&}t�Ҿ��M:n�9$���(�ti_�Iݿ�o��:�k�9$�S�o����!��7����a�=-�!�sH���n�>gx�@�Iݿth�����U����g���s�O)�C:4zo�G�/K��g��k��^�nV �C:zo�G�/�m{ۘ����g�t\��eL琎�ޛ���K���)-.BD�tX��7nrHzo�G�/U��zٜ�CB琴?E7D�ߴt� -�IݿT)�Mc�o��V�H���(���2�sHUB�M��������i/�͔j��Q9ܤt���IݿTy�u�̷�C�/惐_�M:�Tu�ޤ��_��i��YCJ��ٴ�r���C��IݿTWe�I���9�sQ��쮳�sHuD�M����ڻXG�<d�N�H'f> -��j�9����&}t��)�moe��ݵ�,R]�oBr?X%t��{�>��du��<�}�7�]��l�N�ҩ��&}t��9wW�|�q��"U�|���w7�t�ޤ��_:;��F�o�I�Y�cj�F9���c�g���7����]�o�o���_'i��n��n�]�7.��Iݿ��������!}��:4g����!�'zo�G�/��̟��e��,�o�ۡ1���-�[�*�ޛ���Kz��벹��mAgљk�ط7!y~O�,�ބޛ���K����*�B��t���uh��M:�~��,�ޏޛ���Kڏ���QH�m>�j��o�����Sz���y$}�7���t��e\����*�tU۷n�v��v��ܷ�i�7�u��F�a408<C)`mq�Kd�8�Go�0�rH�n/��Z�w����O�ތ���N�m�j��r�-]�z��(�f�ʦڗw�_��[$���-��%ŵ�<M�|���A�=���o��m��3��d�J��-��%���M����U*W�T�z�7��-����Ie��R�6E�=��V�{3Z��K:���Ϣ���C��yח�}�Gk>H��(��oQ��ZI�V�ތ���λ��sk>��7�r}���:u�[��9v�O��/�s|�æ]^=vf�m�]�λ�-��%����Ǜb5ݖ�w\�s�0��d���W�T4Ӽe_��7B}�9��]�}��u�{3Z��Kұ��U�6Eny��YM6�������1���a3֗�=��Ƽ�_���v]My�7�-�\� ��p��� -WI�d�����������������������������������������������������������������������������������������p�0�!��� -endstream endobj 20 0 obj <</BitsPerComponent 8/ColorSpace/DeviceGray/DecodeParms<</BitsPerComponent 4/Colors 1/Columns 700>>/Filter/FlateDecode/Height 700/Intent/RelativeColorimetric/Length 5953/Name/X/Subtype/Image/Type/XObject/Width 700>>stream -H����g�i���c+����j������M��P=� -��rQ�hr��BF.Bj� �%�lv��R��#"2�R��S�L�s�����������>o嗱1�>L<�IϦ2�JM����m���6���V����f��w��ܫ�$�7K�&��k��b�_�����yB�xLf�#${S�Q*.��]�����s��~�Q)��+%0����j����~;A��<�ԓ���N��Hoo�W��D��Tw3��3��p�?k�Vg�{�R�m��ζ��w���+t�w;�/��7M�J=:�U7R���1M:�~uN�'�s�#~��2��~�������u:��t���4��E'8��"}AAd�����a5���&S��3������iG��J~WhХY��}Y���:ݘE�'�yŖo��u�v����X�$���ە�K�YKgtSɹZ��-�L���v8K�\L�T���˨V�tE�����e��>�����2�g������'�dpߜ�ݸ���~ L�n��-�5�+��&�'�����Tt8N?�<h�Kg��2r�M���Я#w����uE���v���a��+�M�UnV闒/�kt����גk�t~i��&���-��2N?���݁���w�݀�������%�Ϻ/��������H�`�r�ˇ`�~�����b�~���s�}8$j�M?xP6���H�~�д���Fb�~���W��t�~�@e� ^�~�p��o����m�~߀�ҏ�2�¡�i�O��ڰC?kN�wP�~�h���2��1�'��A����������o���qD?d��/��:���J�/�%� �K������Y�~}�5���s:�o��O't���'�>U� -�T��M����;����Wt�Y�L~�O������2݃G*�cɗ�t�h�O%7,�Qx��Lr�-��я$�ۣ�p�[���.5� �}�H�vE��+�y�^):g��鷑d�F5M?�<l���IY�Y�{t'Z�E�sD���I�_����"�S����!�hҽ8�L?��K�s�)d`t3�h�� C���q����:���U�N�vE��m���գ�/#X��!)]�}GġO/�Z���W�tD�}w1 CgD��W#�H���K��bHt���� �蘒uN�[�=�S����Ť�S�N�c�YM���ҧ���R�-����>��P��J�K��bG�.˺��ŖM�-���=�t\vu���E):/��u�*�/�N�ۊ]m�0kv�ӊm�tc��ч��ʬH�g�$��l��*��ҡ�wE�T2E�fZ���$�nͰ����]�QY����C�7��cJ�^���Ӣo) ���3�B_R�֥�3d�>�$�]�3��P��3�>�0���F����@��F����P�t{#zIP8�t}���'�t~��������� �F8��tB�� �>��Rt��9��&�.]�P鳉���AMܐ�C\���8�.q`�����M�w��Ӡ�%��sD�����#���f�c�[��"�W�o%�����})q���>��C�{���쏾�&�ʾ�+��N�.�0MIܴ@�������t�zM�H\�O���}!q�4�������t�����#.[����}q��=�ۈ�N�B�֥o#�{J'z�"}q���]�È���HowN�E<@Wz�y�*t����W/Н�b�����D�z}�����v鋈/��V�FD���c�R�����G���)�������5�'.��N����bqI�B<C��y��=�ٟ4�K�w�f?��w����_���x����+����ὢ� ^����.wl�I�@<E�;��/ �:���ϮNw��/�:`۽�����t����m���^�����.~�s����.��4=]|��j�o�r�ݏT��p�_�Iw��-��1������n�^-!xG�{A�� ���қ%[ɷ{Lo�0t�o��,��$��&�XBQN���X�1�l��^ G>�vO��^���s%$��LW���A�$ۭ�k%(ɥ���*aI�[%,��ڥ�Jh�Jw�^*�9L���T��T��N �R2�n�;%<�dڭ�;%@��;C��m$���RBt�D�mz�i�~���F Ӷ�v���+���A옶���PB�c��"�PBui���P���n�O�}�7v�ݥ�I�,4\��$`Vӝ��I�r6�]��I�Nl�{J����l��&a����sz������ݥ�Iؚ�����$p)k���$t��]��I�j��-��$x�ڥwI��;M����i�@�����{I��i�^%1��Hw�^%18����Jbв��9�J�`�]z����t��&����<�I��0�n��$�0�.�Hb�5��cz��b�t�+�"�E�t�'�"���v;� �Ư �K�x|g6����?̶�N�x�̶[��HD̶ۥ�HD�Fۥ�HL�M�;G����lw�^#1��l�J����l�G�����g��"q���t���˞�vW�-�3s��[$.ms�^�[$2�ڥ�Hl�]�Ԃ�tS��M�T�K���{S���K$6W��=��Hlz��m�K$:�ڥwH|&ծx*k&��C�m���C�S2�n��!�4�n��!2�n��!2�n��!2�.�Bb��+�zi"�G� -�ц�v3� -�ѡ�v_�+$F�����v�����v�� -���v����v{�f��_��8��lD�\�`� ��%��X1 83Xp��AA�����LG@��^�zo��]�4�~�9�s��y����r��b��v�Ħn�+�R�����ӽG� 6�Vo���AlZ��n��AlZ���8=�شU���b�~�v��Ħz�v��Ħ���~C� 6����K� FUow�A���� =�U��sz1�z�Mz1�z��b�ڕ\=P���a�+�R����+�R���Ѫ�ޣ'�Wm�>=�X5Q��nz�j�j�=�bմڕL�]�U�7���R�_�=�X5^�]GO V��]�ԈڕL�]�հڕL �]�T�ڕL�]�Uo�v��bT�t]�A����!=��v%W��ݦG����I� 6����F� 6�Uow��Al:���sz�i�z�O�Ħ����3�Mk���g�����O� 6MWo���Al��ޮ�g�jjW2���=���!]w@!&�h�=�X����=�XT���8=�X����!z -�h�G�]�b��:z -�hH�J����{A�!yI��c�A~�ݢ����;G�!��iw��C����n=��3�]G�!��]�T��vO�A�O�-z����z���W�#�$b͆�v��IĚ_�:z�fP�J�����Q���ӣ�- �Nѳ�-[�ڭѳ�-����ϚD5�v%S�u{�0b��v��aĒ���ӈ%�>����K�|���5��k��N�#��mW?kͱ�v��+~۽K�#v�����5��s�n�H����=�X����Az"�b�w�z�J$}��ݧG#�����q��>z&�a��z�J#�=�����Ă��]�>��B<w���Ƃ�{L�%IW^ �8L�=�\R��0���+����C&����;=��n/T�z4H`σ�ۤG���kw�M�v,]7@�&e[�TW�v���h�u��pR������ �)h�;�xR���z<)�~�v�h�`��[��bN��R�nW� d2x� zD)T�t�=���0|�z4Hڭ�CJ�"���!�D{1�գA���!=�(J�n�Sʳ�]=Ļ��ݦ��DJ�=���,�jW���h�.ѣJYN�����Y�,���u'��R����������ELWk��h�v��q����=��#��[ z`)Fg�vg聥o"���5������R������R�����czh)������&�u���R���zl��.��s�ܒ�?@�~M.�;��u��'��=��u��蒷���=��m�k���K��t�0=��l�l�5��%ch�#����Wh�����u��N��\���u�^�d��N�+�<m��:}x�v�Ϯ^�r+�t��ӇW��Kg����$?)�v�:�!�� ���6��n�'uz��.:ٟ<�W!yyI��z���w��eHN�^�sD�C2B���>z��)��_X�"�hЭ~�ވ䢏N���J$�t��8��"Y�;������ -����H�Zt�W��"��#��"�I��蕚�j$q�t�W�W#i{M���r$it��O/GR6I��Q��z$]gt��$�J�G���$U+t��ڣW$i:�˼^7�#I��]� ,�K�m�]�� �&I]����k��<���!���x1�U�W%������*I�$]d��eIJ��Ҡ�% �sl� �.I�<]c��I*�z1�uH�L�E�ض��I�F�x��I -6�oe�^�$���� ��-}I/Nh_� ��"�:a�VpD/OPw��+街'��t�L���:]_Ek���O�W��B����Uv�^�0�J���0�D!���y�L�Q�;���d�^�DG7�M�ޤD�E'��'�*%�'tq�͔2���o�^�ijG�����hѭywJ�T"�S�^��1B�@?�T�a��,�Iz��*]Y ��b%�C��`���JX ������JPt_A]�ە�z�¢�+��q�K/XB��� -n�^���@���d a��*�yz���.]U$k��ŷ=��h6�U�_ ����C/[|j�=E�G�[<�k��xC�]�xr�N)�3z��E�Ao]<�3Bt�k��l���g�⥪�!̧�a� �}z�R��~P�Mz�rk�z`w���- ���:o����4�+�-��l�pD�A��)M*��/!�i��ɤ� }iG�LJ6�s��ӵ$f�>����Jr�����Х$h�>���:�I�������Ju眾�\c�n$]��m���@R����\팮#q+��*�tɛ�O$�[����C�Hr���<4�;�F�(r�K_J�פ���<},y�6�CVF�s��^�5d��h�n!?z�&��C���9�l��w;t��'/��U�og��@�^�׳쀾~���ڵD�>{='� ��/_���+Z�z!�BҞi���8�oi�y'}����d��va��鋚Q�o]�e��6�tЇ.ѣ3��<��\�5���۽G��\#'�u�6K߷l+�}˵�M�t�S�ƅ��/k�}���W5���tiZ#�M�n��.�*}O[6�{���!}Lk���1M_Ң9��%xE_Ѩ�M��;����]�'�������m����B�N����AN����� ?���W�t��-��[�b��t�h=��%�<����})�г:���W����.#u��j3t)S����I�}�ޔ��>�J_En�i�N%1��ȍ��й��5K_C���[��4\����������������ί[t<��/�H��t@�%z�R���d��йؤS���z�����S<����ů�e��86�MK�_n�a�v��ޱ�N��R�^ �{�n,���f%���M�4϶F��ٯ{��0��'C� -� T:t�E�Vp%" -\��C�P�� t)!"���'��Sbߤ=��{9�dc�N��-5�k��ذRw��.~M�#$�f��}���^J�H��zf�z:�ٯ��*pԛ!3�?�=���+��B�T���˟$C~Ͱ��'�>W -�c�<�8�%s��d��gANX�P�뗨{���9s��Kq��FI�r���SE�S��Tߎ-`�=�m�.�\K}1���}'��3��{�^V_��U�n��8�����~VT_�]Q�\���d�~��I�o�T �%�]E�:�5Z�Aų�b]��I8�ڮsqz�~j��������������������������������� E����� -endstream endobj 11 0 obj [/Indexed/DeviceCMYK 164 21 0 R] endobj 19 0 obj <</BitsPerComponent 8/ColorSpace/DeviceGray/DecodeParms<</BitsPerComponent 4/Colors 1/Columns 700>>/Filter/FlateDecode/Height 700/Intent/RelativeColorimetric/Length 6032/Name/X/Subtype/Image/Type/XObject/Width 700>>stream -H���mk����}c�"�X1ŲЁAR,��2,)(��Xy�6�v()vS��%0 ��)!B�HCB Y��B��87���V]���9�{n~�W0�u}9�磏DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD;��f����������bvrf�ii���������W՝�������]���V9j���ZG��噱�� ���������k�:����%z0��Ͼxv�4����,N�CJd>�����h���[R�b���~�Y�凞f�гK�ƟV�j�q�1s�ނf�a��m?�7�uH Ɩ�\?�*�XΖ+ҙ��<E�G|5�O��U{s�czM��J-��^5����o�U������K\f�A�8��� zw�nw���,)�4M���p�9A�Q��OWgNy�ަ8�+ѹ����w*��Ьh,ы����,�M��k~@�e۶�bt�L��D�@/Z�k�Q�S�Io[���I��X{�^�q��tJ��1z�2��6]����D�Z>G@3�O����O�+H�&t7~��]�ҟ�1?j3�5�wt.�9����,өx�5G_E�+ҙx��@_F�V���"}9���.�L_HN�7���G���Cy:�@4��K��f;t�ݣ�%o�Zta�ݠ/&��x�n!<[����6��T��&��`u����6��hC����%쀾~��.�'L�}�,�WL�}�H4��%S3zH�<�1ӲJ�;.��=�q�C;6����З��}����r��#�ec�C=������O�}�V���o���ѵ�)}�X�ӗM@+C_9F��:�>t|f�&�%}����M�}혌��LL�>x<ҷL�]��أ����W��(}�Dծї^��a�з\�>`ʶ��l��^���5O�N���ONY�+�H�>�|��]Bp&��w�ct�Y�/&o-�5e�>���D�}�z�J�{����Q�� ,�w��ܣ��*}$9���uS�O$�Y���[�CHN�C����u�L :�ѷ�.�t"�*җ��ѕx�D�Ez1Aw�=�(қ<]�w��H��Эx�FDz�J���-�ҏ-:d;�1�?/�d|��/!};���C����Fg�q� -2�:�}P�/�}�k:�4��1��7��e(: N�)�x������lP�1h�&魋u:$����"�xo�ctLne�}�AtNN���*tO5�e�Y�tQμ�W-�m�M9R�-��U9Q��,6�(�K;>�˲n�^��r�n˲Iz�bϧt]Ve���E���l��+V5�,j����ta�ҫ�6��,٠+��ʬ(�k�ѝY0I/UܸF�f�Ez��H�N�8z��L�nͰz���2]�Q��:ť�to��^��Eg�/�U�cM:9c�M�s{ts��Z�����^��twܧ�(:��e� -�J�74}�&k�noH%z�����4�>!�� �2�<A���{H/OXKt���:�e��Kzq£#�6�@��p {����t���K?�!�� -�2�D�N�o/镉/�-��@/L�q���/z]�:�c_�u�O -t�}xL/K������*�L�.�g;���7�t�=z@/J����� �&�P_ ����GE��ܢ�$~�D��]�ޑ��+�̮V���f�6��$�j�qvQ�$�*�u�)O�G|���<��Z���[�r�o_Ѕ��*���詎�͈�Ӎ�b�^���ӿk�Z$�U����k��ҝ��^��Iwz�]z)�]�2�J$t�xEoDB�W��"��k}_�އ�㐮�=yz��t�!A9�{}�2� ��ط�UH`jt���J�BB�[����!�i��~g�^���s��o�k����j�k�^��(Ow� z �6���J�$L�r��ʀ��w�^�� -x�H��p���$\�bۥǗ��o�_B�#ۥ���U�t��%l?��mӳK�^`�NңK�~@�{HO.�[�ҽB.��ݥ��-1��cK�H�+����]zh��+ �Yzh��u����%��ӽF�,�p��6=���K���K4�����K<rn�m��J<v����Ǖ�8m�LO+1Yt�.=�D��0݇���Oݵ[�g��<w��EzT���v�ғJl�\�K*ѩ8J7K*�q�n��S��]zL�P�I���1%FY�V�)%FO\�K)Qj;H�=����v��%N��.=�D�e=݇���n���Pb�l�]z@�V�r�S���mW�b�S����IĎ��;A�'1�d��2=��l�f��p�]����N�f��g�l�1{����$n+����M"��cz4���v��d���ڥ�轰��-z0���v��\�;���$~�vڥǒԬ�;M�%)���=��`�F� z*I��^z(I¡�tM%i���3z&IC�|���$ K�ۥG�DT��{�IRa��=����v+�D����v�$[����d� �{�H�a����H:rf�m��H:�ͶK�# �M7K�# 1�gm�GRb��MzIɘ�v��4��E����HR����j�R�`���0����ҳHZ��ڭҳHZf̵ۦg����k�ESU�*c�ӓHjΛjw��DRs�T�;�$��G��}MO"�)�j�CO"��7�.=�$�i(�� �C�N�sHz��iw��C�3n��2=��g�L���9$=kfڥǐ��] �+�+��I� -=���H����K&ڝ����L��AO!)ʛhw��BR�d��=��h�D�Mz -IQ�D����C�+�j�]����R�u�q�"�X,��R2�� �C�B@Ȇ#CH!�an�H�Ѐ2����$ED^�^�hh��Qϗ�9��z���yr��\yH��� 6yhw�� 6���{�� 6���;Eo���7�M�����Ħ�v_�Ħ �v_�Ħ�����Ħy�v[��iٽ�Cz�ش��.=A��P���-�+��U����+�rN�2�@�rn�*�@�rn�:�@�rnw�^ V�]ɕs�C��J�J�����U����*�v��b�s����J�J����7�@���5�8�;B/�����UjWr�v%W����*�v��b�s�W�b�s����ʹ���1�й݊� F��]�ԎڕL�R����jW2����!�AlZpo�Io����Mo�&��}Io�F��}Ao���7�M=����Ħ�v'� b�{��(�Al�����Al��n7�Al��nEo�jW2U��n�^!-�hw�^!��hw�^!M�hw�^!�����B,���� �B,j��n�B,�nU�+ĠC�+��V�����v��bϜ�v_�;Ğ ?�N�;Ğ?��;ĞN?�~B�{��[U�1��v%Su_�6�%b͂�v��%b�=_�N�KĚ������5���՟5��[�jW�j�k�Io[���z��r�_�S��冿v��[���Ϛĥv%S�>�ݢ%�>�%�>۽I�K�}��^��k�jW�i�mw��#v<���#z���g����{Ďv���W��nuH+��]��|�;N/+z}�[����՟5�d�� z��0��yz��P���0�Il�>x%����ѫĂ��>�W�WC�{�^%�HW��v�vw�]R� ��U5K��u�i���%���>x%��P���ˤtwC�;A/�҅J�ꦗI�Z�ڭ��mR��p�>��Iٮ�k�w�6)[�t���&E[ ��2�NJ6��Qz��,d��h��^�mw��'���]z��+l�U;�O���n�E/�R���4�PJ:��2�P -��n�Ko�2M�ow��(e -�n�No�"�Fh�ڤWJ��b�;F���H���R��8���;�<��{��)剓�>Ļz�v�R��X�^��Jib�[U{�T)��x�ޡ�JYj��տ5�1�j�^+%���n�^+%��nU5�R�����ߚx��]�[_�#�[-ҋ���n�XJ;ݪڥ'K��ow��,e�"~���&>l�V��j)�Ѯ~x���n�J��M2��ӻ%L�U�I��=�ڽN/��}J�[��钷5,�j��.y��k���K�^��V�����^��z���nU-��%_��nUu��%[�l�U�B_@r5D��C_@2�G�[U��o y�˭�_�7�,5�p_{A_Ar�Ow�گ�+H���}�9}�O_��}N�A��_:�7�I_Br�[��7:�KHf6�d������3�طз����^�6M_Cr�N�����e��w����|б��IDr1E���}��ꇖ�H&�R�@�D�pHwz�9�*��[t�G:��"�۠+=�0}Iߗt��آ#�{F7z�.�2��t��Z�O#i�Gz�6��]�ϓܥ�#)�F�y�}I�]��:��H��8?f����j�n���I�^�e~\/}#I�/�2Oa�>��h���T�+I�v�*Og���������� ����5�R��]��ݡo%i飋<�u�X��Gt�gB_K�G�x6#��$_�5��*}0I�����Dl�%�]?}3I��t��@MR� ���g��t���I�Mxt����>��~O7xn�����.�};Am���襯'�.�?'O�� g��ϑ>y�Z��sF_P M�<w��o(�6�<���(�A�;/��%�9�:O�CJlu�9_�KJd :9��[J\%�O{c�>��4@���}N�g���3���tk����H�����o*q�Х0MUb�;b�>��7EW�}X m�n,�}����taѷ���t_!u�ו�/�}5H�W¹J��}` e�n+���%�Q����#K订X��,���UE�x��n*�&}j�N�}l�i��)*����]S\��{�7-:��:苋'�tJ��蛋�tI�n�����C����2D^\u� a�Ӌ�O�@}���E�� -}~9��gt=0�a�U�N��x�ԼD���.����lqH?���&�L2Z�S�٬��$d�~9�U�������[�kI�� rZ�ѭ$g�~9�i���я"�q��$I������Е$�k�a�#�?�I� �m�DM:�������y$n�~ 9�c���}G?��]F�ӏ$G����M����+t�h�O%�ڠ����X�����[B��23L?��䠏n!;]��M��%����g�|BW������0�@�z�3n��. g���l�~����h����W��Ш�N��K�J?�EO�W/�8�����~�b��跴e�~����i�$�څ�Ԍ�����_�(��\�[��мA�r����3��v�~ܢ���[�%�����~��]���P�~�e-X���D3��Qۣ_�4�W�7�c�~�L��i��M��˱D?�9���C����g/���+��&�������ҏ���K���6G��!��̻�n O�釓���Iw��9���'W���S�_L~v���!;��M��D&��_J>4Eg���7�+��f�4R�w���x�t)�O����!]H��藑�ӗÇ���W��ѿ�w��B�������t�Я!g3ؤ�I��_Bήo����/�W�s���a=�@?�8�0����o/�FtE��k��Ň�E���Z��ş�&�S<u���o�n*��i���_7�B[��7�P�g�ڹE�W��-�۾�L^�K��`��M%���to��]��)�����gI(��8��o��M��"Awlq�h��Elj�!p������ .�� ��$I��J����w��g:������p8w=/]�6��������>}ˇ\���EM���S -HGyS��ElU�%4Ó�K��J�I鴠1OF���Kڤc�FY��v�,��f�_k��P��[�sAw�p���}�T:_��h�t ��B[�C�k[˄�S@ך�IW�z���|G�����)��,�u��s��zR���M�-���C��������f���FӌûI�-7�ZHF����Q�x������������Wt��Q�jw�7�h,�����반K%b�``���[����i�z9��l�B_����������������������������������E��p�ϟ -endstream endobj 21 0 obj <</Length 660>>stream -r*�q*�q)�p,�p*�p*�p*�p)�p)�p)�p'�p&�oWW!o+�o*�o*�o*�o*�o*�o*�o)�o)�o(�o(�o(�o'�o%�n)�n)�n)�n(�n(�n'�n'�n%�n$�m(�m(�m(�m&�m"�l'�l'�l%�l!�k%�k%�k$�k#�k!�k �k �j$�j"�j"�j�j�i#�i!�i!�i �i �i�i�h"�h"�h �h�h�g!�g�g�g�g�g�g�g�f�f�f�f�f�f�f�f�f�f�f�f�f�f�f�f�e�e�e�e�c �b!�b�b�b�b�b�a�`�_�^�]�]�\�\�\�[�Z�X�W�U�T�S�P�O�N��M�L��K�J��J��I��G��F��E��D��D��B��@��?��>��=��<��:��8��8�7}�4y�4 -z�3u�0 -r�/ p�. m�- j�* f�)c�)a�&]�$[�"X�"W�!V� T� S�O�O�K����� -endstream endobj 13 0 obj <</Color[20224 32768 65535]/Dimmed false/Editable true/Preview true/Printed true/Title(Ebene 1)/Visible true>> endobj 7 0 obj <</BaseFont/SLTWMY+MyriadPro-Regular/Encoding/WinAnsiEncoding/FirstChar 97/FontDescriptor 22 0 R/LastChar 117/Subtype/Type1/Type/Font/Widths[482 569 0 0 0 0 0 0 234 0 0 236 834 555 549 0 0 327 0 0 551]>> endobj 5 0 obj <</BaseFont/ZYKMGK+Catamaran-Black/Encoding/WinAnsiEncoding/FirstChar 32/FontDescriptor 23 0 R/LastChar 119/Subtype/TrueType/Type/Font/Widths[220 243 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 547 619 0 711 0 0 0 0 0 0 0 0 0 0 509 0 457 583 533 0 579 620 282 0 567 302 0 610 0 0 0 395 451 456 0 0 812]>> endobj 6 0 obj <</BaseFont/ZYKMGK+Catamaran-Medium/Encoding/WinAnsiEncoding/FirstChar 32/FontDescriptor 24 0 R/LastChar 252/Subtype/TrueType/Type/Font/Widths[230 0 0 0 0 0 0 0 0 0 0 0 0 0 180 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 419 0 625 0 0 0 0 491 0 0 276 0 0 0 765 0 0 0 0 604 518 0 690 0 0 0 0 0 0 0 0 0 0 0 493 537 464 544 504 324 540 551 217 0 487 236 842 541 545 533 0 338 421 341 541 473 702 0 0 434 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 493 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 541]>> endobj 24 0 obj <</Ascent 1320/CapHeight 680/Descent -377/Flags 32/FontBBox[-544 -377 2532 1320]/FontFamily(Catamaran Medium)/FontFile2 25 0 R/FontName/ZYKMGK+Catamaran-Medium/FontStretch/Normal/FontWeight 500/ItalicAngle 0/StemV 92/Type/FontDescriptor/XHeight 502>> endobj 25 0 obj <</Filter/FlateDecode/Length 7534/Length1 13483>>stream -H��V xT��ϛ��B�����<B�B�4��Ù �2$���3�@ !�"�l��XA6���R{'E�Rm����! V��]���2=w������{��{ι���Y� �aC`lE���Q����$����p}Ӊ!I�� -�߯�7W\Z��8��>��~rݼ�E <_ɛdN���6��x�����)5����+ٿx -+�3:������R7�a��i@o>#q���U��c���l��� �6_*�����ẚ5+Jx��"c�P?s���V�*{����8�f��iشit �т�)k�t7���9��tF"�=��ÁO5��b�c�xB��Cm�:-�H7�d��o��t ;�~�{LD��)��ԓ*���uF��4�jh͡E�NsjG�״S�&}��������&}��S߭�����o�o�E��"�D�p�\Q(��R�G/�2\nW���[s;ܩ�twWw�;�]���kr����O�X,�<�bn�$�q�Q2eR6�)��R1 ��h�n�J3���u�3�w�}��Z_�o`|[��.�9�%��~4��H���!D����Kc|ݯ⫾���-v�vbG�}_�\z�|��\�N��=�z��;�7-N��������^8���Qk�ul����-�ZsZ��vh��qK����f��nIo������{O��.ah<��F~_���ëx�6�n�K/�>:@Mt��� :I�ҠiZ2�_�Z�u�Whn�� -�bk%�m�6^�S�=*��땧mōj��A���zs�%R*u������%�JD�����vJ㜧�h�Q\��P*��)���\�n4���+�;݁����~� -.B�Nc��S!�I ��4�옃���0 ����a �b"x���O�ܡ(��,�cv�\�SE#~��x�#<�~�q~G8ү�(��� ~�������l�B3ZЊ1g�����]�� ��x/�ux���`9~��V�0��o�1�j�̢r -b-fRYx��������4O�M��[xo�3� �����x�{���_���'�+��-�=����>~�?b>�n�a���ϐ���Ew��E��r�N��.����^��*�ͦ��)܉a�E��L��ꨞ���ͣ�t��4z��B��jz�6�N�F��3\�hm�������U�0-������2z����=NOѓ����Z�s4�Kʯ���Q��B� ߲����|���/vV�cgcYj~͏��w����)$&��e��i\�tTLʁN�g�jEdBPj9���UUF��咰$|Fi#��/��H2��z�f�j!�Ҟ;�1��|�*�t��.i˱�']�� -�j���D�J,KDۼ��2�U�gB*{��l -����L -C�ʖ�F�jTr�,�rJ*�,C"��,�������Y�/� �W:/�$�<�n�KTG*�BYbg��Z�_%m�.6�DDD��haB���r+hX.K��A�9���{d�)}�|;�#���58�7,��ZIU�B&�{d�)T�W�ώJ�v��C�r �ơv0u���|���w4��ER�.T�|�;$�#����*�R8���#\�v�~����������8�F=���v.+��)fT���:\ꑝLvB&�F��<0��LQ�r����#Sy����@�+;�B"��#;�e�Q{u��K�� �f���U�)�.�g���f�� �hj�OR�+ST�r%{����I�� [N U�c���W��2xٕ��ͮ�p+(��LF2����>U7I`��/38Z>�a��[�U��w�����W���ŗdp�yE��>-����덄�������0uan�Ռ���g%o1�6%��Q����h��Yfԡ�ӌ&*�ÌvP�kf���}L�Jܥ#�6D_Iw����v�̫�YmƂv�ܫ��m�l2����ד�e3.���t1?%��OI��)ً�)�����e~J�f~J�1?%MS�/S��Ǧ���s��Sɭg�Z�kJO��p��)n�E#\b��K=��}���R��M��� _d�`�����\d�Aq�؏�7�����+=2��%J�%�"�� ��0�/�].�/�U��U�Ϲ��N�|���#���&v�6����'N��NS�i7;%u�6��`mG7`+�R $��������~hB�?�R� Cb?�iAAqy�s���t����9�<��}ޏs=b�� _v�H|�b¼�P"T�ʇ����z���'�� � Iw�Ey 'h -�fDЌ�|l�zB���q��ÔP�<�P0f�X���;X�)��P����)j�-�ɺ`�3(�����L��|7��� ݐ�+'�,�W����ݳ����S� 3��-8�1��f�T�%2��_��D�("����U~rb���>� [��A�B��%�E�������ݐ���f���PO.YoM�Ȯf����_w3x�Kk+R:��S�,�7C�L������^4��8c8��J�Of([K�Hp7 -t��C��C����}Ҏɸ�;�����X�:l���S�JE@F;��e��fL(-uKuf�:���'�I������[�ZTVGڔ!�e�>��ݱ�3��º���Ɇ�8�1��e-a�B=�~�|�q�f��Kq#�?e�m��-m*5�6ʀ��o�Y�p�or1S3���q,��a�8���7 �4��Mn�U�̹�8�E� t�x}�x-ѝy�ct'�:�I`��$�B��U�8N'�C`M�5tR�E蔰��ia�3�.Bg�]��v�����9h�g#������� '��@��Q�/��8ϛ�8�[���<�q�%1;^0!��� �~�X�MH��LH�/���8�+b$�_5!�_6!ѯ`�ExńD�� ��*�Om�wU���&$�7LH�ob�Eẍ́D�fB"|+~�U���S1�e͐*�������UN��d�`-�l -gҎ���U�V��߯��碌������I}������)�.s�6���l�dG�����B��^���8�3��T�$�e����rne�jsќ��:km�qU�9�1Γ��/�5]�mرi�)��ʿ{Nzc�z��N�G��|�g,"�~KZY�G*2SZ�w˲K��^X���E,����9zb\O��jRM���{;�]����~i��w2�6�n���vL�e )�⸁�|��rm�LVe�'�Tu��e2�<G�1�ig�;[�b�y`mV�2��c�d�k�U��RݱHV견�����:�����ol��h���k܇�t�!��źّ[-�+ͻ�L�- -_�s�M^��uq7�1�ȶ�&�Z��:�Ng�����NTKB�ь����̮��+^�ݮ���r-�~��O�9����]y���ʝr��<�;�;y������}�����������@�ˍ� i�̇�$�e��ZG@{(��m�d�Ƽ�y�v{$�Rior4�N�&�Ԯtr���D�z���q{����q����c�J|��=G�N�DK��ַO�������|a�Xȫ���P߶��XBS�4�2:ZHm�:�34��s�����*��^�t�"KU���bR�W7M�Di����9|�AX(�G�+Sje�r�ryreq~n �./NN](�[�ÇO?���o�1��?b=l��V6<dA7BL -r���Kv�xtR�dͤ� ��Rg��.3�]�˨e���C@1�,&HH=2� ��@��=�á�/��Ap���d���ݘJ�.\yz!2h�'3�g������S��0�����¾���/&�Gz\s�]��G�#� �-� �ˮ� �:P�R�/)r��K��4xh�7�k�$�,�m��]�sEau�c�k�%d��ZoN�4D�W��� �u��1��ef��f<�t�E���;To�>�'{~�'��ˁ�;?W���?�]�/??���Jw�����X��*�z�0�� <���X"kG������D�R���o�:L��s�Ȁ(��q�� -�p�١�?0�<.=�?W�_�q�̗V��;��X�����Ao���|�cGsn�+RO�$+��Xl�5%�"9�6Qy�f��oZ���:�sމD��*:�� -��]azJ&����тEIH��/O�G}�d(?'��>���b:��_�p����nW�Ą�{�э�&s;:�.��R�٘,�d�=3�n�ۦij�k ����"�����6�`�O���=S8��ڕj5�����j��d�v�� /]O�tk� -/�{�BX��������:�������~��ET�Vv�%zӬ�d`H�]۠6�U�c�ڀ�'�Vk�� �ZY�����}��~���c�g�P���g�����ȖRk�3ʼn���_h�����f|}An��~�ns�����| ��^hEẙUk��N݁��]n���A�%d»�3����=��Q�Ɋ�����j9O(�X(� F�O_4hE�pժ،(�T����H���#�N��}�����s�=3��A킕��i����Cy��6u���;�1�_�q �c'q($dΓ6�&!!���$&�^e]�Ү��֭Hc��6���Z@h�V�:��M���V���c�nPP`�(�]��ιױC�|��9�;��~��4T7����,l|�]dXdq �Jfr`v� ;��楺D��Y+P5��Ph]�]���"3�LQ� -If -���"�E��i��[�ݛ��Db�d՚�|s|E�776ϋ� ��>WI��a����5��)��r�V6J(#��1��d�sF�3F �"'r���t��Q� -j"3*Qz���+*��H$_'Z��`Tb�$_H�6�s�)�p�ԑ�g�Y�Q�hFT�U�V#���{잽�v=��ﴼ�|��2W3q��Lu��^�Qq�'7��i�"� -��� ���rV�b��o"��% -�g��������tn̶�<�����!2�n3i$3�:�Ɖ�z�Q_ԛ�l��Ι��v��dtn�JFS�K�{��ͯS�|s���vCK,��5zR�|!���*Zc~_��x�7�p���F���+�gf��dj\�&C��9�*��1۫X�y�M�c`����HM4��_7�'yed^sc� N���&�`#�����p6U">�QW��-�P{�v�TWE��W�pN����ő0(��~yY��G�?p��{���߾�Db�g�������u8�-]ɷp�� v�*�s�'���l��cVp���*�Wsϟ�$2u_����ֻ�X��b�wg1�v�[L�ȣ7�$�|���;���³�DH��t�8�CG�$`���=�"3&������ϴJ�eA����H��2��JV��#���$/���+�f� ���Ń|vC��bȩ\������-�bUU��ާ��x�����}��z�TU��h�t$2���M�- �t�?���%Q�{�^��m�!�)o�>�J_���Q$�5 j`��r��F.V1m�F�RidV�~I�0�1g�7�l�����ю$[�1�ߘQ���%;xɊ.}�+XZ�<�;�Ou����?+���&�$#UK��-)?�;�6�~j�V����4s�iE���i�Qu˻E�(��Wg:�N�ܜ:��L�|\k�4�"=�(�a6��9U�$2��)W<c��N�Q�<2L�{q��{9pt��Ϯ=�8�����g�C�!9�#1� ϦYP�.�O�R(�5"�&�(��+ ��������]=2R���q�^�(���� �6����������\$5�W����a��#|n>��k&�P�F�<�+=%c�&nQ�qK���@=�c��ϧ��]L�p������R?����b�+����f۰$�RU�&Ѫ -k�W94����s�k6Z��G�9�Uee8���ي�_�Y���mgG�H^�{Tes��dP?�������A��Ū�먐Z�e`ɼ$-�$s��X� �:�&��w�r�a�jKi�^Ur(��<�8�_��)�*�R��9iFG/�ܤN9"x�������ͣ=yξ��d��%�x��t�����n0x|�+��[�F�~��%������\�7��K�П[N>/۷n�%�Υ�矷V�qSR�*K��y��kS����ϫ}r~��"����&��K� � ۩�a khX,�j�m��� -� ��F_���4 �N�o5۵hm��!Ҁ�Sۂ�Z�h �?�"H��' -�0��6��\b�c��&Qސ��W��U���Q��ǃ�ʯP�����Zx{����}>HE��D0����GA��(F�&���7X�}�赀V"��L��������7�J�� -��֔����N�i/y����.�(�%e7@��o�'���CN��6�A�Ꚅ`�f��{C�1S����Sz�ӗ��ѷ�@�ҹf��T���i{ȵ�{�=k'�ȬE+m�v�*lþ�iO�k0�&ӿ������oC'���el�.P69HNe���m��VM{��iײE�#�D��-� -�Y@��v�΅�C6��{ߢ�8�Z��c��c��!|�a�>�G��GЎb�1����(}C��ư���/� � �Ⱦ���}��Hl}t&�}���'���}WA������@�_{m�[h�h���G���C���Hn��ĕ`��mS[p����sD'���9��珁��0��h���¹1�= ���� ��`L��OA[ -{[�Y�L��FB�?�Ҿ�mʘd�+�Wi�R��& -Y���F�� ��#T�m������@�2��q��j���M� \�a��)n�����z��Њwp�a��+��v�{�盠���m;�,e��U�L��r��0x��y8WsP��m�H�Nb�U��b�^�5����r���c�)�" [�(n�R�����K��kԬU�}?lƑ�a?�9c�O�ľ��b��)�� r����t?;���d�ؗ�J�S�Xӄ5-�Sf� ����N�>��=����>��ed`�|�����`�qj���w�/S�����A��0u��+X� ,�J��'Q��gt���z������m�ϥ�#�����/s��0�{d2��"^XhBDE�� qE04Zd�D4`4� -A�(he#�#����(X�� C|��JA�}a��o�9�����S+i�zX�k�y�8� -C���ǂ{��.g䱫�OT�� -�������������D��3�y"��P���༱��������6�Y'��,HC�7w`S>+�>��������U�u��Q��\4���dt�ڴ -ֆ�K-��[���1�u�á籌l2eH<�1Ҭk�Hi���,},�!����c��{ �����l�@�c�^Y�Y� a"���F9��[��W+Ʋ%����i�2i�-�Zݟ�ּ�M���ѓ��S:c+ʩYQn$C�l�F0q9��U��U�l���8&:�ˡĝ�չ B��'�8�q/���{��\�/gl-g��N�V�sy��م��b��-Rm�\+�Ê~b�0.9�x`�9�T' -endstream endobj 23 0 obj <</Ascent 1320/CapHeight 680/Descent -395/Flags 32/FontBBox[-662 -395 2698 1320]/FontFamily(Catamaran Black)/FontFile2 26 0 R/FontName/ZYKMGK+Catamaran-Black/FontStretch/Normal/FontWeight 900/ItalicAngle 0/StemV 180/Type/FontDescriptor/XHeight 500>> endobj 26 0 obj <</Filter/FlateDecode/Length 6256/Length1 11470>>stream -H��V tT���7���B!cÝy$���Ұ��3ABe�@;��d��H ,b@EeFED��(��T�N�Pl@ -hmk]�`��u����NX���ӗ�������}�r' ���6��T�/����0��pC������ ��Ϟ%0��@B�'�7Nl�];���%�H��)s�7:w�?k��T�MZ�9����ؐ����݃�>�f5��������L���zS��چPS�͛ξ�$�SC u��K�^Ș>m�6sV�z��f��7Ψk��G�9Y/e�ش�m!�-��R�)�F���:�q��Dt��VU5�_j6�#�����xH��}m��-H�7���Ž�rg2y���� ^c<���y�HYd��d�����0I?� TG7�L�G+4��O{E;bk��}��J_�����O����e�U�u�-��*z�\�'��@���\<%v�]�,��Q���Ԝ �tg�3ۙ��s��#�Ag]����'_�R��!Oe���1!܆ �B9�GN*�~TJ��jM~��&�Tjb|=����m�7__�/�W��:}��Iߢ�������} RD��)."�o�%�e0�^��՞�G���q����ލ�N�|��t?'���8paԉ�'Z��SG3�?k�w�W���f����>�}H���#��mړ��~�ַ���{�-�-��dk[��w��mE��X��5|o����x���f�J�h;���C��c4MK��S� �'�Bsj�V�q�j�Z�6X��ծׂ�����z����b3:(� -�]��H�ԝ�_��)��42��5�ѵ��5ϤQ< #�?/�T�8�������Ѹ�|ܩ��:|B�`��up *�)��4��b�\���@��L��-��[ф0n牸��K���5X�Ux?� ]ME����6�I��;q�h�o��r��`'^��9ϻ��3� -��&��?�����_�/f�Zцv�����{�w���<u;p��<<�;��h�B��"���x ��X��TI?��8�e8�0��(��F�d�A��:4������x���\�7�(�B=�b-ަq4����Ca=��3�����x��߰ �f��)�O㟐��~B��:��ըD=Mw� <uuTOw�PCa�͠Y�I<�!�N��4����n�)t7ͦ[�&�ӛ�N�CsyZk�z��� �8=�ݹ���zZC[�^ZLZJi-�{h>-���>ZFу�0��U����8NC����翕X��Bm�v�Ǎ>��m��\��Nv���U��8��Fܱ�q���YBTlGZe�L���� -ȸ���C;���pب�;���(o��=nI��z��LQ+d�_��o.�d�/� ��C�� ��GB��lnم,Soe�%��ѡZYȦ3����_�"[��h"!!��� [��%��R�V�-˲KrY�!��Y�[�L����Y����G&�aI -�e�i0.Q����Q���SjA_Xڊ�����|&Y���*+`XK������3��e�)��f>b�J`���q��ZM��0���En�h -U���ǡF���B��1�Ifs���S�8��n慵H�\�\�˼��1B�.�|��r*��A�E��1B�[��\��`?O��G)f�P��l�b� �U�p�T3�i>Y*w�4���)�Q�s~1<�LUZk���e:/�=���2����i�4��nV� D�j˭>2��hr���2PQ�i�;؞�g�Q�{����^I!�Lw���N�DS�#��r��| ���l=��ڶ�a�gg��~� ���X�d���Ku�F�3����̿�Ze�|�j���nx�O��|�7�Gy�g32��=�H0����K]v'��s�r�e�%%s8�J^fFmJ�4�qJ�2��J��%�f4Q���h��?0�ݔ�kg�.��aC��t��,���9���tuq�s��t晐����_o�Ǹ�S����t2?% �d�d>�S���)y�S���)i�bh�M�&o�^�m�+%���z��)�.��)��0B\��F��P'�wF���s��ٿ(Oپ�d���]3s���cx��8�]� O�n������(f�EK([�� `����"�?֫5����s>_��;�۟/�bs1�B���\�i��²dk�&MG�&˲.LڴI�1i�S�G���M+�?�I�4M�1iZ����L�*���A��糹���4!�s�y�s��r����Q�$��\G��˨���@�"&� $�t��ˣkk��8J�Z*+�A7�N\ڃ�*5�f@ՌHZ��H|i-��5��H�{��3PZ���?1�.J��.�Jsq� -i j�&��������@�L�bd�VRF/`Y�,z�����f�P1S� c��)��C4�dP%#2���]�#Y�"�{Z/�;g!�}U?� _2F+~�rpQ��R�F��i�t(E/��>2��avz&ȡ���d�tUC`�`ttww׃wPZW"�Qn�R2R �=��\ � -E��8Vj����er�䯒܁:�g��wz�j��o����RO�Y'J��5h���QO�"��R_�H�)7�1���pY��g��GPw�B���!���_yKVP��i�B�2$X�hEm�W�0�Qo<�U<Q�d��q��/8����d)��<���؎;��d��ş�m���V=5��.��%�X��� O<�rf@Μ �(�I�8E��C�LbU�y��@\��$ֹ>7���� �O��'�9�I4Gg����3 �ә�L���*q�D�C`I�zY�"�,uZ��}M�"tQ�"�u���%���e��o;�ߐ�� �7u8� -9]��]A�p��8�$�W8�����]�ˑ����tH��اBx]�D���� -nn{�7�H�o��uH�7�e�pG�D��C"|܁���ɑ�G�D��C�_Vo���!$�D��:/�,�����j��{�<������̬f�h�Lt��jP5��z�����ģ� �huk�������Ul�8K��N�E�C(<Ι�L"�b� �T���##��TG�#h0{�ݮTWww&��b�h&ݝ�r��Ѩ2�����v�������2'W��O�í�7�����:��:��:���ޜ=���K�;�n��Wg[m�w{2�3m6wS�#�m|����o�E��7�Q�^l��d�Ĕ���$��)?bz��ߖ?��p�o��U��@<d&֜w����d'6`�ƦT�0��v�=�5���2�|�m�ж� ����?���17�� -%��L������7�0&��'Lu�*�&n\Ɖ�yf0� -�d� -�ELy��b�YL����PD�i����ۤ�ܞ�ʺSj��`v��S)'E �ƨ�9h�����@gzfe�������'��`�'T���f�x��%���O\I�� -��l�Pnx���!�Zdr��>�<��[�T8 ��Y% �&f�*��ﯼps�G7WS�DS��7��ޒ� ޘ�y��[VK�s]��-�N�Y�u�� �(�a0*p�2Y(�����^`F���l^�XzuK��tN8�/p�JĪe�|`g��2��P��b��1�ˮB�5��f�;�Um��AN�P|��r�� ����7B��ɸ���֒����l�s������2�[_��V�� ka�|g#�`E(�$32E�%i����B�pT3��������Ed�n��&xf�0�ڑ�{g���S�юl-�m�1����ﱫG�n����w�����&��YX��ˢ�O�R=�n�F��"LF�3��4#.�X)���^eތ�� -5���X!���X��5s��Q�|�v�0��Q1o�s�|�\� ��/29|� -��ыe��2��PfE%��say�x�Z{61�g#�t��sEF4�٪\̶x︬����������m���R!D���z�q5��;���n&���Fi�ؓ�������I��s�\Oe�5����DW��D�HS��߬�;��:SN7�y��������>ղ��x��v�H�b��`��ŊR���KY�(qWl -) bى�Ʃ�.�Y�;��U��VW�X����P�cj�&��.��:���������ΕE����m~�?��V���_-�l�Rc67Q��������Y/��������,0��,N��"�A�U�13:���_�����Ϛ6�s��O��e} - -��U̎8qn��s�d#���!��q*:�WT.H�ae�X߽6���[-��ة�m�T\<(���/۰��oö��I�(�e�{�s��9CUֻ�M���榸�[VUo?昦����k�y�33�..���E 6m����Y�сr~�oN�k��x�.CYObXD�Wn��2W�L+s�t���U��C��[φ#�Y-�b��x�?{��|t�\�l�D�Pg�!��*^=�=������M�G�w^=H��O���N���/��>��yȟ�,Ǘ�.N9�[�;s,Wh�i4��5�_kq���Mz�*���k�F���e�1��)+�m@��d LW]���{��WkLTG>3��A`yje��"P��a���f��`%���C)�ƶ��Q#U�F�Q�?ڴZ�j�"�5�����J�a�����Ħ���~s�]�Dil�����9s���3�̝�$c)6*99"ʄ�`.S�=:뾓���r�1���Q�I� ��"����w�xni��q Qv����IJbw(���;�l�d��E,�\ma�������� �*wC)�a�7pR�]���[��X��㎼��4����.�ɢ������wũbC�O�� ��à�\�5��e�����y� T��p9,%Rl��Q�H�6�YM���.��<�d�C7]Gv������[{�/9�J��62|:�{�EȞ������:�6�UF��y���y�JyɲM�g����Nq3�ӫ�T�\v�2J�?��o!_L6~ ���ZQ$C����l���ho�e7��~P����3@0�,l�&�9�f�� -i��d���/�^{���/>z����ˁh/�\�Ƕ�4�İS�+�>��R�t`*�8Af~z��_��G�<KU��/�F�nMg�`�,��:��nݶ�`'�т���Q��<]��.$�H���k&��w̄O��'�T��!lV1AσX��X� ��9��X�hu���n��x`���5W��0u���B�#91��yr�XH��#��^!M��5*��>�[�ρs�����n�{�#8���ܮ�Py�lun����|ՠy-$�Gj(Wps����E��l�uho/�^��CJ�M�o�����݇[���_@>@~���T6���m6V�w����*+�^��C�X?���s�Q�۸ u1��0V��6�� ->�J6�z�p���A��Ih2Q�V�@>`����#����=��5g�m�����U��}�]�8l� -�a �Üy%�Ź�/�'ɨ����6�7E�I����T��O���8/ �����٥�W3O��XC��� �(^�"�y�ԋ1��S�GN�}���B;�M�CN��8l�'?K>S!���P���H�!#1���HE�^��ME�Ļ�=/���8��r����$*�_C:�O'�[?�+���=ȅ�+L͉S�R�������F�πvs*�� ������-���rZ�)MZ��1���hƻ�B��Xy�beXk��l%�} -x��ؕ�%��aف�����D��S |B�ʟa���fx�Enѯ^�o���r��9�y��\t��x��ĄB�C�i��/a����6�huC1晋|�t!��)ȸ�P�hJ[���(+�M���+�H�(g�AiP^��=�UC�Z�O�cT>zR�"n��,�q,_)N�4��cǶ���6��P�~�(F�%㑡��Z��CPk�]g�~���膮K��LiYӍ�}�J���x����k�����5���G�R!����潊^����&�j&M�lh>��߄^?��J�9���2Tۃ���w-�UT� -�YKO�2*%/ͧ9�?3�ct��E�#;5�*��@��z�#E�_����Fً|=���S�J�k*mc�f2SE];� -eN�u��a7���Y�Ӎ�FU)�K ��6�����jyړY���ƒv���>YԎ�7����K�K)��s����aʖ6��vN�C�J#y<�0�(�� -endstream endobj 22 0 obj <</Ascent 952/CapHeight 674/CharSet(/a/b/i/l/m/n/o/r/u)/Descent -250/Flags 32/FontBBox[-157 -250 1126 952]/FontFamily(Myriad Pro)/FontFile3 27 0 R/FontName/SLTWMY+MyriadPro-Regular/FontStretch/Normal/FontWeight 400/ItalicAngle 0/StemV 88/Type/FontDescriptor/XHeight 484>> endobj 27 0 obj <</Filter/FlateDecode/Length 1089/Subtype/Type1C>>stream -H�|QkLe�vV(�ʸ�;ff�� -�<"6��vi��`y-�.�KA�����X��2�jI��)�%d�4�BD�XD�����j1M� |�pc�/���so�9��.� (�>��)x3�pGn=k���Yc�A���J�JCR P�� ��P%�����y|���CP��ս�Zq���L�%'�GJ���I�t|ll�&�z�����&3Sm�3ktF���ǰ>�Ψ���$L4˘�"5� DL4c0�3,��eq�e�����j [I��h��XцZԢ�$�6�M���Lj*�-�����St�~uA}-C��L)��ȫ(�� ���A#H��H.r �C����P0��o��<���{[��z��N8������A^S�l��ob%[5H�"DŽI�TA�)h�� -h���a��@��($���7:���{ekp{ށ���ȏ�z'�Ɖ?;��f9|�-"�p�>��?���'��x&O�����8��8Nj���r���@?��Q -�}��i���� ��x#x!ww,˜X��T�"��@^l�u�i��CU�7-�pE\y*��|�9rᣩѯ�;��/S�������Q�;�uv�=�f��as�}ɏ�;�[¦M���֓��fSUu�i�Ro�g-Ԁ���T��=��"+��z���E �p�N�wv1�T�@�����Xw{���k���0�N����_p^���+ݗ�����)���r�9�]\y����6�,���pش�p$�H)9R\K90�}jI��9um�B�� ���J�~`��7�<�%yvt�����b�bV?��19�1�z����Dr>~rY�����>/�̣ÿ�����pB U�E�]0jmx -�Ǘ�k`{��(.S�`4"�9�I;����}@)`�%v�n�T:>� � :�$]�<9ϻ&��lw��ϯ�����5���Yu�)���]zSI[�A���{��Q���я��`S&@��h8�D���|"�8�J��8$��4?��^�� َ�ө�%�1����x�ƃh���x��7x9٣};4�5t�B���#�Q����S�-���� -endstream endobj 12 0 obj <</AIS false/BM/Normal/CA 1.0/OP false/OPM 1/SA true/SMask/None/Type/ExtGState/ca 1.0/op false>> endobj 28 0 obj <</CreationDate(D:20230822015855+02'00')/Creator(Adobe Illustrator 26.0 \(Windows\))/ModDate(D:20230822015855+02'00')/Producer(Adobe PDF library 16.03)/Title(Digitaler Flyer_mBus)>> endobj xref -0 29 -0000000000 65535 f -0000000016 00000 n -0000000076 00000 n -0000058299 00000 n -0000000000 00000 f -0000128509 00000 n -0000128883 00000 n -0000128290 00000 n -0000058350 00000 n -0000058836 00000 n -0000064042 00000 n -0000121120 00000 n -0000145510 00000 n -0000128163 00000 n -0000065809 00000 n -0000076566 00000 n -0000064108 00000 n -0000065247 00000 n -0000065295 00000 n -0000121169 00000 n -0000114916 00000 n -0000127452 00000 n -0000144047 00000 n -0000137440 00000 n -0000129554 00000 n -0000129821 00000 n -0000137706 00000 n -0000144335 00000 n -0000145623 00000 n -trailer -<</Size 29/Root 1 0 R/Info 28 0 R/ID[<3A18F023207E8D46B81BBEC0D2EFC246><CC95ED43BA54FF449740F3A4742D860F>]>> -startxref -145821 -%%EOF diff --git a/static/flyer/Uni.Urban.Mobil._hq.jpg b/static/flyer/Uni.Urban.Mobil._hq.jpg deleted file mode 100644 index e80649885c9b7bb9a0f133f61fa306fcebd8097c..0000000000000000000000000000000000000000 Binary files a/static/flyer/Uni.Urban.Mobil._hq.jpg and /dev/null differ diff --git a/static/flyer/Uni.Urban.Mobil._lq.jpg b/static/flyer/Uni.Urban.Mobil._lq.jpg deleted file mode 100644 index e9bf2dd5fca3db5d6274546e895a5b9383df4ff8..0000000000000000000000000000000000000000 Binary files a/static/flyer/Uni.Urban.Mobil._lq.jpg and /dev/null differ diff --git a/static/flyer/data.json b/static/flyer/data.json index c1c3c2f723c5641440cd8aa1b1cb413b3822e50f..535b6966fdf5a99736d3bf7de3f43200d285c121 100644 --- a/static/flyer/data.json +++ b/static/flyer/data.json @@ -1 +1 @@ -["AIX-eSports","Alnatura_Studi-Tag","HOO Flyer Code Start","IT4Kids_Mitmachen","Nachhaltigkeit an der RWTH_Information_23","RWTH_Schreibzentrum","Semesterbroschuere_Zentrale_Studienberatung","Studentische_Unternehmensberatung","Studentischer_Besuchsdienst","Uni.Urban.Mobil.","Velocity_Aachen","Zentrale-Studienberatung","jDPG-Schlag-den-Prof"] \ No newline at end of file +["Alnatura_Studi-Tag","IT4Kids_Mitmachen","Nachhaltigkeit an der RWTH_Information_23","RWTH_Schreibzentrum","Semesterbroschuere_Zentrale_Studienberatung","Studentische_Unternehmensberatung","Studentischer_Besuchsdienst","Velocity_Aachen","Zentrale-Studienberatung","jDPG-Schlag-den-Prof"] \ No newline at end of file diff --git a/static/stundenplaene/stundenplan-informatik-dark.png b/static/stundenplaene/stundenplan-informatik-dark.png deleted file mode 100644 index eed8070d2a0973809bfe5ba4cd9d825ce68a114b..0000000000000000000000000000000000000000 Binary files a/static/stundenplaene/stundenplan-informatik-dark.png and /dev/null differ diff --git a/static/stundenplaene/stundenplan-informatik.pdf b/static/stundenplaene/stundenplan-informatik.pdf deleted file mode 100644 index 5f93fa1bb1f43c999ce0b695d0ac39c273a21a94..0000000000000000000000000000000000000000 Binary files a/static/stundenplaene/stundenplan-informatik.pdf and /dev/null differ diff --git a/static/stundenplaene/stundenplan-informatik.png b/static/stundenplaene/stundenplan-informatik.png deleted file mode 100644 index b3478937b41ea3ba4d209618f00965282c5af70f..0000000000000000000000000000000000000000 Binary files a/static/stundenplaene/stundenplan-informatik.png and /dev/null differ diff --git a/static/stundenplaene/stundenplan-lehramt-dark.png b/static/stundenplaene/stundenplan-lehramt-dark.png deleted file mode 100644 index 527291f6b845c89d6269c899494d6ccf24072ecc..0000000000000000000000000000000000000000 Binary files a/static/stundenplaene/stundenplan-lehramt-dark.png and /dev/null differ diff --git a/static/stundenplaene/stundenplan-lehramt.pdf b/static/stundenplaene/stundenplan-lehramt.pdf deleted file mode 100644 index 1dc2e9c5943188445545d8a5d829b82d1fae4207..0000000000000000000000000000000000000000 Binary files a/static/stundenplaene/stundenplan-lehramt.pdf and /dev/null differ diff --git a/static/stundenplaene/stundenplan-lehramt.png b/static/stundenplaene/stundenplan-lehramt.png deleted file mode 100644 index 0a4a919f8ebd1f9d3d973c31bdf85a7bfa089665..0000000000000000000000000000000000000000 Binary files a/static/stundenplaene/stundenplan-lehramt.png and /dev/null differ diff --git a/static/stundenplaene/stundenplan-master-deutsch-dark.png b/static/stundenplaene/stundenplan-master-deutsch-dark.png deleted file mode 100644 index 3ed84bec4ee03f6831680089c18c9c1e6b494f3e..0000000000000000000000000000000000000000 Binary files a/static/stundenplaene/stundenplan-master-deutsch-dark.png and /dev/null differ diff --git a/static/stundenplaene/stundenplan-master-deutsch.pdf b/static/stundenplaene/stundenplan-master-deutsch.pdf deleted file mode 100644 index 9bb1514b5031efbe2c2f00614b0c0ae2d1d25a0d..0000000000000000000000000000000000000000 Binary files a/static/stundenplaene/stundenplan-master-deutsch.pdf and /dev/null differ diff --git a/static/stundenplaene/stundenplan-master-deutsch.png b/static/stundenplaene/stundenplan-master-deutsch.png deleted file mode 100644 index 53dc990e7de815299d7fecd6d578c25cdb88672b..0000000000000000000000000000000000000000 Binary files a/static/stundenplaene/stundenplan-master-deutsch.png and /dev/null differ diff --git a/static/stundenplaene/stundenplan-master-englisch-dark.png b/static/stundenplaene/stundenplan-master-englisch-dark.png deleted file mode 100644 index 56552b3dcfc628009da0c302aa1439266ae62588..0000000000000000000000000000000000000000 Binary files a/static/stundenplaene/stundenplan-master-englisch-dark.png and /dev/null differ diff --git a/static/stundenplaene/stundenplan-master-englisch.pdf b/static/stundenplaene/stundenplan-master-englisch.pdf deleted file mode 100644 index 7199c9c485a93e2e79745cf88119ed55fac25a37..0000000000000000000000000000000000000000 Binary files a/static/stundenplaene/stundenplan-master-englisch.pdf and /dev/null differ diff --git a/static/stundenplaene/stundenplan-master-englisch.png b/static/stundenplaene/stundenplan-master-englisch.png deleted file mode 100644 index 747bfb297199b55cb973745d86767a9f4110eaa1..0000000000000000000000000000000000000000 Binary files a/static/stundenplaene/stundenplan-master-englisch.png and /dev/null differ diff --git a/static/stundenplaene/stundenplan-mathematik-dark.png b/static/stundenplaene/stundenplan-mathematik-dark.png deleted file mode 100644 index 5283ad902f2a6618718352e01fb60b9c56ac3a54..0000000000000000000000000000000000000000 Binary files a/static/stundenplaene/stundenplan-mathematik-dark.png and /dev/null differ diff --git a/static/stundenplaene/stundenplan-mathematik.pdf b/static/stundenplaene/stundenplan-mathematik.pdf deleted file mode 100644 index b1c8d2afc22d55bb5f8335863ae112bf8f8cd0fb..0000000000000000000000000000000000000000 Binary files a/static/stundenplaene/stundenplan-mathematik.pdf and /dev/null differ diff --git a/static/stundenplaene/stundenplan-mathematik.png b/static/stundenplaene/stundenplan-mathematik.png deleted file mode 100644 index f76d4da4adc23f46264faff63c904ac6feb4e4f3..0000000000000000000000000000000000000000 Binary files a/static/stundenplaene/stundenplan-mathematik.png and /dev/null differ diff --git a/static/stundenplaene/stundenplan-physik-dark.png b/static/stundenplaene/stundenplan-physik-dark.png deleted file mode 100644 index 31ae994e1567d92e8cfafc203e102a0f74a9d431..0000000000000000000000000000000000000000 Binary files a/static/stundenplaene/stundenplan-physik-dark.png and /dev/null differ diff --git a/static/stundenplaene/stundenplan-physik.pdf b/static/stundenplaene/stundenplan-physik.pdf deleted file mode 100644 index 87bf1c0c094d886195282f7e0b994d16ed3aff19..0000000000000000000000000000000000000000 Binary files a/static/stundenplaene/stundenplan-physik.pdf and /dev/null differ diff --git a/static/stundenplaene/stundenplan-physik.png b/static/stundenplaene/stundenplan-physik.png deleted file mode 100644 index 2d65a3909a09229164de22d69d205d0c01c557b9..0000000000000000000000000000000000000000 Binary files a/static/stundenplaene/stundenplan-physik.png and /dev/null differ diff --git a/static/stundenplaene/stundenplan-wirtschaftsmathematik-dark.png b/static/stundenplaene/stundenplan-wirtschaftsmathematik-dark.png deleted file mode 100644 index eb198cca6fed7f7aa28808d1b7611014dc711d81..0000000000000000000000000000000000000000 Binary files a/static/stundenplaene/stundenplan-wirtschaftsmathematik-dark.png and /dev/null differ diff --git a/static/stundenplaene/stundenplan-wirtschaftsmathematik.pdf b/static/stundenplaene/stundenplan-wirtschaftsmathematik.pdf deleted file mode 100644 index 4ea08a672f595b58b449a78d413b6385b33e02f5..0000000000000000000000000000000000000000 Binary files a/static/stundenplaene/stundenplan-wirtschaftsmathematik.pdf and /dev/null differ diff --git a/static/stundenplaene/stundenplan-wirtschaftsmathematik.png b/static/stundenplaene/stundenplan-wirtschaftsmathematik.png deleted file mode 100644 index 566f3a0f67303c6a8611b67dd995ee52128975ed..0000000000000000000000000000000000000000 Binary files a/static/stundenplaene/stundenplan-wirtschaftsmathematik.png and /dev/null differ diff --git a/static/stundenplaene_/stundenplan-informatik-dark.webp b/static/stundenplaene_/stundenplan-informatik-dark.webp deleted file mode 100644 index 3d51358cd0bec0b9c2e2d4a6fed35f392ccafe18..0000000000000000000000000000000000000000 Binary files a/static/stundenplaene_/stundenplan-informatik-dark.webp and /dev/null differ diff --git a/static/stundenplaene_/stundenplan-informatik.pdf b/static/stundenplaene_/stundenplan-informatik.pdf deleted file mode 100644 index 61a1a77e4b9b56d1e9f13ccdc95e22bec32b9126..0000000000000000000000000000000000000000 Binary files a/static/stundenplaene_/stundenplan-informatik.pdf and /dev/null differ diff --git a/static/stundenplaene_/stundenplan-informatik.webp b/static/stundenplaene_/stundenplan-informatik.webp deleted file mode 100644 index c5d82e02da6f77dd9ec2b5ce297bfb8bffde88bb..0000000000000000000000000000000000000000 Binary files a/static/stundenplaene_/stundenplan-informatik.webp and /dev/null differ diff --git a/static/stundenplaene_/stundenplan-lehramt-dark.webp b/static/stundenplaene_/stundenplan-lehramt-dark.webp deleted file mode 100644 index fc425d6e7b421a02a7bb236269b8801047380435..0000000000000000000000000000000000000000 Binary files a/static/stundenplaene_/stundenplan-lehramt-dark.webp and /dev/null differ diff --git a/static/stundenplaene_/stundenplan-lehramt.pdf b/static/stundenplaene_/stundenplan-lehramt.pdf deleted file mode 100644 index 32c620f19595bad68b08e20600093ac6cc0da1b2..0000000000000000000000000000000000000000 Binary files a/static/stundenplaene_/stundenplan-lehramt.pdf and /dev/null differ diff --git a/static/stundenplaene_/stundenplan-lehramt.webp b/static/stundenplaene_/stundenplan-lehramt.webp deleted file mode 100644 index d0e40bf889cb9c93e2525dac1b3201345c3d76e2..0000000000000000000000000000000000000000 Binary files a/static/stundenplaene_/stundenplan-lehramt.webp and /dev/null differ diff --git a/static/stundenplaene_/stundenplan-master-deutsch-dark.webp b/static/stundenplaene_/stundenplan-master-deutsch-dark.webp deleted file mode 100644 index 0eb4d0eba1fc8c78da1a3fc9ffeb58385c74c25b..0000000000000000000000000000000000000000 Binary files a/static/stundenplaene_/stundenplan-master-deutsch-dark.webp and /dev/null differ diff --git a/static/stundenplaene_/stundenplan-master-deutsch.pdf b/static/stundenplaene_/stundenplan-master-deutsch.pdf deleted file mode 100644 index 21aefcc8f2597c9b10944e8c6b012fc998ed3808..0000000000000000000000000000000000000000 Binary files a/static/stundenplaene_/stundenplan-master-deutsch.pdf and /dev/null differ diff --git a/static/stundenplaene_/stundenplan-master-deutsch.webp b/static/stundenplaene_/stundenplan-master-deutsch.webp deleted file mode 100644 index 7e289193f9ba03bd89207b809bb0e83bc37b9c62..0000000000000000000000000000000000000000 Binary files a/static/stundenplaene_/stundenplan-master-deutsch.webp and /dev/null differ diff --git a/static/stundenplaene_/stundenplan-master-englisch-dark.webp b/static/stundenplaene_/stundenplan-master-englisch-dark.webp deleted file mode 100644 index c7243fafd01fe16b8508ba7829b9d5ac89722d77..0000000000000000000000000000000000000000 Binary files a/static/stundenplaene_/stundenplan-master-englisch-dark.webp and /dev/null differ diff --git a/static/stundenplaene_/stundenplan-master-englisch.pdf b/static/stundenplaene_/stundenplan-master-englisch.pdf deleted file mode 100644 index 7708ebcff71ce6b8c3fe2026570d39471a781e8b..0000000000000000000000000000000000000000 Binary files a/static/stundenplaene_/stundenplan-master-englisch.pdf and /dev/null differ diff --git a/static/stundenplaene_/stundenplan-master-englisch.webp b/static/stundenplaene_/stundenplan-master-englisch.webp deleted file mode 100644 index 6f6612a8a2fcf05fa425ac55bafc74b8998f377e..0000000000000000000000000000000000000000 Binary files a/static/stundenplaene_/stundenplan-master-englisch.webp and /dev/null differ diff --git a/static/stundenplaene_/stundenplan-mathematik-dark.webp b/static/stundenplaene_/stundenplan-mathematik-dark.webp deleted file mode 100644 index a2d43d34de1c3fe21e430ace473a34052347e9e8..0000000000000000000000000000000000000000 Binary files a/static/stundenplaene_/stundenplan-mathematik-dark.webp and /dev/null differ diff --git a/static/stundenplaene_/stundenplan-mathematik.pdf b/static/stundenplaene_/stundenplan-mathematik.pdf deleted file mode 100644 index 13348b53983917a85f219e8e3e61c96a250477b3..0000000000000000000000000000000000000000 Binary files a/static/stundenplaene_/stundenplan-mathematik.pdf and /dev/null differ diff --git a/static/stundenplaene_/stundenplan-mathematik.webp b/static/stundenplaene_/stundenplan-mathematik.webp deleted file mode 100644 index f51e2ec06fa0cfe23ce4d5526ca472c547b44f05..0000000000000000000000000000000000000000 Binary files a/static/stundenplaene_/stundenplan-mathematik.webp and /dev/null differ diff --git a/static/stundenplaene_/stundenplan-physik-dark.webp b/static/stundenplaene_/stundenplan-physik-dark.webp deleted file mode 100644 index c367e48a2682502e83d0840b7c705378d78bd774..0000000000000000000000000000000000000000 Binary files a/static/stundenplaene_/stundenplan-physik-dark.webp and /dev/null differ diff --git a/static/stundenplaene_/stundenplan-physik.pdf b/static/stundenplaene_/stundenplan-physik.pdf deleted file mode 100644 index 0d0875462e7337d016e8852ddfb658a0558b1b99..0000000000000000000000000000000000000000 Binary files a/static/stundenplaene_/stundenplan-physik.pdf and /dev/null differ diff --git a/static/stundenplaene_/stundenplan-physik.webp b/static/stundenplaene_/stundenplan-physik.webp deleted file mode 100644 index e83a0fcd1d2898524277114e61c3179ab7d31101..0000000000000000000000000000000000000000 Binary files a/static/stundenplaene_/stundenplan-physik.webp and /dev/null differ diff --git a/static/stundenplaene_/stundenplan-wirtschaftsmathematik-dark.webp b/static/stundenplaene_/stundenplan-wirtschaftsmathematik-dark.webp deleted file mode 100644 index 387306883ffc53aa0d99662ba19f9043e3ed4685..0000000000000000000000000000000000000000 Binary files a/static/stundenplaene_/stundenplan-wirtschaftsmathematik-dark.webp and /dev/null differ diff --git a/static/stundenplaene_/stundenplan-wirtschaftsmathematik.pdf b/static/stundenplaene_/stundenplan-wirtschaftsmathematik.pdf deleted file mode 100644 index 155f92b9f0baa4fdba1ba48bade4af2f5a003ac1..0000000000000000000000000000000000000000 Binary files a/static/stundenplaene_/stundenplan-wirtschaftsmathematik.pdf and /dev/null differ diff --git a/static/stundenplaene_/stundenplan-wirtschaftsmathematik.webp b/static/stundenplaene_/stundenplan-wirtschaftsmathematik.webp deleted file mode 100644 index e1fe482d8b0f10d440d94b4146527f2f3a728653..0000000000000000000000000000000000000000 Binary files a/static/stundenplaene_/stundenplan-wirtschaftsmathematik.webp and /dev/null differ diff --git a/svelte.config.js b/svelte.config.js index f0151556989c15231bffdb1d2a0384ba9ca020e6..4681417fe53b0fbb53d06b4ca347379a8281c4cc 100644 --- a/svelte.config.js +++ b/svelte.config.js @@ -1,20 +1,18 @@ -import preprocess from "svelte-preprocess"; import adapter from "@sveltejs/adapter-node"; +import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"; /** @type {import('@sveltejs/kit').Config} */ const config = { - kit: { - // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. - // If your environment is not supported or you settled on a specific environment, switch out the adapter. - // See https://kit.svelte.dev/docs/adapters for more information about adapters. - adapter: adapter(), - }, + // Consult https://kit.svelte.dev/docs/integrations#preprocessors + // for more information about preprocessors + preprocess: [vitePreprocess({})], - preprocess: [ - preprocess({ - postcss: true, - }), - ], + kit: { + // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. + // If your environment is not supported or you settled on a specific environment, switch out the adapter. + // See https://kit.svelte.dev/docs/adapters for more information about adapters. + adapter: adapter(), + }, }; export default config; diff --git a/tailwind.config.cjs b/tailwind.config.cjs index eb5d1a48925ad7545aa513eb367b72e23530686d..9c42179a9b00e637b06c7244b53ad247c902f19a 100644 --- a/tailwind.config.cjs +++ b/tailwind.config.cjs @@ -1,36 +1,36 @@ /** @type {import('tailwindcss').Config}*/ const config = { - content: [ - "./src/**/*.{html,js,svelte,ts}", - "./node_modules/flowbite-svelte/**/*.{html,js,svelte,ts}", - ], - - theme: { - extend: { - colors: { - // green - primary: { "50": "#f0fdf4", "100": "#dcfce7", "200": "#bbf7d0", "300": "#86efac", "400": "#4ade80", "500": "#22c55e", "600": "#16a34a", "700": "#15803d", "800": "#166534", "900": "#14532d" }, - }, - /*height: { - "120": "30rem", - }, - margin: { - "auto": "auto", - },*/ - minWidth: { - "60": "15rem", - }, - spacing: { - "120": "30rem", - "auto": "auto", - }, - }, - }, - - plugins: [ - require("flowbite/plugin"), - ], - darkMode: "class", + content: [ + "./src/**/*.{html,js,svelte,ts}", + "./node_modules/flowbite-svelte/**/*.{html,js,svelte,ts}", + ], + + theme: { + extend: { + colors: { + // green + primary: { "50": "#f0fdf4", "100": "#dcfce7", "200": "#bbf7d0", "300": "#86efac", "400": "#4ade80", "500": "#22c55e", "600": "#16a34a", "700": "#15803d", "800": "#166534", "900": "#14532d" }, + }, + /*height: { + "120": "30rem", + }, + margin: { + "auto": "auto", + },*/ + minWidth: { + "60": "15rem", + }, + spacing: { + "120": "30rem", + "auto": "auto", + }, + }, + }, + + plugins: [ + require("flowbite/plugin"), + ], + darkMode: "class", }; module.exports = config; diff --git a/jsconfig.json b/tsconfig.json similarity index 50% rename from jsconfig.json rename to tsconfig.json index 45bcf7228fdf4bba3b68a224ed3165f4ceb0d0ae..82081abc36139c17c4b442eea6399c4b7ebb910e 100644 --- a/jsconfig.json +++ b/tsconfig.json @@ -9,23 +9,9 @@ "skipLibCheck": true, "sourceMap": true, "strict": true, - "newLine": "lf", - "noImplicitAny": true, - "removeComments": true, - "allowUnreachableCode": false, - "alwaysStrict": true, - "noFallthroughCasesInSwitch": true, - "noImplicitOverride": true, - "noImplicitReturns": true, - "noImplicitThis": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "strictBindCallApply": true, - "strictFunctionTypes": true, - "strictNullChecks": true, // - //"noErrorTruncation": true, + "moduleResolution": "bundler" } - // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias and https://kit.svelte.dev/docs/configuration#files + // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias // // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes // from the referenced tsconfig.json - TypeScript does not merge them in diff --git a/vite.config.js b/vite.config.js deleted file mode 100644 index f6373b1763969c6c6bee6b333c177e42278aedcf..0000000000000000000000000000000000000000 --- a/vite.config.js +++ /dev/null @@ -1,24 +0,0 @@ -import { sveltekit } from "@sveltejs/kit/vite"; -import { defineConfig } from "vite"; -import watchAndRun from "vite-plugin-watch-and-run"; -import path from "path"; - -export default defineConfig({ - plugins: [ - sveltekit(), - watchAndRun([ - { - name: "render-flyer-images", - watchKind: ["add", "change", "ready", "unlink"], - watch: `{${path.resolve("static/flyer/*.pdf")},${path.resolve("src/lib/server/renderFlyerImages.js")}}`, - run: "npm run render-flyer-images", - delay: 300, - }, - ]), - ], - server: { - hmr: { - //clientPort: 443, - }, - }, -}); diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000000000000000000000000000000000000..bbf8c7da43f0080dc6b9fb275f9583b7c17f1506 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,6 @@ +import { sveltekit } from '@sveltejs/kit/vite'; +import { defineConfig } from 'vite'; + +export default defineConfig({ + plugins: [sveltekit()] +});