From 684e2e7fa4e010a442cc26abaeaa9866fe014d17 Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Fri, 1 Oct 2021 18:28:53 +0200 Subject: [PATCH 01/16] Add a --dev flag to forward requests to node server --- main.go | 9 +++++++++ static.go | 38 +++++++++++++++++++++++++++++++++++--- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/main.go b/main.go index e875efc..1b2804f 100644 --- a/main.go +++ b/main.go @@ -59,6 +59,7 @@ func StripPrefix(prefix string, h http.Handler) http.Handler { func main() { var bind = flag.String("bind", ":8081", "Bind port/socket") var dsn = flag.String("dsn", DSNGenerator(), "DSN to connect to the MySQL server") + flag.StringVar(&DevProxy, "dev", DevProxy, "Proxify traffic to this host for static assets") flag.StringVar(&baseURL, "baseurl", baseURL, "URL prepended to each URL") flag.UintVar(¤tPromo, "current-promo", currentPromo, "Year of the current promotion") flag.Var(&localAuthUsers, "local-auth-user", "Allow local authentication for this user (bypass OIDC).") @@ -76,6 +77,14 @@ func main() { baseURL = "" } + if DevProxy != "" { + Router().GET("/.svelte-kit/*_", serveOrReverse("")) + Router().GET("/node_modules/*_", serveOrReverse("")) + Router().GET("/@vite/*_", serveOrReverse("")) + Router().GET("/__vite_ping", serveOrReverse("")) + Router().GET("/src/*_", serveOrReverse("")) + } + initializeOIDC() // Initialize contents diff --git a/static.go b/static.go index 0f24406..2ea309e 100644 --- a/static.go +++ b/static.go @@ -1,17 +1,49 @@ package main import ( + "io" "net/http" + "net/url" + "path" "github.com/julienschmidt/httprouter" ) +var DevProxy string + func serveOrReverse(forced_url string) func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { - if forced_url != "" { - r.URL.Path = forced_url + if DevProxy != "" { + if u, err := url.Parse(DevProxy); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } else { + if forced_url != "" { + u.Path = path.Join(u.Path, forced_url) + } else { + u.Path = path.Join(u.Path, r.URL.Path) + } + + if r, err := http.NewRequest(r.Method, u.String(), r.Body); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } else if resp, err := http.DefaultClient.Do(r); err != nil { + http.Error(w, err.Error(), http.StatusBadGateway) + } else { + defer resp.Body.Close() + + for key := range resp.Header { + w.Header().Add(key, resp.Header.Get(key)) + } + w.WriteHeader(resp.StatusCode) + + io.Copy(w, resp.Body) + } + } + } else { + if forced_url != "" { + r.URL.Path = forced_url + } + http.FileServer(Assets).ServeHTTP(w, r) } - http.FileServer(Assets).ServeHTTP(w, r) } } From 38180f8afd511e54fc1e3718738aa16d2f5c1a54 Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Sat, 19 Feb 2022 15:56:28 +0100 Subject: [PATCH 02/16] Make go binaries lighter --- .drone.yml | 4 ++-- Dockerfile | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.drone.yml b/.drone.yml index 24d0b89..96cd551 100644 --- a/.drone.yml +++ b/.drone.yml @@ -19,7 +19,7 @@ steps: commands: - apk --no-cache add build-base - go get -v - - go build -v + - go build -v -ldflags="-s -w" environment: GOARM: 7 @@ -55,7 +55,7 @@ steps: commands: - apk --no-cache add build-base - go get -v - - go build -v + - go build -v -ldflags="-s -w" - name: publish image: plugins/docker diff --git a/Dockerfile b/Dockerfile index e181451..e3cbaab 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,7 +9,7 @@ COPY htdocs/ ./htdocs/ RUN go generate -v && \ go get -d -v && \ - go build -v + go build -v -ldflags="-s -w" FROM alpine From ded0e8e1c8be99bce144308e92fc6fcfc8acf873 Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Thu, 18 Nov 2021 12:12:28 +0100 Subject: [PATCH 03/16] WIP Svelte --- atsebayt/.eslintrc.cjs | 20 + atsebayt/.gitignore | 5 + atsebayt/.prettierrc | 6 + atsebayt/README.md | 38 + atsebayt/package-lock.json | 1880 +++++++++++++++++ atsebayt/package.json | 29 + atsebayt/src/app.html | 18 + atsebayt/src/components/DateFormat.svelte | 17 + atsebayt/src/components/QuestionForm.svelte | 167 ++ .../src/components/QuestionProposal.svelte | 21 + .../src/components/QuestionProposals.svelte | 113 + atsebayt/src/components/StudentGrades.svelte | 25 + atsebayt/src/components/SurveyAdmin.svelte | 99 + atsebayt/src/components/SurveyBadge.svelte | 11 + atsebayt/src/components/SurveyList.svelte | 81 + .../src/components/SurveyQuestions.svelte | 101 + atsebayt/src/components/UserSurveys.svelte | 61 + .../src/components/ValidateSubmissions.svelte | 60 + atsebayt/src/global.d.ts | 1 + atsebayt/src/hooks.js | 6 + atsebayt/src/lib/questions.js | 112 + atsebayt/src/lib/surveys.js | 144 ++ atsebayt/src/lib/users.js | 53 + atsebayt/src/routes/__layout.svelte | 138 ++ atsebayt/src/routes/auth.svelte | 71 + atsebayt/src/routes/bug-bounty.svelte | 89 + atsebayt/src/routes/grades/[promo].svelte | 17 + atsebayt/src/routes/grades/index.svelte | 5 + atsebayt/src/routes/index.svelte | 29 + atsebayt/src/routes/surveys/[sid].svelte | 62 + atsebayt/src/routes/surveys/index.svelte | 8 + atsebayt/src/routes/users/[uid]/index.svelte | 111 + .../routes/users/[uid]/surveys/[sid].svelte | 64 + .../routes/users/[uid]/surveys/index.svelte | 64 + atsebayt/src/routes/users/index.svelte | 79 + atsebayt/src/stores/user.js | 27 + atsebayt/static/css/bootstrap.min.css | 12 + atsebayt/static/favicon.png | Bin 0 -> 1571 bytes atsebayt/static/img/srstamps.png | Bin 0 -> 16124 bytes atsebayt/svelte.config.js | 13 + atsebayt/tsconfig.json | 31 + auth.go | 47 +- auth_krb5.go | 2 +- auth_oidc.go | 2 +- handler.go | 37 +- main.go | 5 + static.go | 14 +- users.go | 27 +- 48 files changed, 3976 insertions(+), 46 deletions(-) create mode 100644 atsebayt/.eslintrc.cjs create mode 100644 atsebayt/.gitignore create mode 100644 atsebayt/.prettierrc create mode 100644 atsebayt/README.md create mode 100644 atsebayt/package-lock.json create mode 100644 atsebayt/package.json create mode 100644 atsebayt/src/app.html create mode 100644 atsebayt/src/components/DateFormat.svelte create mode 100644 atsebayt/src/components/QuestionForm.svelte create mode 100644 atsebayt/src/components/QuestionProposal.svelte create mode 100644 atsebayt/src/components/QuestionProposals.svelte create mode 100644 atsebayt/src/components/StudentGrades.svelte create mode 100644 atsebayt/src/components/SurveyAdmin.svelte create mode 100644 atsebayt/src/components/SurveyBadge.svelte create mode 100644 atsebayt/src/components/SurveyList.svelte create mode 100644 atsebayt/src/components/SurveyQuestions.svelte create mode 100644 atsebayt/src/components/UserSurveys.svelte create mode 100644 atsebayt/src/components/ValidateSubmissions.svelte create mode 100644 atsebayt/src/global.d.ts create mode 100644 atsebayt/src/hooks.js create mode 100644 atsebayt/src/lib/questions.js create mode 100644 atsebayt/src/lib/surveys.js create mode 100644 atsebayt/src/lib/users.js create mode 100644 atsebayt/src/routes/__layout.svelte create mode 100644 atsebayt/src/routes/auth.svelte create mode 100644 atsebayt/src/routes/bug-bounty.svelte create mode 100644 atsebayt/src/routes/grades/[promo].svelte create mode 100644 atsebayt/src/routes/grades/index.svelte create mode 100644 atsebayt/src/routes/index.svelte create mode 100644 atsebayt/src/routes/surveys/[sid].svelte create mode 100644 atsebayt/src/routes/surveys/index.svelte create mode 100644 atsebayt/src/routes/users/[uid]/index.svelte create mode 100644 atsebayt/src/routes/users/[uid]/surveys/[sid].svelte create mode 100644 atsebayt/src/routes/users/[uid]/surveys/index.svelte create mode 100644 atsebayt/src/routes/users/index.svelte create mode 100644 atsebayt/src/stores/user.js create mode 100644 atsebayt/static/css/bootstrap.min.css create mode 100644 atsebayt/static/favicon.png create mode 100644 atsebayt/static/img/srstamps.png create mode 100644 atsebayt/svelte.config.js create mode 100644 atsebayt/tsconfig.json diff --git a/atsebayt/.eslintrc.cjs b/atsebayt/.eslintrc.cjs new file mode 100644 index 0000000..fba3861 --- /dev/null +++ b/atsebayt/.eslintrc.cjs @@ -0,0 +1,20 @@ +module.exports = { + root: true, + parser: '@typescript-eslint/parser', + extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'], + plugins: ['svelte3', '@typescript-eslint'], + ignorePatterns: ['*.cjs'], + overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }], + settings: { + 'svelte3/typescript': () => require('typescript') + }, + parserOptions: { + sourceType: 'module', + ecmaVersion: 2019 + }, + env: { + browser: true, + es2017: true, + node: true + } +}; diff --git a/atsebayt/.gitignore b/atsebayt/.gitignore new file mode 100644 index 0000000..dad6f80 --- /dev/null +++ b/atsebayt/.gitignore @@ -0,0 +1,5 @@ +.DS_Store +node_modules +/build +/.svelte-kit +/package diff --git a/atsebayt/.prettierrc b/atsebayt/.prettierrc new file mode 100644 index 0000000..ff2677e --- /dev/null +++ b/atsebayt/.prettierrc @@ -0,0 +1,6 @@ +{ + "useTabs": true, + "singleQuote": true, + "trailingComma": "none", + "printWidth": 100 +} diff --git a/atsebayt/README.md b/atsebayt/README.md new file mode 100644 index 0000000..82510ca --- /dev/null +++ b/atsebayt/README.md @@ -0,0 +1,38 @@ +# create-svelte + +Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte); + +## Creating a project + +If you're seeing this, you've probably already done this step. Congrats! + +```bash +# create a new project in the current directory +npm init svelte@next + +# create a new project in my-app +npm init svelte@next my-app +``` + +> Note: the `@next` is temporary + +## Developing + +Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: + +```bash +npm run dev + +# or start the server and open the app in a new browser tab +npm run dev -- --open +``` + +## Building + +Before creating a production version of your app, install an [adapter](https://kit.svelte.dev/docs#adapters) for your target environment. Then: + +```bash +npm run build +``` + +> You can preview the built app with `npm run preview`, regardless of whether you installed an adapter. This should _not_ be used to serve your app in production. diff --git a/atsebayt/package-lock.json b/atsebayt/package-lock.json new file mode 100644 index 0000000..30bae31 --- /dev/null +++ b/atsebayt/package-lock.json @@ -0,0 +1,1880 @@ +{ + "name": "atsebayt", + "version": "0.0.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.15.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", + "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", + "dev": true + }, + "@babel/highlight": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", + "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "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, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + } + } + }, + "@eslint/eslintrc": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", + "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^13.9.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + } + } + }, + "@humanwhocodes/config-array": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", + "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^1.2.0", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + } + }, + "@humanwhocodes/object-schema": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz", + "integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==", + "dev": true + }, + "@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, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@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 + }, + "@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, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@rollup/pluginutils": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.1.2.tgz", + "integrity": "sha512-ROn4qvkxP9SyPeHaf7uQC/GPFY6L/OWy9+bd9AwcjOAWQwxRscoEyAUD8qCY5o5iL4jqQwoLk2kaTKJPb/HwzQ==", + "dev": true, + "requires": { + "estree-walker": "^2.0.1", + "picomatch": "^2.2.2" + } + }, + "@sveltejs/kit": { + "version": "1.0.0-next.266", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-1.0.0-next.266.tgz", + "integrity": "sha512-nlc+ECltLIadY12K+DerDLtgKRBe/ri+R5e2iWFIovetk31heU92nd1k24DL+FMcNo7B0Ik4XxurHCV2eoft3g==", + "dev": true, + "requires": { + "@sveltejs/vite-plugin-svelte": "^1.0.0-next.32", + "sade": "^1.7.4", + "vite": "^2.8.0" + } + }, + "@sveltejs/vite-plugin-svelte": { + "version": "1.0.0-next.37", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-1.0.0-next.37.tgz", + "integrity": "sha512-EdSXw2rXeOahNrQfMJVZxa/NxZxW1a0TiBI3s+pVxnxU14hEQtnkLtdbTFhnceu22gJpNPFSIJRcIwRBBDQIeA==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^4.1.2", + "debug": "^4.3.3", + "kleur": "^4.1.4", + "magic-string": "^0.25.7", + "svelte-hmr": "^0.14.9" + }, + "dependencies": { + "debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + } + } + }, + "@types/json-schema": { + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", + "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", + "dev": true + }, + "@types/node": { + "version": "17.0.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.17.tgz", + "integrity": "sha512-e8PUNQy1HgJGV3iU/Bp2+D/DXh3PYeyli8LgIwsQcs1Ar1LoaWHSIT6Rw+H2rNJmiq6SNWiDytfx8+gYj7wDHw==", + "dev": true + }, + "@types/pug": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.6.tgz", + "integrity": "sha512-SnHmG9wN1UVmagJOnyo/qkk0Z7gejYxOYYmaAwr5u2yFYfsupN3sg10kyzN8Hep/2zbHxCnsumxOoRIRMBwKCg==", + "dev": true + }, + "@types/sass": { + "version": "1.43.1", + "resolved": "https://registry.npmjs.org/@types/sass/-/sass-1.43.1.tgz", + "integrity": "sha512-BPdoIt1lfJ6B7rw35ncdwBZrAssjcwzI5LByIrYs+tpXlj/CAkuVdRsgZDdP4lq5EjyWzwxZCqAoFyHKFwp32g==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@typescript-eslint/eslint-plugin": { + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.33.0.tgz", + "integrity": "sha512-aINiAxGVdOl1eJyVjaWn/YcVAq4Gi/Yo35qHGCnqbWVz61g39D0h23veY/MA0rFFGfxK7TySg2uwDeNv+JgVpg==", + "dev": true, + "requires": { + "@typescript-eslint/experimental-utils": "4.33.0", + "@typescript-eslint/scope-manager": "4.33.0", + "debug": "^4.3.1", + "functional-red-black-tree": "^1.0.1", + "ignore": "^5.1.8", + "regexpp": "^3.1.0", + "semver": "^7.3.5", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/experimental-utils": { + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.33.0.tgz", + "integrity": "sha512-zeQjOoES5JFjTnAhI5QY7ZviczMzDptls15GFsI6jyUOq0kOf9+WonkhtlIhh0RgHRnqj5gdNxW5j1EvAyYg6Q==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.7", + "@typescript-eslint/scope-manager": "4.33.0", + "@typescript-eslint/types": "4.33.0", + "@typescript-eslint/typescript-estree": "4.33.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0" + } + }, + "@typescript-eslint/parser": { + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.33.0.tgz", + "integrity": "sha512-ZohdsbXadjGBSK0/r+d87X0SBmKzOq4/S5nzK6SBgJspFo9/CUDJ7hjayuze+JK7CZQLDMroqytp7pOcFKTxZA==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "4.33.0", + "@typescript-eslint/types": "4.33.0", + "@typescript-eslint/typescript-estree": "4.33.0", + "debug": "^4.3.1" + } + }, + "@typescript-eslint/scope-manager": { + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.33.0.tgz", + "integrity": "sha512-5IfJHpgTsTZuONKbODctL4kKuQje/bzBRkwHE8UOZ4f89Zeddg+EGZs8PD8NcN4LdM3ygHWYB3ukPAYjvl/qbQ==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.33.0", + "@typescript-eslint/visitor-keys": "4.33.0" + } + }, + "@typescript-eslint/types": { + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.33.0.tgz", + "integrity": "sha512-zKp7CjQzLQImXEpLt2BUw1tvOMPfNoTAfb8l51evhYbOEEzdWyQNmHWWGPR6hwKJDAi+1VXSBmnhL9kyVTTOuQ==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.33.0.tgz", + "integrity": "sha512-rkWRY1MPFzjwnEVHsxGemDzqqddw2QbTJlICPD9p9I9LfsO8fdmfQPOX3uKfUaGRDFJbfrtm/sXhVXN4E+bzCA==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.33.0", + "@typescript-eslint/visitor-keys": "4.33.0", + "debug": "^4.3.1", + "globby": "^11.0.3", + "is-glob": "^4.0.1", + "semver": "^7.3.5", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.33.0.tgz", + "integrity": "sha512-uqi/2aSz9g2ftcHWf8uLPJA70rUv6yuMW5Bohw+bwcuzaxQIHaKFZCKGoGXIrc9vkTJ3+0txM73K0Hq3d5wgIg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.33.0", + "eslint-visitor-keys": "^2.0.0" + } + }, + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + }, + "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 + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, + "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 + }, + "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, + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "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 + }, + "astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "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 + }, + "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, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "dev": true + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "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, + "requires": { + "color-convert": "^2.0.1" + } + }, + "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, + "requires": { + "color-name": "~1.1.4" + } + }, + "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 + }, + "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 + }, + "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, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.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" + } + }, + "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, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "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, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "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 + }, + "detect-indent": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", + "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", + "dev": true + }, + "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, + "requires": { + "path-type": "^4.0.0" + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "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 + }, + "enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "requires": { + "ansi-colors": "^4.1.1" + } + }, + "es6-promise": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", + "integrity": "sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=", + "dev": true + }, + "esbuild": { + "version": "0.14.21", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.21.tgz", + "integrity": "sha512-7WEoNMBJdLN993dr9h0CpFHPRc3yFZD+EAVY9lg6syJJ12gc5fHq8d75QRExuhnMkT2DaRiIKFThRvDWP+fO+A==", + "dev": true, + "requires": { + "esbuild-android-arm64": "0.14.21", + "esbuild-darwin-64": "0.14.21", + "esbuild-darwin-arm64": "0.14.21", + "esbuild-freebsd-64": "0.14.21", + "esbuild-freebsd-arm64": "0.14.21", + "esbuild-linux-32": "0.14.21", + "esbuild-linux-64": "0.14.21", + "esbuild-linux-arm": "0.14.21", + "esbuild-linux-arm64": "0.14.21", + "esbuild-linux-mips64le": "0.14.21", + "esbuild-linux-ppc64le": "0.14.21", + "esbuild-linux-riscv64": "0.14.21", + "esbuild-linux-s390x": "0.14.21", + "esbuild-netbsd-64": "0.14.21", + "esbuild-openbsd-64": "0.14.21", + "esbuild-sunos-64": "0.14.21", + "esbuild-windows-32": "0.14.21", + "esbuild-windows-64": "0.14.21", + "esbuild-windows-arm64": "0.14.21" + } + }, + "esbuild-android-arm64": { + "version": "0.14.21", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.21.tgz", + "integrity": "sha512-Bqgld1TY0wZv8TqiQmVxQFgYzz8ZmyzT7clXBDZFkOOdRybzsnj8AZuK1pwcLVA7Ya6XncHgJqIao7NFd3s0RQ==", + "dev": true, + "optional": true + }, + "esbuild-darwin-64": { + "version": "0.14.21", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.21.tgz", + "integrity": "sha512-j+Eg+e13djzyYINVvAbOo2/zvZ2DivuJJTaBrJnJHSD7kUNuGHRkHoSfFjbI80KHkn091w350wdmXDNSgRjfYQ==", + "dev": true, + "optional": true + }, + "esbuild-darwin-arm64": { + "version": "0.14.21", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.21.tgz", + "integrity": "sha512-nDNTKWDPI0RuoPj5BhcSB2z5EmZJJAyRtZLIjyXSqSpAyoB8eyAKXl4lB8U2P78Fnh4Lh1le/fmpewXE04JhBQ==", + "dev": true, + "optional": true + }, + "esbuild-freebsd-64": { + "version": "0.14.21", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.21.tgz", + "integrity": "sha512-zIurkCHXhxELiDZtLGiexi8t8onQc2LtuE+S7457H/pP0g0MLRKMrsn/IN4LDkNe6lvBjuoZZi2OfelOHn831g==", + "dev": true, + "optional": true + }, + "esbuild-freebsd-arm64": { + "version": "0.14.21", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.21.tgz", + "integrity": "sha512-wdxMmkJfbwcN+q85MpeUEamVZ40FNsBa9mPq8tAszDn8TRT2HoJvVRADPIIBa9SWWwlDChIMjkDKAnS3KS/sPA==", + "dev": true, + "optional": true + }, + "esbuild-linux-32": { + "version": "0.14.21", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.21.tgz", + "integrity": "sha512-fmxvyzOPPh2xiEHojpCeIQP6pXcoKsWbz3ryDDIKLOsk4xp3GbpHIEAWP0xTeuhEbendmvBDVKbAVv3PnODXLg==", + "dev": true, + "optional": true + }, + "esbuild-linux-64": { + "version": "0.14.21", + "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.21.tgz", + "integrity": "sha512-edZyNOv1ql+kpmlzdqzzDjRQYls+tSyi4QFi+PdBhATJFUqHsnNELWA9vMSzAaInPOEaVUTA5Ml28XFChcy4DA==", + "dev": true, + "optional": true + }, + "esbuild-linux-arm": { + "version": "0.14.21", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.21.tgz", + "integrity": "sha512-aSU5pUueK6afqmLQsbU+QcFBT62L+4G9hHMJDHWfxgid6hzhSmfRH9U/f+ymvxsSTr/HFRU4y7ox8ZyhlVl98w==", + "dev": true, + "optional": true + }, + "esbuild-linux-arm64": { + "version": "0.14.21", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.21.tgz", + "integrity": "sha512-t5qxRkq4zdQC0zXpzSB2bTtfLgOvR0C6BXYaRE/6/k8/4SrkZcTZBeNu+xGvwCU4b5dU9ST9pwIWkK6T1grS8g==", + "dev": true, + "optional": true + }, + "esbuild-linux-mips64le": { + "version": "0.14.21", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.21.tgz", + "integrity": "sha512-jLZLQGCNlUsmIHtGqNvBs3zN+7a4D9ckf0JZ+jQTwHdZJ1SgV9mAjbB980OFo66LoY+WeM7t3WEnq3FjI1zw4A==", + "dev": true, + "optional": true + }, + "esbuild-linux-ppc64le": { + "version": "0.14.21", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.21.tgz", + "integrity": "sha512-4TWxpK391en2UBUw6GSrukToTDu6lL9vkm3Ll40HrI08WG3qcnJu7bl8e1+GzelDsiw1QmfAY/nNvJ6iaHRpCQ==", + "dev": true, + "optional": true + }, + "esbuild-linux-riscv64": { + "version": "0.14.21", + "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.21.tgz", + "integrity": "sha512-fElngqOaOfTsF+u+oetDLHsPG74vB2ZaGZUqmGefAJn3a5z9Z2pNa4WpVbbKgHpaAAy5tWM1m1sbGohj6Ki6+Q==", + "dev": true, + "optional": true + }, + "esbuild-linux-s390x": { + "version": "0.14.21", + "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.21.tgz", + "integrity": "sha512-brleZ6R5fYv0qQ7ZBwenQmP6i9TdvJCB092c/3D3pTLQHBGHJb5zWgKxOeS7bdHzmLy6a6W7GbFk6QKpjyD6QA==", + "dev": true, + "optional": true + }, + "esbuild-netbsd-64": { + "version": "0.14.21", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.21.tgz", + "integrity": "sha512-nCEgsLCQ8RoFWVV8pVI+kX66ICwbPP/M9vEa0NJGIEB/Vs5sVGMqkf67oln90XNSkbc0bPBDuo4G6FxlF7PN8g==", + "dev": true, + "optional": true + }, + "esbuild-openbsd-64": { + "version": "0.14.21", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.21.tgz", + "integrity": "sha512-h9zLMyVD0T73MDTVYIb/qUTokwI6EJH9O6wESuTNq6+XpMSr6C5aYZ4fvFKdNELW+Xsod+yDS2hV2JTUAbFrLA==", + "dev": true, + "optional": true + }, + "esbuild-sunos-64": { + "version": "0.14.21", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.21.tgz", + "integrity": "sha512-Kl+7Cot32qd9oqpLdB1tEGXEkjBlijrIxMJ0+vlDFaqsODutif25on0IZlFxEBtL2Gosd4p5WCV1U7UskNQfXA==", + "dev": true, + "optional": true + }, + "esbuild-windows-32": { + "version": "0.14.21", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.21.tgz", + "integrity": "sha512-V7vnTq67xPBUCk/9UtlolmQ798Ecjdr1ZoI1vcSgw7M82aSSt0eZdP6bh5KAFZU8pxDcx3qoHyWQfHYr11f22A==", + "dev": true, + "optional": true + }, + "esbuild-windows-64": { + "version": "0.14.21", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.21.tgz", + "integrity": "sha512-kDgHjKOHwjfJDCyRGELzVxiP/RBJBTA+wyspf78MTTJQkyPuxH2vChReNdWc+dU2S4gIZFHMdP1Qrl/k22ZmaA==", + "dev": true, + "optional": true + }, + "esbuild-windows-arm64": { + "version": "0.14.21", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.21.tgz", + "integrity": "sha512-8Sbo0zpzgwWrwjQYLmHF78f7E2xg5Ve63bjB2ng3V2aManilnnTGaliq2snYg+NOX60+hEvJHRdVnuIAHW0lVw==", + "dev": true, + "optional": true + }, + "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 + }, + "eslint": { + "version": "7.32.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", + "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", + "dev": true, + "requires": { + "@babel/code-frame": "7.12.11", + "@eslint/eslintrc": "^0.4.3", + "@humanwhocodes/config-array": "^0.5.0", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.1", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.1.2", + "globals": "^13.6.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^6.0.9", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "dependencies": { + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } + } + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + } + } + }, + "eslint-config-prettier": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz", + "integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==", + "dev": true + }, + "eslint-plugin-svelte3": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-svelte3/-/eslint-plugin-svelte3-3.4.0.tgz", + "integrity": "sha512-MIQUTuRv3o7LyQ+360qOc9mLT35j1I5YzHr04g/UDcvJTpg0X/kHWELY99ve869Rp/9wjqD7I26Aq5H8OH5RIg==", + "dev": true + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^2.0.0" + } + }, + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + }, + "espree": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "dev": true, + "requires": { + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } + } + }, + "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, + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "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==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "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 + }, + "fast-glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", + "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "dev": true, + "requires": { + "@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" + } + }, + "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 + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "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, + "requires": { + "flat-cache": "^3.0.4" + } + }, + "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, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.2.tgz", + "integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "dev": true, + "requires": { + "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" + } + }, + "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, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globals": { + "version": "13.11.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.11.0.tgz", + "integrity": "sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "requires": { + "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" + } + }, + "graceful-fs": { + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", + "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true + }, + "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, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "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, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-core-module": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", + "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "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 + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "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 + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "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 + }, + "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": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "kleur": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.4.tgz", + "integrity": "sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA==", + "dev": true + }, + "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, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "dev": true + }, + "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 + }, + "lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", + "dev": true + }, + "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, + "requires": { + "yallist": "^4.0.0" + } + }, + "magic-string": { + "version": "0.25.7", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", + "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==", + "dev": true, + "requires": { + "sourcemap-codec": "^1.4.4" + } + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "micromatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.2.3" + } + }, + "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 + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "nanoid": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz", + "integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "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 + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "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.3" + } + }, + "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, + "requires": { + "callsites": "^3.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "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 + }, + "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==", + "dev": true + }, + "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 + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "postcss": { + "version": "8.4.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.6.tgz", + "integrity": "sha512-OovjwIzs9Te46vlEx7+uXB0PLijpwjXGKXjVGGPIGubGpq7uh5Xgf6D6FiJ/SzJMBosHDp6a2hiXOS97iBXcaA==", + "dev": true, + "requires": { + "nanoid": "^3.2.0", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + } + }, + "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 + }, + "prettier": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz", + "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==", + "dev": true + }, + "prettier-plugin-svelte": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-2.6.0.tgz", + "integrity": "sha512-NPSRf6Y5rufRlBleok/pqg4+1FyGsL0zYhkYP6hnueeL1J/uCm3OfOZPsLX4zqD9VAdcXfyEL2PYqGv8ZoOSfA==", + "dev": true + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "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 + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true + }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true + }, + "resolve": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", + "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", + "dev": true, + "requires": { + "is-core-module": "^2.8.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "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 + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "rollup": { + "version": "2.67.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.67.2.tgz", + "integrity": "sha512-hoEiBWwZtf1QdK3jZIq59L0FJj4Fiv4RplCO4pvCRC86qsoFurWB4hKQIjoRf3WvJmk5UZ9b0y5ton+62fC7Tw==", + "dev": true, + "requires": { + "fsevents": "~2.3.2" + } + }, + "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, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "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, + "requires": { + "mri": "^1.1.0" + } + }, + "sander": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/sander/-/sander-0.5.1.tgz", + "integrity": "sha1-dB4kXiMfB8r7b98PEzrfohalAq0=", + "dev": true, + "requires": { + "es6-promise": "^3.1.2", + "graceful-fs": "^4.1.3", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.2" + }, + "dependencies": { + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "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, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "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 + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "dependencies": { + "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, + "requires": { + "color-convert": "^2.0.1" + } + }, + "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, + "requires": { + "color-name": "~1.1.4" + } + }, + "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 + } + } + }, + "sorcery": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/sorcery/-/sorcery-0.10.0.tgz", + "integrity": "sha1-iukK19fLBfxZ8asMY3hF1cFaUrc=", + "dev": true, + "requires": { + "buffer-crc32": "^0.2.5", + "minimist": "^1.2.0", + "sander": "^0.5.0", + "sourcemap-codec": "^1.3.0" + } + }, + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true + }, + "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==", + "dev": true + }, + "sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "dev": true + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "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, + "requires": { + "min-indent": "^1.0.0" + } + }, + "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 + }, + "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, + "requires": { + "has-flag": "^3.0.0" + } + }, + "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==", + "dev": true + }, + "svelte": { + "version": "3.46.4", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.46.4.tgz", + "integrity": "sha512-qKJzw6DpA33CIa+C/rGp4AUdSfii0DOTCzj/2YpSKKayw5WGSS624Et9L1nU1k2OVRS9vaENQXp2CVZNU+xvIg==", + "dev": true + }, + "svelte-check": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-2.4.3.tgz", + "integrity": "sha512-0zJMMgqYHoP7QEG3tfc5DekpHAOqoy4QOL8scWMSdHIpVVDVC0MuYK57nFyj3XVTW8Zfm85FlgnAdQYsVmST2Q==", + "dev": true, + "requires": { + "chokidar": "^3.4.1", + "fast-glob": "^3.2.7", + "import-fresh": "^3.2.1", + "minimist": "^1.2.5", + "picocolors": "^1.0.0", + "sade": "^1.7.4", + "source-map": "^0.7.3", + "svelte-preprocess": "^4.0.0", + "typescript": "*" + } + }, + "svelte-hmr": { + "version": "0.14.9", + "resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.14.9.tgz", + "integrity": "sha512-bKE9+4qb4sAnA+TKHiYurUl970rjA0XmlP9TEP7K/ncyWz3m81kA4HOgmlZK/7irGK7gzZlaPDI3cmf8fp/+tg==", + "dev": true + }, + "svelte-preprocess": { + "version": "4.10.3", + "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-4.10.3.tgz", + "integrity": "sha512-ttw17lJfb/dx2ZJT9sesaXT5l7mPQ9Apx1H496Kli3Hkk7orIRGpOw6rCPkRNzr6ueVPqb4vzodS5x7sBFhKHw==", + "dev": true, + "requires": { + "@types/pug": "^2.0.4", + "@types/sass": "^1.16.0", + "detect-indent": "^6.0.0", + "magic-string": "^0.25.7", + "sorcery": "^0.10.0", + "strip-indent": "^3.0.0" + } + }, + "table": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/table/-/table-6.7.1.tgz", + "integrity": "sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg==", + "dev": true, + "requires": { + "ajv": "^8.0.1", + "lodash.clonedeep": "^4.5.0", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ajv": { + "version": "8.6.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.3.tgz", + "integrity": "sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + } + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "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, + "requires": { + "is-number": "^7.0.0" + } + }, + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + }, + "tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, + "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, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "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 + }, + "typescript": { + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", + "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", + "dev": true + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, + "vite": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-2.8.1.tgz", + "integrity": "sha512-Typ8qjUnW0p53gBsJpisrKcZlEbUPZATja9BG6Z09QZjg9YrnEn/htkr/VH4WhnH7eNUQeSD+wKI1lHzQRWskw==", + "dev": true, + "requires": { + "esbuild": "^0.14.14", + "fsevents": "~2.3.2", + "postcss": "^8.4.6", + "resolve": "^1.22.0", + "rollup": "^2.59.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } +} diff --git a/atsebayt/package.json b/atsebayt/package.json new file mode 100644 index 0000000..656be15 --- /dev/null +++ b/atsebayt/package.json @@ -0,0 +1,29 @@ +{ + "name": "atsebayt", + "version": "0.0.1", + "scripts": { + "dev": "svelte-kit dev", + "build": "svelte-kit build", + "preview": "svelte-kit preview", + "check": "svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-check --tsconfig ./tsconfig.json --watch", + "lint": "prettier --ignore-path .gitignore --check --plugin-search-dir=. . && eslint --ignore-path .gitignore .", + "format": "prettier --ignore-path .gitignore --write --plugin-search-dir=. ." + }, + "devDependencies": { + "@sveltejs/kit": "^1.0.0-next.266", + "@typescript-eslint/eslint-plugin": "^4.33.0", + "@typescript-eslint/parser": "^4.33.0", + "eslint": "^7.32.0", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-svelte3": "^3.4.0", + "prettier": "^2.5.1", + "prettier-plugin-svelte": "^2.6.0", + "svelte": "^3.46.4", + "svelte-check": "^2.4.3", + "svelte-preprocess": "^4.10.3", + "tslib": "^2.3.1", + "typescript": "^4.5.5" + }, + "type": "module" +} diff --git a/atsebayt/src/app.html b/atsebayt/src/app.html new file mode 100644 index 0000000..d1f8e33 --- /dev/null +++ b/atsebayt/src/app.html @@ -0,0 +1,18 @@ + + + + + + + + + + + %svelte.head% + + +
+
+
%svelte.body%
+ + diff --git a/atsebayt/src/components/DateFormat.svelte b/atsebayt/src/components/DateFormat.svelte new file mode 100644 index 0000000..563a993 --- /dev/null +++ b/atsebayt/src/components/DateFormat.svelte @@ -0,0 +1,17 @@ + + +{formatDate(date, dateStyle, timeStyle)} diff --git a/atsebayt/src/components/QuestionForm.svelte b/atsebayt/src/components/QuestionForm.svelte new file mode 100644 index 0000000..841292d --- /dev/null +++ b/atsebayt/src/components/QuestionForm.svelte @@ -0,0 +1,167 @@ + + +
+
+ {#if $user.is_admin} + + {#if edit} + + {:else} + + {/if} + {/if} + + {#if edit} +
+ +
+
+ {:else} +

{qid + 1}. {question.title}

+ {/if} + + {#if edit} +
+ +
+ +
+
+ + + {:else if question.description} +

{@html question.description}

+ {/if} +
+
+ {#if false && response_history} +
+
+ Historique : + +
+
+ {/if} + {#if edit} + {#if question.kind == 'text' || question.kind == 'int'} +
+ +
+ +
+
+ {:else} + {#await question.getProposals()} +
+
+ Chargement des choix … +
+ {:then proposals} + + {/await} + {/if} + {:else if question.kind == 'mcq' || question.kind == 'ucq'} + {#await question.getProposals()} +
+
+ Chargement des choix … +
+ {:then proposals} + + {/await} + {:else if readonly} +

{value}

+ {:else if question.kind == 'int'} + + {:else} + + {/if} + + {#if false} +
+
+ + +
+
+ + +
+
+
+ +
+
+ + +
+
+ Créez la question pour ajouter des propositions +
+
+
+
+ {question.response.score} % +
+

{question.response.score_explaination}

+
+
+ {/if} +
+
diff --git a/atsebayt/src/components/QuestionProposal.svelte b/atsebayt/src/components/QuestionProposal.svelte new file mode 100644 index 0000000..e1a1281 --- /dev/null +++ b/atsebayt/src/components/QuestionProposal.svelte @@ -0,0 +1,21 @@ + + + + import { QuestionProposal } from '../lib/questions'; + + export let edit = false; + export let proposals = []; + export let kind = 'mcq'; + export let readonly = false; + export let id_question = 0; + export let value; + + let valueCheck = []; + $: { + console.log(value); + if (value) { + valueCheck = value.split(','); + } + } + + function addProposal() { + const p = new QuestionProposal(); + p.id_question = id_question; + proposals.push(p); + proposals = proposals; + } + + +{#each proposals as proposal, pid (proposal.id)} +
+ {#if kind == 'mcq'} + { value = valueCheck.join(',')}} + > + {:else} + + {/if} + {#if edit} +
{ proposal.save().then(() => proposal = proposal); } }> +
+ proposal.changed = true} + > + + +
+
+ {:else} + + {/if} +
+{/each} +{#if edit} + {#if kind == 'mcq'} + + {:else} + + {/if} + +{/if} diff --git a/atsebayt/src/components/StudentGrades.svelte b/atsebayt/src/components/StudentGrades.svelte new file mode 100644 index 0000000..44d9e46 --- /dev/null +++ b/atsebayt/src/components/StudentGrades.svelte @@ -0,0 +1,25 @@ + + +

+ Étudiants {#if promo}{promo}{/if} + Notes +

+ + + + + + + + + + + + + + + + +
IDLogin{ survey.title }
{ user.id }{ user.login }{ grades[user.id][survey.id] }
diff --git a/atsebayt/src/components/SurveyAdmin.svelte b/atsebayt/src/components/SurveyAdmin.svelte new file mode 100644 index 0000000..b12c8f3 --- /dev/null +++ b/atsebayt/src/components/SurveyAdmin.svelte @@ -0,0 +1,99 @@ + + +
+ +
+
+ +
+
{survey.id} + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ + +
+ +
+ + +
+
+ +
+
+ + {#if survey.id} + + + {/if} +
+
+ +
+
diff --git a/atsebayt/src/components/SurveyBadge.svelte b/atsebayt/src/components/SurveyBadge.svelte new file mode 100644 index 0000000..4b28e0d --- /dev/null +++ b/atsebayt/src/components/SurveyBadge.svelte @@ -0,0 +1,11 @@ + + +{#if survey.startAvailability() > Date.now()}Prévu> +{:else if survey.endAvailability() > Date.now()}En cours +{:else if !survey.corrected}Terminé +{:else}Corrigé +{/if} diff --git a/atsebayt/src/components/SurveyList.svelte b/atsebayt/src/components/SurveyList.svelte new file mode 100644 index 0000000..0c2e2b8 --- /dev/null +++ b/atsebayt/src/components/SurveyList.svelte @@ -0,0 +1,81 @@ + + + + + + + + {#if $user} + + {/if} + + + {#await getSurveys()} + + + + {:then surveys} + + {#each surveys as survey, sid (survey.id)} + {#if survey.shown && (!$user || (!$user.was_admin || $user.promo == survey.promo) || $user.is_admin)} + {#if $user && $user.is_admin && (sid == 0 || surveys[sid-1].promo != survey.promo)} + + + + {/if} + goto(`surveys/${survey.id}`)}> + + {#if survey.start_availability > Date.now()} + + {:else} + + {/if} + {#if !$user} + {:else if !survey.corrected} + + {:else} + + {/if} + + {/if} + {/each} + + {/await} + {#if $user && $user.is_admin} + + + + + + {/if} +
IntituléDateScore
+
+ Chargement des questionnaires … +
+ {survey.promo} +
+ {survey.title} + + + + + + + + N/A + {#await getScore(survey)} +
+ {:then score} + {score.score} + {/await} +
+ Ajouter un questionnaire +
diff --git a/atsebayt/src/components/SurveyQuestions.svelte b/atsebayt/src/components/SurveyQuestions.svelte new file mode 100644 index 0000000..3f5de62 --- /dev/null +++ b/atsebayt/src/components/SurveyQuestions.svelte @@ -0,0 +1,101 @@ + + +
+ {#each questions as question, qid (question.id)} + deleteQuestion(question, qid)} + bind:value={responses[question.id].value} + /> + {/each} + {#each newquestions as question, qid (qid)} + deleteNewQuestion(question, qid)} + /> + {/each} + +
+ + {#if $user && $user.is_admin} + + {/if} +
+ diff --git a/atsebayt/src/components/UserSurveys.svelte b/atsebayt/src/components/UserSurveys.svelte new file mode 100644 index 0000000..dad0bb3 --- /dev/null +++ b/atsebayt/src/components/UserSurveys.svelte @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + {#await getSurveys()} + + + + {:then surveys} + {#each surveys as survey, sid (survey.id)} + {#if allPromos || survey.promo === student.promo} + goto(`users/${student.id}/surveys/${survey.id}`)}> + + + + {#await getUserGrade(student.id, survey)} + + {:then gr} + + {/await} + {#await getUserScore(student.id, survey)} + + {:then score} + + {/await} + + {/if} + {/each} + {/await} + +
IDTitrePromoAvancementNote
+
+
+ Chargement des questionnaires… +
+
{survey.id}{survey.title}{survey.promo} +
+
+ {gr.avancement * 100} % + +
+
+ {score.score}{#if score.score >= 0}/20{/if} +
diff --git a/atsebayt/src/components/ValidateSubmissions.svelte b/atsebayt/src/components/ValidateSubmissions.svelte new file mode 100644 index 0000000..b10aeb8 --- /dev/null +++ b/atsebayt/src/components/ValidateSubmissions.svelte @@ -0,0 +1,60 @@ + + + + {#await getUserRendus()} + Please wait... + {:then rendus} + + + {#each Object.keys(rendus) as renduname, rid (rid)} + + {/each} + + + + + {#each Object.keys(rendus) as renduname, rid (rid)} + + {/each} + + + {/await} +
{renduname}
+ {#if rendus[renduname]} +
+ {rendus[renduname].hash} + {:else} + – + {/if} +
+ + diff --git a/atsebayt/src/global.d.ts b/atsebayt/src/global.d.ts new file mode 100644 index 0000000..63908c6 --- /dev/null +++ b/atsebayt/src/global.d.ts @@ -0,0 +1 @@ +/// diff --git a/atsebayt/src/hooks.js b/atsebayt/src/hooks.js new file mode 100644 index 0000000..edd4379 --- /dev/null +++ b/atsebayt/src/hooks.js @@ -0,0 +1,6 @@ +export async function handle({ event, resolve }) { + const response = await resolve(event, { + ssr: false, + }); + return response; +} diff --git a/atsebayt/src/lib/questions.js b/atsebayt/src/lib/questions.js new file mode 100644 index 0000000..fa6bccf --- /dev/null +++ b/atsebayt/src/lib/questions.js @@ -0,0 +1,112 @@ +export class QuestionProposal { + constructor(res) { + if (res) { + this.update(res); + } + } + + update({ id, id_question, label }) { + this.id = id; + this.id_question = id_question; + this.label = label; + if (this.changed !== undefined) + delete this.changed; + } + + async save() { + const res = await fetch(this.id?`api/questions/${this.id_question}/proposals/${this.id}`:`api/questions/${this.id_question}/proposals`, { + method: this.id?'PUT':'POST', + headers: {'Accept': 'application/json'}, + body: JSON.stringify(this), + }); + if (res.status == 200) { + const data = await res.json(); + this.update(data); + return data; + } else { + throw new Error((await res.json()).errmsg); + } + } + + async delete() { + if (this.id) { + const res = await fetch(`api/questions/${this.id_question}/proposals/${this.id}`, { + method: 'DELETE', + headers: {'Accept': 'application/json'}, + }); + if (res.status == 200) { + return true; + } else { + throw new Error((await res.json()).errmsg); + } + } + } +} + +export class Question { + constructor(res) { + if (res) { + this.update(res); + } + } + + update({ id, id_survey, title, description, desc_raw, placeholder, kind }) { + this.id = id; + this.id_survey = id_survey; + this.title = title; + this.description = description; + this.desc_raw = desc_raw; + this.placeholder = placeholder; + this.kind = kind; + } + + async getProposals() { + const res = await fetch(`api/questions/${this.id}/proposals`, { + method: 'GET', + headers: {'Accept': 'application/json'}, + }); + if (res.status == 200) { + return (await res.json()).map((p) => new QuestionProposal(p)) + } else { + throw new Error((await res.json()).errmsg); + } + } + + async save() { + const res = await fetch(this.id?`api/questions/${this.id}`:`api/surveys/${this.id_survey}/questions`, { + method: this.id?'PUT':'POST', + headers: {'Accept': 'application/json'}, + body: JSON.stringify(this), + }); + if (res.status == 200) { + const data = await res.json(); + this.update(data); + return data; + } else { + throw new Error((await res.json()).errmsg); + } + } + + async delete() { + if (this.id) { + const res = await fetch(`api/questions/${this.id}`, { + method: 'DELETE', + headers: {'Accept': 'application/json'}, + }); + if (res.status == 200) { + return true; + } else { + throw new Error((await res.json()).errmsg); + } + } + } +} + +export async function getQuestions(sid) { + const res = await fetch(`api/surveys/${sid}/questions`, {headers: {'Accept': 'application/json'}}) + if (res.status == 200) { + return (await res.json()).map((e) => new Question(e)) + } else { + throw new Error((await res.json()).errmsg); + } +} diff --git a/atsebayt/src/lib/surveys.js b/atsebayt/src/lib/surveys.js new file mode 100644 index 0000000..d1b6702 --- /dev/null +++ b/atsebayt/src/lib/surveys.js @@ -0,0 +1,144 @@ +import { getQuestions } from './questions'; + + +class Survey { + constructor(res) { + if (res) { + this.update(res); + } + } + + update({ id, title, promo, shown, corrected, start_availability, end_availability }) { + this.id = id; + this.title = title; + this.promo = promo; + this.shown = shown; + this.corrected = corrected; + if (this.start_availability != start_availability) { + this.start_availability = start_availability; + delete this.__start_availability; + } + if (this.end_availability != end_availability) { + this.end_availability = end_availability; + delete this.__end_availability; + } + } + + startAvailability() { + if (!this.__start_availability) { + this.__start_availability = new Date(this.start_availability) + } + return this.__start_availability + } + + endAvailability() { + if (!this.__end_availability) { + this.__end_availability = new Date(this.end_availability) + } + return this.__end_availability + } + + isFinished() { + return this.endAvailability() < new Date(); + } + + async retrieveAnswers(id_user=null) { + const res = await fetch(id_user?`api/users/${id_user}/surveys/${this.id}/responses`:`api/surveys/${this.id}/responses`, { + method: 'GET', + headers: {'Accept': 'application/json'}, + }); + if (res.status == 200) { + return await res.json(); + } else { + throw new Error((await res.json()).errmsg); + } + } + + async submitAnswers(answer, id_user=null) { + const res = await fetch(id_user?`api/users/${id_user}/surveys/${this.id}`:`api/surveys/${this.id}`, { + method: 'POST', + headers: {'Accept': 'application/json'}, + body: JSON.stringify(answer), + }); + if (res.status == 200) { + return await res.json(); + } else { + throw new Error((await res.json()).errmsg); + } + } + + async save() { + const res = await fetch(this.id?`api/surveys/${this.id}`:'api/surveys', { + method: this.id?'PUT':'POST', + headers: {'Accept': 'application/json'}, + body: JSON.stringify(this), + }); + if (res.status == 200) { + const data = await res.json() + this.update(data); + return data; + } else { + throw new Error((await res.json()).errmsg); + } + } + + async duplicate() { + if (this.id) { + const oldSurveyId = this.id; + delete this.id; + const res = await fetch(`api/surveys`, { + method: 'POST', + headers: {'Accept': 'application/json'}, + body: JSON.stringify(this), + }); + if (res.status == 200) { + const response = await res.json(); + + // Now recopy questions + const questions = await getQuestions(oldSurveyId); + for (const q of questions) { + delete q.id; + q.id_survey = response.id; + q.save(); + } + + return response; + } else { + throw new Error((await res.json()).errmsg); + } + } + } + + async delete() { + console.log("delete", this.id) + if (this.id) { + const res = await fetch(`api/surveys/${this.id}`, { + method: 'DELETE', + headers: {'Accept': 'application/json'}, + }); + if (res.status == 200) { + return true; + } else { + throw new Error((await res.json()).errmsg); + } + } + } +} + +export async function getSurveys() { + const res = await fetch(`api/surveys`, {headers: {'Accept': 'application/json'}}) + if (res.status == 200) { + return (await res.json()).map((s) => new Survey(s)); + } else { + throw new Error((await res.json()).errmsg); + } +} + +export async function getSurvey(sid) { + const res = await fetch(`api/surveys/${sid}`, {headers: {'Accept': 'application/json'}}) + if (res.status == 200) { + return new Survey(await res.json()); + } else { + throw new Error((await res.json()).errmsg); + } +} diff --git a/atsebayt/src/lib/users.js b/atsebayt/src/lib/users.js new file mode 100644 index 0000000..a6e00d8 --- /dev/null +++ b/atsebayt/src/lib/users.js @@ -0,0 +1,53 @@ +export async function getPromos() { + const res = await fetch('api/promos', {headers: {'Accept': 'application/json'}}) + if (res.status == 200) { + return await res.json(); + } else { + throw new Error((await res.json()).errmsg); + } +} + +export async function getUsers() { + const res = await fetch('api/users', {headers: {'Accept': 'application/json'}}) + if (res.status == 200) { + return await res.json(); + } else { + throw new Error((await res.json()).errmsg); + } +} + +export async function getUser(uid) { + const res = await fetch(`api/users/${uid}`, {headers: {'Accept': 'application/json'}}) + if (res.status == 200) { + return await res.json(); + } else { + throw new Error((await res.json()).errmsg); + } +} + +export async function getUserGrade(uid, survey) { + const res = await fetch(`api/users/${uid}/surveys/${survey.id}/grades`, {headers: {'Accept': 'application/json'}}) + if (res.status == 200) { + return await res.json(); + } else { + throw new Error((await res.json()).errmsg); + } +} + +export async function getUserScore(uid, survey) { + const res = await fetch(`api/users/${uid}/surveys/${survey.id}/score`, {headers: {'Accept': 'application/json'}}) + if (res.status == 200) { + return await res.json(); + } else { + throw new Error((await res.json()).errmsg); + } +} + +export async function getScore(survey) { + const res = await fetch(`api/surveys/${survey.id}/score`, {headers: {'Accept': 'application/json'}}) + if (res.status == 200) { + return await res.json(); + } else { + throw new Error((await res.json()).errmsg); + } +} diff --git a/atsebayt/src/routes/__layout.svelte b/atsebayt/src/routes/__layout.svelte new file mode 100644 index 0000000..8c22e89 --- /dev/null +++ b/atsebayt/src/routes/__layout.svelte @@ -0,0 +1,138 @@ + + + + + + +
+ +
diff --git a/atsebayt/src/routes/auth.svelte b/atsebayt/src/routes/auth.svelte new file mode 100644 index 0000000..b3a055c --- /dev/null +++ b/atsebayt/src/routes/auth.svelte @@ -0,0 +1,71 @@ + + + + +
+
+

Accès à votre compte

+
+ + +
+
+ + +
+ +
+ + +
diff --git a/atsebayt/src/routes/bug-bounty.svelte b/atsebayt/src/routes/bug-bounty.svelte new file mode 100644 index 0000000..e19b36d --- /dev/null +++ b/atsebayt/src/routes/bug-bounty.svelte @@ -0,0 +1,89 @@ +

Bug Bounty

+ +

+ Comme tous les services accessibles en ligne, ce site présente un certain nombre de bugs et de vulnérabilités qui ne font pas partie des fonctionnalités attendues. +

+ +

+ Par exception aux règles qui vous ont été données, vous êtes autorisés à rechercher des vulnérabilités sur tous les services (et leurs infrastructures afférentes) qui vous sont mis à disposition sur nemunai.re, selon les conditions suivantes : +

+ +
    +
  • vous ne devez pas entraver volontairement la progression de vos camarades ou le fonctionnement d'une partie de l'infrastructure ;
  • +
  • vous devez maîtriser les outils que vous utiliser : certains outils mal maîtrisés peuvent bombarder de requêtes un service au point de le faire tomber. Les services mis à votre disposition ne constituent pas une plateforme d'entraînement à l'utilisation de ces outils, vous avez des machines virtuelles pour cela ;
  • +
  • vous devez rapporter rapidement à bounty@nemunai.re tous les bugs ou vulnérabilités que vous découvrez.
  • +
+ +

+ En plus du maintien des bénéfices que vous avez éventuellement pu obtenir (par exemple changer une note), vous obtiendrez également un bonus sur votre note finale. + À titre indicatif, voici ce à quoi vous pouvez vous attendre : +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Exemples de vulnérabilitésPoints
Faute d'orthographe/grammaire, non conformité, bug, ...crédité dans le commit
XSS, CSRF, injection SQL/LDAP, ...1 point
Violation de permission, fuite d'informations, ...2 points
Exécution arbitraire de code3 points
Exécution arbitraire de code sur l'hôte5 points
+
+ +

+ Lorsque vous découvrez une vulnérabilité en groupe, précisez les noms et le rôle que chacun a eu dans la découverte. +

+ +
+ + + À toute fin utile, l'usage et la non-divulgation d'une vulnérabilité sont sanctionnables. + +
+ +

Hall of Fame

+ +
+
+ Il était toujours possible de répondre aux questionnaires après l'heure de clôture. + +2 pts +
+
+
+ mahe.charpy + albin.parou + sebastien.januszczak + clement.lanata + alexandre.delorme + justin.puchelle +
+

+ Divulguée et corrigée le 19 novembre 2021. + Commit +

+
+
+ +
diff --git a/atsebayt/src/routes/grades/[promo].svelte b/atsebayt/src/routes/grades/[promo].svelte new file mode 100644 index 0000000..8ec24ef --- /dev/null +++ b/atsebayt/src/routes/grades/[promo].svelte @@ -0,0 +1,17 @@ + + + + + diff --git a/atsebayt/src/routes/grades/index.svelte b/atsebayt/src/routes/grades/index.svelte new file mode 100644 index 0000000..026f5ce --- /dev/null +++ b/atsebayt/src/routes/grades/index.svelte @@ -0,0 +1,5 @@ + + + diff --git a/atsebayt/src/routes/index.svelte b/atsebayt/src/routes/index.svelte new file mode 100644 index 0000000..24d274f --- /dev/null +++ b/atsebayt/src/routes/index.svelte @@ -0,0 +1,29 @@ + + +
+
+ {#if $user} + avatar {$user.login} +

+ Bienvenue {$user.firstname} ! +

+
+ +

Tu as fait les rendus suivants :

+ +

Voici la liste des questionnaires :

+ {:else} +

+ Vous voici arrivés sur le site dédié aux cours d'Administration Linux avancée, du FIC et de Virtualisation légère. +

+

+ Vous devez vous identifier pour accéder au contenu. +

+ {/if} + +
+
diff --git a/atsebayt/src/routes/surveys/[sid].svelte b/atsebayt/src/routes/surveys/[sid].svelte new file mode 100644 index 0000000..123aa47 --- /dev/null +++ b/atsebayt/src/routes/surveys/[sid].svelte @@ -0,0 +1,62 @@ + + + + +{#await getSurvey(sid)} +
+
+ Chargement du questionnaire … +
+{:then survey} + {#if $user && $user.is_admin} + + + {/if} +
+

+ < + {survey.title} +

+ +
+ + {#if $user.is_admin && edit} + edit = false} /> + {/if} + + {#await getQuestions(survey.id)} +
+
+ Chargement des questions … +
+ {:then questions} + + {/await} +{:catch error} +
+

+ < + Questionnaire introuvable +

+ {error} +
+{/await} diff --git a/atsebayt/src/routes/surveys/index.svelte b/atsebayt/src/routes/surveys/index.svelte new file mode 100644 index 0000000..c326140 --- /dev/null +++ b/atsebayt/src/routes/surveys/index.svelte @@ -0,0 +1,8 @@ + + +
+ +
diff --git a/atsebayt/src/routes/users/[uid]/index.svelte b/atsebayt/src/routes/users/[uid]/index.svelte new file mode 100644 index 0000000..da85176 --- /dev/null +++ b/atsebayt/src/routes/users/[uid]/index.svelte @@ -0,0 +1,111 @@ + + + + +{#await getUser(uid)} +

+ Étudiant +

+ +
+
+ Chargement des détails… +
+{:then student} +
+
+

+ + {#if student.firstname} + {student.firstname} {student.lastname} + {:else} + {student.login} + {/if} +

+ {#if student.promo} + {student.promo} + {/if} +
+
+
+
+ + avatar + +
+
+
+
ID
+
{student.id}
+ +
Login
+
+ + {student.login} + +
+ +
E-mail
+
{student.email}
+ +
Nom
+
{student.lastname}
+ +
Prénom
+
{student.firstname}
+ +
Inscription
+
{student.time}
+ +
Groupes
+
+
    + {#each student.groups.split(',').slice(1) as g, gid (gid)} +
  • + {g} +
  • + {/each} +
+
+ +
Admin
+
{student.is_admin?"Oui":"Non"}
+
+
+
+
+
+ +

+ Questionnaires +

+
+ +
+{/await} diff --git a/atsebayt/src/routes/users/[uid]/surveys/[sid].svelte b/atsebayt/src/routes/users/[uid]/surveys/[sid].svelte new file mode 100644 index 0000000..592b9bd --- /dev/null +++ b/atsebayt/src/routes/users/[uid]/surveys/[sid].svelte @@ -0,0 +1,64 @@ + + + + +{#await getUser(uid)} +

+ Étudiant +

+ +
+
+ Chargement des détails… +
+{:then student} + {#await getSurvey(sid)} +
+
+ Chargement du questionnaire … +
+ {:then survey} +
+

+ {student.login} / + {survey.title} +

+ +
+ + {#await getQuestions(survey.id)} +
+
+ Chargement des questions … +
+ {:then questions} + + {/await} + {:catch error} +
+

+ < + Questionnaire introuvable +

+ {error} +
+ {/await} +{/await} diff --git a/atsebayt/src/routes/users/[uid]/surveys/index.svelte b/atsebayt/src/routes/users/[uid]/surveys/index.svelte new file mode 100644 index 0000000..5d01356 --- /dev/null +++ b/atsebayt/src/routes/users/[uid]/surveys/index.svelte @@ -0,0 +1,64 @@ + + + + +{#await getUser(uid)} +

+ Étudiant +

+ +
+
+ Chargement des détails… +
+{:then student} +
+
+

+ + + {#if student.firstname} + {student.firstname} {student.lastname} + {:else} + {student.login} + {/if} +

+ {#if student.promo} + {student.promo} + {/if} +
+
+ +

+ Questionnaires +

+
+ +
+{/await} diff --git a/atsebayt/src/routes/users/index.svelte b/atsebayt/src/routes/users/index.svelte new file mode 100644 index 0000000..c7c6980 --- /dev/null +++ b/atsebayt/src/routes/users/index.svelte @@ -0,0 +1,79 @@ + + +{#if $user && $user.is_admin} + + + + {#await getPromos() then promos} +
+ +
+ {/await} +{/if} +

+ Étudiants +

+ +{#await getUsers()} +
+
+ Chargement des étudiants … +
+{:then users} + + + + + + + + + + + + + + {#each users.filter((u) => (filterPromo === "" || filterPromo === u.promo)) as u, uid (u.id)} + + + + + + + + + + {/each} + +
IDLoginE-mailNomPrénomDate d'inscriptionPromo
+ {u.id} +
+ {u.login} +
+
+ {u.login} + + {u.email} + {u.lastname}{u.firstname} + + {u.promo}
+{/await} diff --git a/atsebayt/src/stores/user.js b/atsebayt/src/stores/user.js new file mode 100644 index 0000000..42aedce --- /dev/null +++ b/atsebayt/src/stores/user.js @@ -0,0 +1,27 @@ +import { writable } from 'svelte/store'; + +function createUserStore() { + const { subscribe, set, update } = writable(undefined); + + return { + subscribe, + set: (auth) => { + update((m) => auth); + }, + update: (res_auth, cb=null) => { + if (res_auth.status === 200) { + res_auth.json().then((auth) => { + update((m) => (Object.assign(m?m:{}, auth))); + + if (cb) { + cb(my); + } + }); + } else if (res_auth.status >= 400 && res_auth.status < 500) { + update((m) => (null)); + } + }, + }; +} + +export const user = createUserStore(); diff --git a/atsebayt/static/css/bootstrap.min.css b/atsebayt/static/css/bootstrap.min.css new file mode 100644 index 0000000..1ffbf34 --- /dev/null +++ b/atsebayt/static/css/bootstrap.min.css @@ -0,0 +1,12 @@ +/*! + * Bootswatch v5.1.1 + * Homepage: https://bootswatch.com + * Copyright 2012-2021 Thomas Park + * Licensed under MIT + * Based on Bootstrap +*//*! + * Bootstrap v5.1.1 (https://getbootstrap.com/) + * Copyright 2011-2021 The Bootstrap Authors + * Copyright 2011-2021 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */@import url(https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap);:root{--bs-blue:#3459e6;--bs-indigo:#6610f2;--bs-purple:#6f42c1;--bs-pink:#d63384;--bs-red:#da292e;--bs-orange:#f8765f;--bs-yellow:#f4bd61;--bs-green:#2fb380;--bs-teal:#20c997;--bs-cyan:#287bb5;--bs-white:#fff;--bs-gray:#6c757d;--bs-gray-dark:#343a40;--bs-gray-100:#f8f9fa;--bs-gray-200:#e9ecef;--bs-gray-300:#dee2e6;--bs-gray-400:#ced4da;--bs-gray-500:#adb5bd;--bs-gray-600:#6c757d;--bs-gray-700:#495057;--bs-gray-800:#343a40;--bs-gray-900:#212529;--bs-primary:#3459e6;--bs-secondary:#fff;--bs-success:#2fb380;--bs-info:#287bb5;--bs-warning:#f4bd61;--bs-danger:#da292e;--bs-light:#f8f9fa;--bs-dark:#212529;--bs-primary-rgb:52,89,230;--bs-secondary-rgb:255,255,255;--bs-success-rgb:47,179,128;--bs-info-rgb:40,123,181;--bs-warning-rgb:244,189,97;--bs-danger-rgb:218,41,46;--bs-light-rgb:248,249,250;--bs-dark-rgb:33,37,41;--bs-white-rgb:255,255,255;--bs-black-rgb:0,0,0;--bs-body-color-rgb:73,80,87;--bs-body-bg-rgb:255,255,255;--bs-font-sans-serif:Inter,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";--bs-font-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--bs-gradient:linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));--bs-body-font-family:var(--bs-font-sans-serif);--bs-body-font-size:1rem;--bs-body-font-weight:400;--bs-body-line-height:1.5;--bs-body-color:#495057;--bs-body-bg:#fff}*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2;color:#212529}.h1,h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){.h1,h1{font-size:2.5rem}}.h2,h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){.h2,h2{font-size:2rem}}.h3,h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){.h3,h3{font-size:1.75rem}}.h4,h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){.h4,h4{font-size:1.5rem}}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[data-bs-original-title],abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}.small,small{font-size:.875em}.mark,mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#3459e6;text-decoration:underline}a:hover{color:#2a47b8}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:var(--bs-font-monospace);font-size:1em;direction:ltr;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:#d63384;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:1rem;padding-bottom:1rem;color:#6c757d;text-align:left}th{font-weight:500;text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:calc(1.625rem + 4.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-1{font-size:5rem}}.display-2{font-size:calc(1.575rem + 3.9vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-2{font-size:4.5rem}}.display-3{font-size:calc(1.525rem + 3.3vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-3{font-size:4rem}}.display-4{font-size:calc(1.475rem + 2.7vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-4{font-size:3.5rem}}.display-5{font-size:calc(1.425rem + 2.1vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-5{font-size:3rem}}.display-6{font-size:calc(1.375rem + 1.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-6{font-size:2.5rem}}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:.875em;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote>:last-child{margin-bottom:0}.blockquote-footer{margin-top:-1rem;margin-bottom:1rem;font-size:.875em;color:#6c757d}.blockquote-footer::before{content:"\2014\00A0"}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;box-shadow:0 .125rem .25rem rgba(0,0,0,.075);max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:.875em;color:#6c757d}.container,.container-fluid,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{width:100%;padding-right:var(--bs-gutter-x,.75rem);padding-left:var(--bs-gutter-x,.75rem);margin-right:auto;margin-left:auto}@media (min-width:576px){.container,.container-sm{max-width:540px}}@media (min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}@media (min-width:1400px){.container,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{max-width:1320px}}.row{--bs-gutter-x:1.5rem;--bs-gutter-y:0;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-top:calc(var(--bs-gutter-y) * -1);margin-right:calc(var(--bs-gutter-x) * -.5);margin-left:calc(var(--bs-gutter-x) * -.5)}.row>*{-ms-flex-negative:0;flex-shrink:0;width:100%;max-width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5);margin-top:var(--bs-gutter-y)}.col{-ms-flex:1 0 0%;flex:1 0 0%}.row-cols-auto>*{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.row-cols-1>*{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.row-cols-2>*{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.row-cols-3>*{-ms-flex:0 0 auto;flex:0 0 auto;width:33.333333%}.row-cols-4>*{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.row-cols-5>*{-ms-flex:0 0 auto;flex:0 0 auto;width:20%}.row-cols-6>*{-ms-flex:0 0 auto;flex:0 0 auto;width:16.666667%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.col-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.333333%}.col-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.666667%}.col-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.col-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.333333%}.col-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.666667%}.col-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.col-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.333333%}.col-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.666667%}.col-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.col-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.333333%}.col-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.666667%}.col-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.offset-1{margin-left:8.333333%}.offset-2{margin-left:16.666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.333333%}.offset-5{margin-left:41.666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.333333%}.offset-8{margin-left:66.666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.333333%}.offset-11{margin-left:91.666667%}.g-0,.gx-0{--bs-gutter-x:0}.g-0,.gy-0{--bs-gutter-y:0}.g-1,.gx-1{--bs-gutter-x:0.25rem}.g-1,.gy-1{--bs-gutter-y:0.25rem}.g-2,.gx-2{--bs-gutter-x:0.5rem}.g-2,.gy-2{--bs-gutter-y:0.5rem}.g-3,.gx-3{--bs-gutter-x:1rem}.g-3,.gy-3{--bs-gutter-y:1rem}.g-4,.gx-4{--bs-gutter-x:1.5rem}.g-4,.gy-4{--bs-gutter-y:1.5rem}.g-5,.gx-5{--bs-gutter-x:3rem}.g-5,.gy-5{--bs-gutter-y:3rem}@media (min-width:576px){.col-sm{-ms-flex:1 0 0%;flex:1 0 0%}.row-cols-sm-auto>*{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.row-cols-sm-1>*{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.row-cols-sm-2>*{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.row-cols-sm-3>*{-ms-flex:0 0 auto;flex:0 0 auto;width:33.333333%}.row-cols-sm-4>*{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.row-cols-sm-5>*{-ms-flex:0 0 auto;flex:0 0 auto;width:20%}.row-cols-sm-6>*{-ms-flex:0 0 auto;flex:0 0 auto;width:16.666667%}.col-sm-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.col-sm-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.333333%}.col-sm-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.666667%}.col-sm-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.col-sm-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.333333%}.col-sm-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.666667%}.col-sm-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.col-sm-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.333333%}.col-sm-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.666667%}.col-sm-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.col-sm-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.333333%}.col-sm-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.666667%}.col-sm-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.333333%}.offset-sm-2{margin-left:16.666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.333333%}.offset-sm-5{margin-left:41.666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.333333%}.offset-sm-8{margin-left:66.666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.333333%}.offset-sm-11{margin-left:91.666667%}.g-sm-0,.gx-sm-0{--bs-gutter-x:0}.g-sm-0,.gy-sm-0{--bs-gutter-y:0}.g-sm-1,.gx-sm-1{--bs-gutter-x:0.25rem}.g-sm-1,.gy-sm-1{--bs-gutter-y:0.25rem}.g-sm-2,.gx-sm-2{--bs-gutter-x:0.5rem}.g-sm-2,.gy-sm-2{--bs-gutter-y:0.5rem}.g-sm-3,.gx-sm-3{--bs-gutter-x:1rem}.g-sm-3,.gy-sm-3{--bs-gutter-y:1rem}.g-sm-4,.gx-sm-4{--bs-gutter-x:1.5rem}.g-sm-4,.gy-sm-4{--bs-gutter-y:1.5rem}.g-sm-5,.gx-sm-5{--bs-gutter-x:3rem}.g-sm-5,.gy-sm-5{--bs-gutter-y:3rem}}@media (min-width:768px){.col-md{-ms-flex:1 0 0%;flex:1 0 0%}.row-cols-md-auto>*{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.row-cols-md-1>*{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.row-cols-md-2>*{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.row-cols-md-3>*{-ms-flex:0 0 auto;flex:0 0 auto;width:33.333333%}.row-cols-md-4>*{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.row-cols-md-5>*{-ms-flex:0 0 auto;flex:0 0 auto;width:20%}.row-cols-md-6>*{-ms-flex:0 0 auto;flex:0 0 auto;width:16.666667%}.col-md-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.col-md-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.333333%}.col-md-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.666667%}.col-md-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.col-md-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.333333%}.col-md-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.666667%}.col-md-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.col-md-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.333333%}.col-md-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.666667%}.col-md-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.col-md-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.333333%}.col-md-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.666667%}.col-md-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.333333%}.offset-md-2{margin-left:16.666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.333333%}.offset-md-5{margin-left:41.666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.333333%}.offset-md-8{margin-left:66.666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.333333%}.offset-md-11{margin-left:91.666667%}.g-md-0,.gx-md-0{--bs-gutter-x:0}.g-md-0,.gy-md-0{--bs-gutter-y:0}.g-md-1,.gx-md-1{--bs-gutter-x:0.25rem}.g-md-1,.gy-md-1{--bs-gutter-y:0.25rem}.g-md-2,.gx-md-2{--bs-gutter-x:0.5rem}.g-md-2,.gy-md-2{--bs-gutter-y:0.5rem}.g-md-3,.gx-md-3{--bs-gutter-x:1rem}.g-md-3,.gy-md-3{--bs-gutter-y:1rem}.g-md-4,.gx-md-4{--bs-gutter-x:1.5rem}.g-md-4,.gy-md-4{--bs-gutter-y:1.5rem}.g-md-5,.gx-md-5{--bs-gutter-x:3rem}.g-md-5,.gy-md-5{--bs-gutter-y:3rem}}@media (min-width:992px){.col-lg{-ms-flex:1 0 0%;flex:1 0 0%}.row-cols-lg-auto>*{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.row-cols-lg-1>*{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.row-cols-lg-2>*{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.row-cols-lg-3>*{-ms-flex:0 0 auto;flex:0 0 auto;width:33.333333%}.row-cols-lg-4>*{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.row-cols-lg-5>*{-ms-flex:0 0 auto;flex:0 0 auto;width:20%}.row-cols-lg-6>*{-ms-flex:0 0 auto;flex:0 0 auto;width:16.666667%}.col-lg-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.col-lg-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.333333%}.col-lg-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.666667%}.col-lg-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.col-lg-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.333333%}.col-lg-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.666667%}.col-lg-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.col-lg-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.333333%}.col-lg-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.666667%}.col-lg-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.col-lg-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.333333%}.col-lg-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.666667%}.col-lg-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.333333%}.offset-lg-2{margin-left:16.666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.333333%}.offset-lg-5{margin-left:41.666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.333333%}.offset-lg-8{margin-left:66.666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.333333%}.offset-lg-11{margin-left:91.666667%}.g-lg-0,.gx-lg-0{--bs-gutter-x:0}.g-lg-0,.gy-lg-0{--bs-gutter-y:0}.g-lg-1,.gx-lg-1{--bs-gutter-x:0.25rem}.g-lg-1,.gy-lg-1{--bs-gutter-y:0.25rem}.g-lg-2,.gx-lg-2{--bs-gutter-x:0.5rem}.g-lg-2,.gy-lg-2{--bs-gutter-y:0.5rem}.g-lg-3,.gx-lg-3{--bs-gutter-x:1rem}.g-lg-3,.gy-lg-3{--bs-gutter-y:1rem}.g-lg-4,.gx-lg-4{--bs-gutter-x:1.5rem}.g-lg-4,.gy-lg-4{--bs-gutter-y:1.5rem}.g-lg-5,.gx-lg-5{--bs-gutter-x:3rem}.g-lg-5,.gy-lg-5{--bs-gutter-y:3rem}}@media (min-width:1200px){.col-xl{-ms-flex:1 0 0%;flex:1 0 0%}.row-cols-xl-auto>*{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.row-cols-xl-1>*{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.row-cols-xl-2>*{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.row-cols-xl-3>*{-ms-flex:0 0 auto;flex:0 0 auto;width:33.333333%}.row-cols-xl-4>*{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.row-cols-xl-5>*{-ms-flex:0 0 auto;flex:0 0 auto;width:20%}.row-cols-xl-6>*{-ms-flex:0 0 auto;flex:0 0 auto;width:16.666667%}.col-xl-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.col-xl-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.333333%}.col-xl-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.666667%}.col-xl-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.col-xl-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.333333%}.col-xl-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.666667%}.col-xl-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.col-xl-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.333333%}.col-xl-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.666667%}.col-xl-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.col-xl-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.333333%}.col-xl-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.666667%}.col-xl-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.333333%}.offset-xl-2{margin-left:16.666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.333333%}.offset-xl-5{margin-left:41.666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.333333%}.offset-xl-8{margin-left:66.666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.333333%}.offset-xl-11{margin-left:91.666667%}.g-xl-0,.gx-xl-0{--bs-gutter-x:0}.g-xl-0,.gy-xl-0{--bs-gutter-y:0}.g-xl-1,.gx-xl-1{--bs-gutter-x:0.25rem}.g-xl-1,.gy-xl-1{--bs-gutter-y:0.25rem}.g-xl-2,.gx-xl-2{--bs-gutter-x:0.5rem}.g-xl-2,.gy-xl-2{--bs-gutter-y:0.5rem}.g-xl-3,.gx-xl-3{--bs-gutter-x:1rem}.g-xl-3,.gy-xl-3{--bs-gutter-y:1rem}.g-xl-4,.gx-xl-4{--bs-gutter-x:1.5rem}.g-xl-4,.gy-xl-4{--bs-gutter-y:1.5rem}.g-xl-5,.gx-xl-5{--bs-gutter-x:3rem}.g-xl-5,.gy-xl-5{--bs-gutter-y:3rem}}@media (min-width:1400px){.col-xxl{-ms-flex:1 0 0%;flex:1 0 0%}.row-cols-xxl-auto>*{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.row-cols-xxl-1>*{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.row-cols-xxl-2>*{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.row-cols-xxl-3>*{-ms-flex:0 0 auto;flex:0 0 auto;width:33.333333%}.row-cols-xxl-4>*{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.row-cols-xxl-5>*{-ms-flex:0 0 auto;flex:0 0 auto;width:20%}.row-cols-xxl-6>*{-ms-flex:0 0 auto;flex:0 0 auto;width:16.666667%}.col-xxl-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.col-xxl-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.333333%}.col-xxl-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.666667%}.col-xxl-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.col-xxl-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.333333%}.col-xxl-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.666667%}.col-xxl-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.col-xxl-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.333333%}.col-xxl-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.666667%}.col-xxl-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.col-xxl-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.333333%}.col-xxl-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.666667%}.col-xxl-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.offset-xxl-0{margin-left:0}.offset-xxl-1{margin-left:8.333333%}.offset-xxl-2{margin-left:16.666667%}.offset-xxl-3{margin-left:25%}.offset-xxl-4{margin-left:33.333333%}.offset-xxl-5{margin-left:41.666667%}.offset-xxl-6{margin-left:50%}.offset-xxl-7{margin-left:58.333333%}.offset-xxl-8{margin-left:66.666667%}.offset-xxl-9{margin-left:75%}.offset-xxl-10{margin-left:83.333333%}.offset-xxl-11{margin-left:91.666667%}.g-xxl-0,.gx-xxl-0{--bs-gutter-x:0}.g-xxl-0,.gy-xxl-0{--bs-gutter-y:0}.g-xxl-1,.gx-xxl-1{--bs-gutter-x:0.25rem}.g-xxl-1,.gy-xxl-1{--bs-gutter-y:0.25rem}.g-xxl-2,.gx-xxl-2{--bs-gutter-x:0.5rem}.g-xxl-2,.gy-xxl-2{--bs-gutter-y:0.5rem}.g-xxl-3,.gx-xxl-3{--bs-gutter-x:1rem}.g-xxl-3,.gy-xxl-3{--bs-gutter-y:1rem}.g-xxl-4,.gx-xxl-4{--bs-gutter-x:1.5rem}.g-xxl-4,.gy-xxl-4{--bs-gutter-y:1.5rem}.g-xxl-5,.gx-xxl-5{--bs-gutter-x:3rem}.g-xxl-5,.gy-xxl-5{--bs-gutter-y:3rem}}.table{--bs-table-bg:transparent;--bs-table-accent-bg:transparent;--bs-table-striped-color:#495057;--bs-table-striped-bg:rgba(0, 0, 0, 0.05);--bs-table-active-color:#495057;--bs-table-active-bg:rgba(0, 0, 0, 0.1);--bs-table-hover-color:#495057;--bs-table-hover-bg:rgba(0, 0, 0, 0.075);width:100%;margin-bottom:1rem;color:#495057;vertical-align:top;border-color:#dee2e6}.table>:not(caption)>*>*{padding:1rem 1rem;background-color:var(--bs-table-bg);border-bottom-width:1px;box-shadow:inset 0 0 0 9999px var(--bs-table-accent-bg)}.table>tbody{vertical-align:inherit}.table>thead{vertical-align:bottom}.table>:not(:last-child)>:last-child>*{border-bottom-color:currentColor}.caption-top{caption-side:top}.table-sm>:not(caption)>*>*{padding:.5rem .5rem}.table-bordered>:not(caption)>*{border-width:1px 0}.table-bordered>:not(caption)>*>*{border-width:0 1px}.table-borderless>:not(caption)>*>*{border-bottom-width:0}.table-striped>tbody>tr:nth-of-type(odd){--bs-table-accent-bg:var(--bs-table-striped-bg);color:var(--bs-table-striped-color)}.table-active{--bs-table-accent-bg:var(--bs-table-active-bg);color:var(--bs-table-active-color)}.table-hover>tbody>tr:hover{--bs-table-accent-bg:var(--bs-table-hover-bg);color:var(--bs-table-hover-color)}.table-primary{--bs-table-bg:#d6defa;--bs-table-striped-bg:#cbd3ee;--bs-table-striped-color:#000;--bs-table-active-bg:#c1c8e1;--bs-table-active-color:#fff;--bs-table-hover-bg:#c6cde7;--bs-table-hover-color:#000;color:#000;border-color:#c1c8e1}.table-secondary{--bs-table-bg:white;--bs-table-striped-bg:#f2f2f2;--bs-table-striped-color:#000;--bs-table-active-bg:#e6e6e6;--bs-table-active-color:#000;--bs-table-hover-bg:#ececec;--bs-table-hover-color:#000;color:#000;border-color:#e6e6e6}.table-success{--bs-table-bg:#d5f0e6;--bs-table-striped-bg:#cae4db;--bs-table-striped-color:#000;--bs-table-active-bg:#c0d8cf;--bs-table-active-color:#000;--bs-table-hover-bg:#c5ded5;--bs-table-hover-color:#000;color:#000;border-color:#c0d8cf}.table-info{--bs-table-bg:#d4e5f0;--bs-table-striped-bg:#c9dae4;--bs-table-striped-color:#000;--bs-table-active-bg:#bfced8;--bs-table-active-color:#000;--bs-table-hover-bg:#c4d4de;--bs-table-hover-color:#000;color:#000;border-color:#bfced8}.table-warning{--bs-table-bg:#fdf2df;--bs-table-striped-bg:#f0e6d4;--bs-table-striped-color:#000;--bs-table-active-bg:#e4dac9;--bs-table-active-color:#000;--bs-table-hover-bg:#eae0ce;--bs-table-hover-color:#000;color:#000;border-color:#e4dac9}.table-danger{--bs-table-bg:#f8d4d5;--bs-table-striped-bg:#ecc9ca;--bs-table-striped-color:#000;--bs-table-active-bg:#dfbfc0;--bs-table-active-color:#fff;--bs-table-hover-bg:#e5c4c5;--bs-table-hover-color:#000;color:#000;border-color:#dfbfc0}.table-light{--bs-table-bg:#f8f9fa;--bs-table-striped-bg:#ecedee;--bs-table-striped-color:#000;--bs-table-active-bg:#dfe0e1;--bs-table-active-color:#000;--bs-table-hover-bg:#e5e6e7;--bs-table-hover-color:#000;color:#000;border-color:#dfe0e1}.table-dark{--bs-table-bg:#212529;--bs-table-striped-bg:#2c3034;--bs-table-striped-color:#fff;--bs-table-active-bg:#373b3e;--bs-table-active-color:#fff;--bs-table-hover-bg:#323539;--bs-table-hover-color:#fff;color:#fff;border-color:#373b3e}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}@media (max-width:575.98px){.table-responsive-sm{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:767.98px){.table-responsive-md{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:991.98px){.table-responsive-lg{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1199.98px){.table-responsive-xl{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1399.98px){.table-responsive-xxl{overflow-x:auto;-webkit-overflow-scrolling:touch}}.form-label{margin-bottom:.5rem;font-weight:500}.col-form-label{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);margin-bottom:0;font-size:inherit;font-weight:500;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem}.form-text{margin-top:.25rem;font-size:.875em;color:#6c757d}.form-control{display:block;width:100%;padding:.5rem 1rem;font-size:.875rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:.25rem;box-shadow:0 1px 2px rgba(0,0,0,.05);transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control[type=file]{overflow:hidden}.form-control[type=file]:not(:disabled):not([readonly]){cursor:pointer}.form-control:focus{color:#495057;background-color:#fff;border-color:#9aacf3;outline:0;box-shadow:0 1px 2px rgba(0,0,0,.05),0 0 0 .25rem rgba(52,89,230,.25)}.form-control::-webkit-date-and-time-value{height:1.5em}.form-control::-webkit-input-placeholder{color:#6c757d;opacity:1}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control:-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}.form-control::file-selector-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;-moz-margin-end:1rem;margin-inline-end:1rem;color:#495057;background-color:#f8f9fa;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::file-selector-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::file-selector-button{background-color:#ecedee}.form-control::-webkit-file-upload-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem;color:#495057;background-color:#f8f9fa;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;-webkit-transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::-webkit-file-upload-button{-webkit-transition:none;transition:none}}.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button{background-color:#ecedee}.form-control-plaintext{display:block;width:100%;padding:.5rem 0;margin-bottom:0;line-height:1.5;color:#495057;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{min-height:calc(1.5em + .5rem + 2px);padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.form-control-sm::file-selector-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;-moz-margin-end:.5rem;margin-inline-end:.5rem}.form-control-sm::-webkit-file-upload-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-lg{min-height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.form-control-lg::file-selector-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;-moz-margin-end:1rem;margin-inline-end:1rem}.form-control-lg::-webkit-file-upload-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}textarea.form-control{min-height:calc(1.5em + 1rem + 2px)}textarea.form-control-sm{min-height:calc(1.5em + .5rem + 2px)}textarea.form-control-lg{min-height:calc(1.5em + 1rem + 2px)}.form-control-color{width:3rem;height:auto;padding:.5rem}.form-control-color:not(:disabled):not([readonly]){cursor:pointer}.form-control-color::-moz-color-swatch{height:1.5em;border-radius:.25rem}.form-control-color::-webkit-color-swatch{height:1.5em;border-radius:.25rem}.form-select{display:block;width:100%;padding:.5rem 3rem .5rem 1rem;-moz-padding-start:calc(1rem - 3px);font-size:.875rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right 1rem center;background-size:16px 12px;border:1px solid #ced4da;border-radius:.25rem;box-shadow:inset 0 1px 2px rgba(0,0,0,.075);transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-select{transition:none}}.form-select:focus{border-color:#9aacf3;outline:0;box-shadow:inset 0 1px 2px rgba(0,0,0,.075),0 0 0 .25rem rgba(52,89,230,.25)}.form-select[multiple],.form-select[size]:not([size="1"]){padding-right:1rem;background-image:none}.form-select:disabled{background-color:#e9ecef}.form-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #495057}.form-select-sm{padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.form-select-lg{padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}.form-check{display:block;min-height:1.5rem;padding-left:1.5em;margin-bottom:.125rem}.form-check .form-check-input{float:left;margin-left:-1.5em}.form-check-input{width:1em;height:1em;margin-top:.25em;vertical-align:top;background-color:#fff;background-repeat:no-repeat;background-position:center;background-size:contain;border:1px solid rgba(0,0,0,.25);-webkit-appearance:none;-moz-appearance:none;appearance:none;-webkit-print-color-adjust:exact;color-adjust:exact}.form-check-input[type=checkbox]{border-radius:.25em}.form-check-input[type=radio]{border-radius:50%}.form-check-input:active{-webkit-filter:brightness(90%);filter:brightness(90%)}.form-check-input:focus{border-color:#9aacf3;outline:0;box-shadow:0 0 0 .25rem rgba(52,89,230,.25)}.form-check-input:checked{background-color:#3459e6;border-color:#3459e6}.form-check-input:checked[type=checkbox]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e")}.form-check-input:checked[type=radio]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e")}.form-check-input[type=checkbox]:indeterminate{background-color:#3459e6;border-color:#3459e6;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e")}.form-check-input:disabled{pointer-events:none;-webkit-filter:none;filter:none;opacity:.5}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{opacity:.5}.form-switch{padding-left:2.5em}.form-switch .form-check-input{width:2em;margin-left:-2.5em;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e");background-position:left center;border-radius:2em;transition:background-position .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-switch .form-check-input{transition:none}}.form-switch .form-check-input:focus{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%239aacf3'/%3e%3c/svg%3e")}.form-switch .form-check-input:checked{background-position:right center;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.form-check-inline{display:inline-block;margin-right:1rem}.btn-check{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.btn-check:disabled+.btn,.btn-check[disabled]+.btn{pointer-events:none;-webkit-filter:none;filter:none;opacity:.65}.form-range{width:100%;height:1.5rem;padding:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(52,89,230,.25)}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(52,89,230,.25)}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#3459e6;border:0;border-radius:1rem;box-shadow:0 .1rem .25rem rgba(0,0,0,.1);-webkit-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.form-range::-webkit-slider-thumb:active{background-color:#c2cdf8}.form-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem;box-shadow:inset 0 1px 2px rgba(0,0,0,.075)}.form-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#3459e6;border:0;border-radius:1rem;box-shadow:0 .1rem .25rem rgba(0,0,0,.1);-moz-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-range::-moz-range-thumb{-moz-transition:none;transition:none}}.form-range::-moz-range-thumb:active{background-color:#c2cdf8}.form-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem;box-shadow:inset 0 1px 2px rgba(0,0,0,.075)}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.form-range:disabled::-moz-range-thumb{background-color:#adb5bd}.form-floating{position:relative}.form-floating>.form-control,.form-floating>.form-select{height:calc(3.5rem + 2px);line-height:1.25}.form-floating>label{position:absolute;top:0;left:0;height:100%;padding:1rem 1rem;pointer-events:none;border:1px solid transparent;-webkit-transform-origin:0 0;transform-origin:0 0;transition:opacity .1s ease-in-out,-webkit-transform .1s ease-in-out;transition:opacity .1s ease-in-out,transform .1s ease-in-out;transition:opacity .1s ease-in-out,transform .1s ease-in-out,-webkit-transform .1s ease-in-out}@media (prefers-reduced-motion:reduce){.form-floating>label{transition:none}}.form-floating>.form-control{padding:1rem 1rem}.form-floating>.form-control::-webkit-input-placeholder{color:transparent}.form-floating>.form-control::-moz-placeholder{color:transparent}.form-floating>.form-control:-ms-input-placeholder{color:transparent}.form-floating>.form-control::-ms-input-placeholder{color:transparent}.form-floating>.form-control::placeholder{color:transparent}.form-floating>.form-control:not(:-moz-placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:not(:-ms-input-placeholder){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-select{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:not(:-moz-placeholder-shown)~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:not(:-ms-input-placeholder)~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:focus~label,.form-floating>.form-control:not(:placeholder-shown)~label,.form-floating>.form-select~label{opacity:.65;-webkit-transform:scale(.85) translateY(-.5rem) translateX(.15rem);transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:-webkit-autofill~label{opacity:.65;-webkit-transform:scale(.85) translateY(-.5rem) translateX(.15rem);transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.input-group{position:relative;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:stretch;align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-select{position:relative;-ms-flex:1 1 auto;flex:1 1 auto;width:1%;min-width:0}.input-group>.form-control:focus,.input-group>.form-select:focus{z-index:3}.input-group .btn{position:relative;z-index:2}.input-group .btn:focus{z-index:3}.input-group-text{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;padding:.5rem 1rem;font-size:.875rem;font-weight:400;line-height:1.5;color:#495057;text-align:center;white-space:nowrap;background-color:#f8f9fa;border:1px solid #ced4da;border-radius:.25rem}.input-group-lg>.btn,.input-group-lg>.form-control,.input-group-lg>.form-select,.input-group-lg>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.input-group-sm>.btn,.input-group-sm>.form-control,.input-group-sm>.form-select,.input-group-sm>.input-group-text{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:4rem}.input-group:not(.has-validation)>.dropdown-toggle:nth-last-child(n+3),.input-group:not(.has-validation)>:not(:last-child):not(.dropdown-toggle):not(.dropdown-menu){border-top-right-radius:0;border-bottom-right-radius:0}.input-group.has-validation>.dropdown-toggle:nth-last-child(n+4),.input-group.has-validation>:nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:-1px;border-top-left-radius:0;border-bottom-left-radius:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#2fb380}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:rgba(47,179,128,.9);border-radius:.25rem}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:#2fb380;padding-right:calc(1.5em + 1rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%232fb380' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .25rem) center;background-size:calc(.75em + .5rem) calc(.75em + .5rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#2fb380;box-shadow:0 0 0 .25rem rgba(47,179,128,.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + 1rem);background-position:top calc(.375em + .25rem) right calc(.375em + .25rem)}.form-select.is-valid,.was-validated .form-select:valid{border-color:#2fb380}.form-select.is-valid:not([multiple]):not([size]),.form-select.is-valid:not([multiple])[size="1"],.was-validated .form-select:valid:not([multiple]):not([size]),.was-validated .form-select:valid:not([multiple])[size="1"]{padding-right:5.5rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%232fb380' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-position:right 1rem center,center right 3rem;background-size:16px 12px,calc(.75em + .5rem) calc(.75em + .5rem)}.form-select.is-valid:focus,.was-validated .form-select:valid:focus{border-color:#2fb380;box-shadow:0 0 0 .25rem rgba(47,179,128,.25)}.form-check-input.is-valid,.was-validated .form-check-input:valid{border-color:#2fb380}.form-check-input.is-valid:checked,.was-validated .form-check-input:valid:checked{background-color:#2fb380}.form-check-input.is-valid:focus,.was-validated .form-check-input:valid:focus{box-shadow:0 0 0 .25rem rgba(47,179,128,.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#2fb380}.form-check-inline .form-check-input~.valid-feedback{margin-left:.5em}.input-group .form-control.is-valid,.input-group .form-select.is-valid,.was-validated .input-group .form-control:valid,.was-validated .input-group .form-select:valid{z-index:1}.input-group .form-control.is-valid:focus,.input-group .form-select.is-valid:focus,.was-validated .input-group .form-control:valid:focus,.was-validated .input-group .form-select:valid:focus{z-index:3}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#da292e}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:rgba(218,41,46,.9);border-radius:.25rem}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:#da292e;padding-right:calc(1.5em + 1rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23da292e'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23da292e' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .25rem) center;background-size:calc(.75em + .5rem) calc(.75em + .5rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#da292e;box-shadow:0 0 0 .25rem rgba(218,41,46,.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + 1rem);background-position:top calc(.375em + .25rem) right calc(.375em + .25rem)}.form-select.is-invalid,.was-validated .form-select:invalid{border-color:#da292e}.form-select.is-invalid:not([multiple]):not([size]),.form-select.is-invalid:not([multiple])[size="1"],.was-validated .form-select:invalid:not([multiple]):not([size]),.was-validated .form-select:invalid:not([multiple])[size="1"]{padding-right:5.5rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23da292e'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23da292e' stroke='none'/%3e%3c/svg%3e");background-position:right 1rem center,center right 3rem;background-size:16px 12px,calc(.75em + .5rem) calc(.75em + .5rem)}.form-select.is-invalid:focus,.was-validated .form-select:invalid:focus{border-color:#da292e;box-shadow:0 0 0 .25rem rgba(218,41,46,.25)}.form-check-input.is-invalid,.was-validated .form-check-input:invalid{border-color:#da292e}.form-check-input.is-invalid:checked,.was-validated .form-check-input:invalid:checked{background-color:#da292e}.form-check-input.is-invalid:focus,.was-validated .form-check-input:invalid:focus{box-shadow:0 0 0 .25rem rgba(218,41,46,.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#da292e}.form-check-inline .form-check-input~.invalid-feedback{margin-left:.5em}.input-group .form-control.is-invalid,.input-group .form-select.is-invalid,.was-validated .input-group .form-control:invalid,.was-validated .input-group .form-select:invalid{z-index:2}.input-group .form-control.is-invalid:focus,.input-group .form-select.is-invalid:focus,.was-validated .input-group .form-control:invalid:focus,.was-validated .input-group .form-select:invalid:focus{z-index:3}.btn{display:inline-block;font-weight:500;line-height:1.5;color:#495057;text-align:center;text-decoration:none;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:transparent;border:1px solid transparent;padding:.5rem 1rem;font-size:.875rem;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#495057}.btn-check:focus+.btn,.btn:focus{outline:0;box-shadow:0 1px 2px rgba(0,0,0,.05)}.btn-check:active+.btn,.btn-check:checked+.btn,.btn.active,.btn:active{box-shadow:0 1px 2px rgba(0,0,0,.05)}.btn-check:active+.btn:focus,.btn-check:checked+.btn:focus,.btn.active:focus,.btn:active:focus{box-shadow:0 1px 2px rgba(0,0,0,.05),0 1px 2px rgba(0,0,0,.05)}.btn.disabled,.btn:disabled,fieldset:disabled .btn{pointer-events:none;opacity:.65;box-shadow:none}.btn-primary{color:#fff;background-color:#3459e6;border-color:#3459e6;box-shadow:0 1px 2px rgba(0,0,0,.05)}.btn-primary:hover{color:#fff;background-color:#2c4cc4;border-color:#2a47b8}.btn-check:focus+.btn-primary,.btn-primary:focus{color:#fff;background-color:#2c4cc4;border-color:#2a47b8;box-shadow:0 1px 2px rgba(0,0,0,.05),0 0 0 .25rem rgba(82,114,234,.5)}.btn-check:active+.btn-primary,.btn-check:checked+.btn-primary,.btn-primary.active,.btn-primary:active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#2a47b8;border-color:#2743ad}.btn-check:active+.btn-primary:focus,.btn-check:checked+.btn-primary:focus,.btn-primary.active:focus,.btn-primary:active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 1px 2px rgba(0,0,0,.05),0 0 0 .25rem rgba(82,114,234,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#3459e6;border-color:#3459e6}.btn-secondary{color:#000;background-color:#fff;border-color:#fff;box-shadow:0 1px 2px rgba(0,0,0,.05)}.btn-secondary:hover{color:#000;background-color:#fff;border-color:#fff}.btn-check:focus+.btn-secondary,.btn-secondary:focus{color:#000;background-color:#fff;border-color:#fff;box-shadow:0 1px 2px rgba(0,0,0,.05),0 0 0 .25rem rgba(217,217,217,.5)}.btn-check:active+.btn-secondary,.btn-check:checked+.btn-secondary,.btn-secondary.active,.btn-secondary:active,.show>.btn-secondary.dropdown-toggle{color:#000;background-color:#fff;border-color:#fff}.btn-check:active+.btn-secondary:focus,.btn-check:checked+.btn-secondary:focus,.btn-secondary.active:focus,.btn-secondary:active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 1px 2px rgba(0,0,0,.05),0 0 0 .25rem rgba(217,217,217,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#000;background-color:#fff;border-color:#fff}.btn-success{color:#fff;background-color:#2fb380;border-color:#2fb380;box-shadow:0 1px 2px rgba(0,0,0,.05)}.btn-success:hover{color:#fff;background-color:#28986d;border-color:#268f66}.btn-check:focus+.btn-success,.btn-success:focus{color:#fff;background-color:#28986d;border-color:#268f66;box-shadow:0 1px 2px rgba(0,0,0,.05),0 0 0 .25rem rgba(78,190,147,.5)}.btn-check:active+.btn-success,.btn-check:checked+.btn-success,.btn-success.active,.btn-success:active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#268f66;border-color:#238660}.btn-check:active+.btn-success:focus,.btn-check:checked+.btn-success:focus,.btn-success.active:focus,.btn-success:active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 1px 2px rgba(0,0,0,.05),0 0 0 .25rem rgba(78,190,147,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#2fb380;border-color:#2fb380}.btn-info{color:#fff;background-color:#287bb5;border-color:#287bb5;box-shadow:0 1px 2px rgba(0,0,0,.05)}.btn-info:hover{color:#fff;background-color:#22699a;border-color:#206291}.btn-check:focus+.btn-info,.btn-info:focus{color:#fff;background-color:#22699a;border-color:#206291;box-shadow:0 1px 2px rgba(0,0,0,.05),0 0 0 .25rem rgba(72,143,192,.5)}.btn-check:active+.btn-info,.btn-check:checked+.btn-info,.btn-info.active,.btn-info:active,.show>.btn-info.dropdown-toggle{color:#fff;background-color:#206291;border-color:#1e5c88}.btn-check:active+.btn-info:focus,.btn-check:checked+.btn-info:focus,.btn-info.active:focus,.btn-info:active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 1px 2px rgba(0,0,0,.05),0 0 0 .25rem rgba(72,143,192,.5)}.btn-info.disabled,.btn-info:disabled{color:#fff;background-color:#287bb5;border-color:#287bb5}.btn-warning{color:#fff;background-color:#f4bd61;border-color:#f4bd61;box-shadow:0 1px 2px rgba(0,0,0,.05)}.btn-warning:hover{color:#fff;background-color:#cfa152;border-color:#c3974e}.btn-check:focus+.btn-warning,.btn-warning:focus{color:#fff;background-color:#cfa152;border-color:#c3974e;box-shadow:0 1px 2px rgba(0,0,0,.05),0 0 0 .25rem rgba(246,199,121,.5)}.btn-check:active+.btn-warning,.btn-check:checked+.btn-warning,.btn-warning.active,.btn-warning:active,.show>.btn-warning.dropdown-toggle{color:#fff;background-color:#c3974e;border-color:#b78e49}.btn-check:active+.btn-warning:focus,.btn-check:checked+.btn-warning:focus,.btn-warning.active:focus,.btn-warning:active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 1px 2px rgba(0,0,0,.05),0 0 0 .25rem rgba(246,199,121,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#fff;background-color:#f4bd61;border-color:#f4bd61}.btn-danger{color:#fff;background-color:#da292e;border-color:#da292e;box-shadow:0 1px 2px rgba(0,0,0,.05)}.btn-danger:hover{color:#fff;background-color:#b92327;border-color:#ae2125}.btn-check:focus+.btn-danger,.btn-danger:focus{color:#fff;background-color:#b92327;border-color:#ae2125;box-shadow:0 1px 2px rgba(0,0,0,.05),0 0 0 .25rem rgba(224,73,77,.5)}.btn-check:active+.btn-danger,.btn-check:checked+.btn-danger,.btn-danger.active,.btn-danger:active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#ae2125;border-color:#a41f23}.btn-check:active+.btn-danger:focus,.btn-check:checked+.btn-danger:focus,.btn-danger.active:focus,.btn-danger:active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 1px 2px rgba(0,0,0,.05),0 0 0 .25rem rgba(224,73,77,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#da292e;border-color:#da292e}.btn-light{color:#000;background-color:#f8f9fa;border-color:#f8f9fa;box-shadow:0 1px 2px rgba(0,0,0,.05)}.btn-light:hover{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:focus+.btn-light,.btn-light:focus{color:#000;background-color:#f9fafb;border-color:#f9fafb;box-shadow:0 1px 2px rgba(0,0,0,.05),0 0 0 .25rem rgba(211,212,213,.5)}.btn-check:active+.btn-light,.btn-check:checked+.btn-light,.btn-light.active,.btn-light:active,.show>.btn-light.dropdown-toggle{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:active+.btn-light:focus,.btn-check:checked+.btn-light:focus,.btn-light.active:focus,.btn-light:active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 1px 2px rgba(0,0,0,.05),0 0 0 .25rem rgba(211,212,213,.5)}.btn-light.disabled,.btn-light:disabled{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-dark{color:#fff;background-color:#212529;border-color:#212529;box-shadow:0 1px 2px rgba(0,0,0,.05)}.btn-dark:hover{color:#fff;background-color:#1c1f23;border-color:#1a1e21}.btn-check:focus+.btn-dark,.btn-dark:focus{color:#fff;background-color:#1c1f23;border-color:#1a1e21;box-shadow:0 1px 2px rgba(0,0,0,.05),0 0 0 .25rem rgba(66,70,73,.5)}.btn-check:active+.btn-dark,.btn-check:checked+.btn-dark,.btn-dark.active,.btn-dark:active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1a1e21;border-color:#191c1f}.btn-check:active+.btn-dark:focus,.btn-check:checked+.btn-dark:focus,.btn-dark.active:focus,.btn-dark:active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 1px 2px rgba(0,0,0,.05),0 0 0 .25rem rgba(66,70,73,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#212529;border-color:#212529}.btn-outline-primary{color:#3459e6;border-color:#3459e6}.btn-outline-primary:hover{color:#fff;background-color:#3459e6;border-color:#3459e6}.btn-check:focus+.btn-outline-primary,.btn-outline-primary:focus{box-shadow:0 0 0 .25rem rgba(52,89,230,.5)}.btn-check:active+.btn-outline-primary,.btn-check:checked+.btn-outline-primary,.btn-outline-primary.active,.btn-outline-primary.dropdown-toggle.show,.btn-outline-primary:active{color:#fff;background-color:#3459e6;border-color:#3459e6}.btn-check:active+.btn-outline-primary:focus,.btn-check:checked+.btn-outline-primary:focus,.btn-outline-primary.active:focus,.btn-outline-primary.dropdown-toggle.show:focus,.btn-outline-primary:active:focus{box-shadow:0 1px 2px rgba(0,0,0,.05),0 0 0 .25rem rgba(52,89,230,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#3459e6;background-color:transparent}.btn-outline-secondary{color:#fff;border-color:#fff}.btn-outline-secondary:hover{color:#000;background-color:#fff;border-color:#fff}.btn-check:focus+.btn-outline-secondary,.btn-outline-secondary:focus{box-shadow:0 0 0 .25rem rgba(255,255,255,.5)}.btn-check:active+.btn-outline-secondary,.btn-check:checked+.btn-outline-secondary,.btn-outline-secondary.active,.btn-outline-secondary.dropdown-toggle.show,.btn-outline-secondary:active{color:#000;background-color:#fff;border-color:#fff}.btn-check:active+.btn-outline-secondary:focus,.btn-check:checked+.btn-outline-secondary:focus,.btn-outline-secondary.active:focus,.btn-outline-secondary.dropdown-toggle.show:focus,.btn-outline-secondary:active:focus{box-shadow:0 1px 2px rgba(0,0,0,.05),0 0 0 .25rem rgba(255,255,255,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#fff;background-color:transparent}.btn-outline-success{color:#2fb380;border-color:#2fb380}.btn-outline-success:hover{color:#fff;background-color:#2fb380;border-color:#2fb380}.btn-check:focus+.btn-outline-success,.btn-outline-success:focus{box-shadow:0 0 0 .25rem rgba(47,179,128,.5)}.btn-check:active+.btn-outline-success,.btn-check:checked+.btn-outline-success,.btn-outline-success.active,.btn-outline-success.dropdown-toggle.show,.btn-outline-success:active{color:#fff;background-color:#2fb380;border-color:#2fb380}.btn-check:active+.btn-outline-success:focus,.btn-check:checked+.btn-outline-success:focus,.btn-outline-success.active:focus,.btn-outline-success.dropdown-toggle.show:focus,.btn-outline-success:active:focus{box-shadow:0 1px 2px rgba(0,0,0,.05),0 0 0 .25rem rgba(47,179,128,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#2fb380;background-color:transparent}.btn-outline-info{color:#287bb5;border-color:#287bb5}.btn-outline-info:hover{color:#fff;background-color:#287bb5;border-color:#287bb5}.btn-check:focus+.btn-outline-info,.btn-outline-info:focus{box-shadow:0 0 0 .25rem rgba(40,123,181,.5)}.btn-check:active+.btn-outline-info,.btn-check:checked+.btn-outline-info,.btn-outline-info.active,.btn-outline-info.dropdown-toggle.show,.btn-outline-info:active{color:#fff;background-color:#287bb5;border-color:#287bb5}.btn-check:active+.btn-outline-info:focus,.btn-check:checked+.btn-outline-info:focus,.btn-outline-info.active:focus,.btn-outline-info.dropdown-toggle.show:focus,.btn-outline-info:active:focus{box-shadow:0 1px 2px rgba(0,0,0,.05),0 0 0 .25rem rgba(40,123,181,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#287bb5;background-color:transparent}.btn-outline-warning{color:#f4bd61;border-color:#f4bd61}.btn-outline-warning:hover{color:#fff;background-color:#f4bd61;border-color:#f4bd61}.btn-check:focus+.btn-outline-warning,.btn-outline-warning:focus{box-shadow:0 0 0 .25rem rgba(244,189,97,.5)}.btn-check:active+.btn-outline-warning,.btn-check:checked+.btn-outline-warning,.btn-outline-warning.active,.btn-outline-warning.dropdown-toggle.show,.btn-outline-warning:active{color:#fff;background-color:#f4bd61;border-color:#f4bd61}.btn-check:active+.btn-outline-warning:focus,.btn-check:checked+.btn-outline-warning:focus,.btn-outline-warning.active:focus,.btn-outline-warning.dropdown-toggle.show:focus,.btn-outline-warning:active:focus{box-shadow:0 1px 2px rgba(0,0,0,.05),0 0 0 .25rem rgba(244,189,97,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#f4bd61;background-color:transparent}.btn-outline-danger{color:#da292e;border-color:#da292e}.btn-outline-danger:hover{color:#fff;background-color:#da292e;border-color:#da292e}.btn-check:focus+.btn-outline-danger,.btn-outline-danger:focus{box-shadow:0 0 0 .25rem rgba(218,41,46,.5)}.btn-check:active+.btn-outline-danger,.btn-check:checked+.btn-outline-danger,.btn-outline-danger.active,.btn-outline-danger.dropdown-toggle.show,.btn-outline-danger:active{color:#fff;background-color:#da292e;border-color:#da292e}.btn-check:active+.btn-outline-danger:focus,.btn-check:checked+.btn-outline-danger:focus,.btn-outline-danger.active:focus,.btn-outline-danger.dropdown-toggle.show:focus,.btn-outline-danger:active:focus{box-shadow:0 1px 2px rgba(0,0,0,.05),0 0 0 .25rem rgba(218,41,46,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#da292e;background-color:transparent}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:hover{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:focus+.btn-outline-light,.btn-outline-light:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-check:active+.btn-outline-light,.btn-check:checked+.btn-outline-light,.btn-outline-light.active,.btn-outline-light.dropdown-toggle.show,.btn-outline-light:active{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:active+.btn-outline-light:focus,.btn-check:checked+.btn-outline-light:focus,.btn-outline-light.active:focus,.btn-outline-light.dropdown-toggle.show:focus,.btn-outline-light:active:focus{box-shadow:0 1px 2px rgba(0,0,0,.05),0 0 0 .25rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-dark{color:#212529;border-color:#212529}.btn-outline-dark:hover{color:#fff;background-color:#212529;border-color:#212529}.btn-check:focus+.btn-outline-dark,.btn-outline-dark:focus{box-shadow:0 0 0 .25rem rgba(33,37,41,.5)}.btn-check:active+.btn-outline-dark,.btn-check:checked+.btn-outline-dark,.btn-outline-dark.active,.btn-outline-dark.dropdown-toggle.show,.btn-outline-dark:active{color:#fff;background-color:#212529;border-color:#212529}.btn-check:active+.btn-outline-dark:focus,.btn-check:checked+.btn-outline-dark:focus,.btn-outline-dark.active:focus,.btn-outline-dark.dropdown-toggle.show:focus,.btn-outline-dark:active:focus{box-shadow:0 1px 2px rgba(0,0,0,.05),0 0 0 .25rem rgba(33,37,41,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#212529;background-color:transparent}.btn-link{font-weight:400;color:#3459e6;text-decoration:underline}.btn-link:hover{color:#2a47b8}.btn-link.disabled,.btn-link:disabled{color:#6c757d}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.collapsing.collapse-horizontal{width:0;height:auto;transition:width .35s ease}@media (prefers-reduced-motion:reduce){.collapsing.collapse-horizontal{transition:none}}.dropdown,.dropend,.dropstart,.dropup{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;z-index:1000;display:none;min-width:10rem;padding:.5rem 0;margin:0;font-size:.875rem;color:#495057;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid #dee2e6;border-radius:.25rem;box-shadow:0 1px 2px rgba(0,0,0,.05)}.dropdown-menu[data-bs-popper]{top:100%;left:0;margin-top:.125rem}.dropdown-menu-start{--bs-position:start}.dropdown-menu-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-end{--bs-position:end}.dropdown-menu-end[data-bs-popper]{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-start{--bs-position:start}.dropdown-menu-sm-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-sm-end{--bs-position:end}.dropdown-menu-sm-end[data-bs-popper]{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-start{--bs-position:start}.dropdown-menu-md-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-md-end{--bs-position:end}.dropdown-menu-md-end[data-bs-popper]{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-start{--bs-position:start}.dropdown-menu-lg-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-lg-end{--bs-position:end}.dropdown-menu-lg-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-start{--bs-position:start}.dropdown-menu-xl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xl-end{--bs-position:end}.dropdown-menu-xl-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1400px){.dropdown-menu-xxl-start{--bs-position:start}.dropdown-menu-xxl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xxl-end{--bs-position:end}.dropdown-menu-xxl-end[data-bs-popper]{right:0;left:auto}}.dropup .dropdown-menu[data-bs-popper]{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-menu[data-bs-popper]{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropend .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropend .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-toggle::after{vertical-align:0}.dropstart .dropdown-menu[data-bs-popper]{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropstart .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropstart .dropdown-toggle::after{display:none}.dropstart .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropstart .dropdown-toggle:empty::after{margin-left:0}.dropstart .dropdown-toggle::before{vertical-align:0}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid #e9ecef}.dropdown-item{display:block;width:100%;padding:.5rem 1rem;clear:both;font-weight:400;color:#212529;text-align:inherit;text-decoration:none;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#fff;background-color:#3459e6}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#3459e6}.dropdown-item.disabled,.dropdown-item:disabled{color:#adb5bd;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.5rem 1rem;color:#212529}.dropdown-menu-dark{color:#dee2e6;background-color:#343a40;border-color:#dee2e6}.dropdown-menu-dark .dropdown-item{color:#dee2e6}.dropdown-menu-dark .dropdown-item:focus,.dropdown-menu-dark .dropdown-item:hover{color:#fff;background-color:rgba(255,255,255,.15)}.dropdown-menu-dark .dropdown-item.active,.dropdown-menu-dark .dropdown-item:active{color:#fff;background-color:#3459e6}.dropdown-menu-dark .dropdown-item.disabled,.dropdown-menu-dark .dropdown-item:disabled{color:#adb5bd}.dropdown-menu-dark .dropdown-divider{border-color:#e9ecef}.dropdown-menu-dark .dropdown-item-text{color:#dee2e6}.dropdown-menu-dark .dropdown-header{color:#adb5bd}.btn-group,.btn-group-vertical{position:relative;display:-ms-inline-flexbox;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;-ms-flex:1 1 auto;flex:1 1 auto}.btn-group-vertical>.btn-check:checked+.btn,.btn-group-vertical>.btn-check:focus+.btn,.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn-check:checked+.btn,.btn-group>.btn-check:focus+.btn,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:1}.btn-toolbar{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-pack:start;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:nth-child(n+3),.btn-group>:not(.btn-check)+.btn{border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.dropdown-toggle-split::after,.dropend .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropstart .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group.show .dropdown-toggle{box-shadow:0 1px 2px rgba(0,0,0,.05)}.btn-group.show .dropdown-toggle.btn-link{box-shadow:none}.btn-group-vertical{-ms-flex-direction:column;flex-direction:column;-ms-flex-align:start;align-items:flex-start;-ms-flex-pack:center;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn~.btn{border-top-left-radius:0;border-top-right-radius:0}.nav{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem;color:#495057;text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out}@media (prefers-reduced-motion:reduce){.nav-link{transition:none}}.nav-link:focus,.nav-link:hover{color:#495057}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-link{margin-bottom:-1px;background:0 0;border:1px solid transparent;border-top-left-radius:0;border-top-right-radius:0}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6;isolation:isolate}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#3459e6;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{background:0 0;border:0;border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#3459e6}.nav-fill .nav-item,.nav-fill>.nav-link{-ms-flex:1 1 auto;flex:1 1 auto;text-align:center}.nav-justified .nav-item,.nav-justified>.nav-link{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;text-align:center}.nav-fill .nav-item .nav-link,.nav-justified .nav-item .nav-link{width:100%}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between;padding-top:.85rem;padding-bottom:.85rem}.navbar>.container,.navbar>.container-fluid,.navbar>.container-lg,.navbar>.container-md,.navbar>.container-sm,.navbar>.container-xl,.navbar>.container-xxl{display:-ms-flexbox;display:flex;-ms-flex-wrap:inherit;flex-wrap:inherit;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between}.navbar-brand{padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;text-decoration:none;white-space:nowrap}.navbar-nav{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static}.navbar-text{padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{-ms-flex-preferred-size:100%;flex-basis:100%;-ms-flex-positive:1;flex-grow:1;-ms-flex-align:center;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem;transition:box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.navbar-toggler{transition:none}}.navbar-toggler:hover{text-decoration:none}.navbar-toggler:focus{text-decoration:none;outline:0;box-shadow:0 0 0 .25rem}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;background-repeat:no-repeat;background-position:center;background-size:100%}.navbar-nav-scroll{max-height:var(--bs-scroll-height,75vh);overflow-y:auto}@media (min-width:576px){.navbar-expand-sm{-ms-flex-wrap:nowrap;flex-wrap:nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-sm .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.75rem;padding-left:.75rem}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}.navbar-expand-sm .offcanvas-header{display:none}.navbar-expand-sm .offcanvas{position:inherit;bottom:0;z-index:1000;-ms-flex-positive:1;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;-webkit-transform:none;transform:none}.navbar-expand-sm .offcanvas-bottom,.navbar-expand-sm .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-sm .offcanvas-body{display:-ms-flexbox;display:flex;-ms-flex-positive:0;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:768px){.navbar-expand-md{-ms-flex-wrap:nowrap;flex-wrap:nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-md .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.75rem;padding-left:.75rem}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}.navbar-expand-md .offcanvas-header{display:none}.navbar-expand-md .offcanvas{position:inherit;bottom:0;z-index:1000;-ms-flex-positive:1;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;-webkit-transform:none;transform:none}.navbar-expand-md .offcanvas-bottom,.navbar-expand-md .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-md .offcanvas-body{display:-ms-flexbox;display:flex;-ms-flex-positive:0;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:992px){.navbar-expand-lg{-ms-flex-wrap:nowrap;flex-wrap:nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-lg .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.75rem;padding-left:.75rem}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}.navbar-expand-lg .offcanvas-header{display:none}.navbar-expand-lg .offcanvas{position:inherit;bottom:0;z-index:1000;-ms-flex-positive:1;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;-webkit-transform:none;transform:none}.navbar-expand-lg .offcanvas-bottom,.navbar-expand-lg .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-lg .offcanvas-body{display:-ms-flexbox;display:flex;-ms-flex-positive:0;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:1200px){.navbar-expand-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-xl .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.75rem;padding-left:.75rem}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}.navbar-expand-xl .offcanvas-header{display:none}.navbar-expand-xl .offcanvas{position:inherit;bottom:0;z-index:1000;-ms-flex-positive:1;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;-webkit-transform:none;transform:none}.navbar-expand-xl .offcanvas-bottom,.navbar-expand-xl .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-xl .offcanvas-body{display:-ms-flexbox;display:flex;-ms-flex-positive:0;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:1400px){.navbar-expand-xxl{-ms-flex-wrap:nowrap;flex-wrap:nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-xxl .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-xxl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xxl .navbar-nav .nav-link{padding-right:.75rem;padding-left:.75rem}.navbar-expand-xxl .navbar-nav-scroll{overflow:visible}.navbar-expand-xxl .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-xxl .navbar-toggler{display:none}.navbar-expand-xxl .offcanvas-header{display:none}.navbar-expand-xxl .offcanvas{position:inherit;bottom:0;z-index:1000;-ms-flex-positive:1;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;-webkit-transform:none;transform:none}.navbar-expand-xxl .offcanvas-bottom,.navbar-expand-xxl .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-xxl .offcanvas-body{display:-ms-flexbox;display:flex;-ms-flex-positive:0;flex-grow:0;padding:0;overflow-y:visible}}.navbar-expand{-ms-flex-wrap:nowrap;flex-wrap:nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.75rem;padding-left:.75rem}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-expand .offcanvas-header{display:none}.navbar-expand .offcanvas{position:inherit;bottom:0;z-index:1000;-ms-flex-positive:1;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;-webkit-transform:none;transform:none}.navbar-expand .offcanvas-bottom,.navbar-expand .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand .offcanvas-body{display:-ms-flexbox;display:flex;-ms-flex-positive:0;flex-grow:0;padding:0;overflow-y:visible}.navbar-light .navbar-brand{color:#000}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:#000}.navbar-light .navbar-nav .nav-link{color:#000}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:#000}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .show>.nav-link{color:#000}.navbar-light .navbar-toggler{color:#000;border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='%23000' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:#000}.navbar-light .navbar-text a,.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:#000}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:#fff}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:#fff}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:#fff;border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='%23fff' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:#fff}.navbar-dark .navbar-text a,.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{-ms-flex:1 1 auto;flex:1 1 auto;padding:1rem 1.5rem}.card-title{margin-bottom:.5rem}.card-subtitle{margin-top:-.25rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link+.card-link{margin-left:1.5rem}.card-header{padding:1rem 1.5rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-footer{padding:1rem 1.5rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.75rem;margin-bottom:-1rem;margin-left:-.75rem;border-bottom:0}.card-header-pills{margin-right:-.75rem;margin-left:-.75rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1rem;border-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom,.card-img-top{width:100%}.card-img,.card-img-top{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-group>.card{margin-bottom:.75rem}@media (min-width:576px){.card-group{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap}.card-group>.card{-ms-flex:1 0 0%;flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.accordion-button{position:relative;display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;width:100%;padding:1rem 1.25rem;font-size:1rem;color:#495057;text-align:left;background-color:#fff;border:0;border-radius:0;overflow-anchor:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,border-radius .15s ease}@media (prefers-reduced-motion:reduce){.accordion-button{transition:none}}.accordion-button:not(.collapsed){color:#2f50cf;background-color:#ebeefd;box-shadow:inset 0 -1px 0 rgba(0,0,0,.125)}.accordion-button:not(.collapsed)::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%232f50cf'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");-webkit-transform:rotate(-180deg);transform:rotate(-180deg)}.accordion-button::after{-ms-flex-negative:0;flex-shrink:0;width:1.25rem;height:1.25rem;margin-left:auto;content:"";background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23495057'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-size:1.25rem;transition:-webkit-transform .2s ease-in-out;transition:transform .2s ease-in-out;transition:transform .2s ease-in-out,-webkit-transform .2s ease-in-out}@media (prefers-reduced-motion:reduce){.accordion-button::after{transition:none}}.accordion-button:hover{z-index:2}.accordion-button:focus{z-index:3;border-color:#9aacf3;outline:0;box-shadow:0 1px 2px rgba(0,0,0,.05)}.accordion-header{margin-bottom:0}.accordion-item{background-color:#fff;border:1px solid rgba(0,0,0,.125)}.accordion-item:first-of-type{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.accordion-item:first-of-type .accordion-button{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.accordion-item:not(:first-of-type){border-top:0}.accordion-item:last-of-type{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-item:last-of-type .accordion-button.collapsed{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.accordion-item:last-of-type .accordion-collapse{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-body{padding:1rem 1.25rem}.accordion-flush .accordion-collapse{border-width:0}.accordion-flush .accordion-item{border-right:0;border-left:0;border-radius:0}.accordion-flush .accordion-item:first-child{border-top:0}.accordion-flush .accordion-item:last-child{border-bottom:0}.accordion-flush .accordion-item .accordion-button{border-radius:0}.breadcrumb{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:0 1rem;margin-bottom:1rem;list-style:none}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:.5rem;color:#6c757d;content:var(--bs-breadcrumb-divider, ">")}.breadcrumb-item.active{color:#6c757d}.pagination{display:-ms-flexbox;display:flex;padding-left:0;list-style:none}.page-link{position:relative;display:block;color:#495057;text-decoration:none;background-color:#fff;border:1px solid #dee2e6;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.page-link{transition:none}}.page-link:hover{z-index:2;color:#495057;background-color:#f8f9fa;border-color:#dee2e6}.page-link:focus{z-index:3;color:#495057;background-color:#e9ecef;outline:0;box-shadow:0 0 0 .25rem rgba(52,89,230,.25)}.page-item:not(:first-child) .page-link{margin-left:-1px}.page-item.active .page-link{z-index:3;color:#fff;background-color:#3459e6;border-color:#3459e6}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;background-color:#fff;border-color:#dee2e6}.page-link{padding:.5rem 1rem}.page-item:first-child .page-link{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.35em .65em;font-size:.75em;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.alert{position:relative;padding:1rem 1rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{position:absolute;top:0;right:0;z-index:2;padding:1.25rem 1rem}.alert-primary{color:#1f358a;background-color:#d6defa;border-color:#c2cdf8}.alert-primary .alert-link{color:#192a6e}.alert-secondary{color:#999;background-color:#fff;border-color:#fff}.alert-secondary .alert-link{color:#7a7a7a}.alert-success{color:#1c6b4d;background-color:#d5f0e6;border-color:#c1e8d9}.alert-success .alert-link{color:#16563e}.alert-info{color:#184a6d;background-color:#d4e5f0;border-color:#bfd7e9}.alert-info .alert-link{color:#133b57}.alert-warning{color:#92713a;background-color:#fdf2df;border-color:#fcebd0}.alert-warning .alert-link{color:#755a2e}.alert-danger{color:#83191c;background-color:#f8d4d5;border-color:#f4bfc0}.alert-danger .alert-link{color:#691416}.alert-light{color:#959596;background-color:#fefefe;border-color:#fdfdfe}.alert-light .alert-link{color:#777778}.alert-dark{color:#141619;background-color:#d3d3d4;border-color:#bcbebf}.alert-dark .alert-link{color:#101214}@-webkit-keyframes progress-bar-stripes{0%{background-position-x:1rem}}@keyframes progress-bar-stripes{0%{background-position-x:1rem}}.progress{display:-ms-flexbox;display:flex;height:1rem;overflow:hidden;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem;box-shadow:inset 0 1px 2px rgba(0,0,0,.075)}.progress-bar{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;overflow:hidden;color:#fff;text-align:center;white-space:nowrap;background-color:#3459e6;transition:width .6s ease}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{-webkit-animation:1s linear infinite progress-bar-stripes;animation:1s linear infinite progress-bar-stripes}@media (prefers-reduced-motion:reduce){.progress-bar-animated{-webkit-animation:none;animation:none}}.list-group{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:.25rem}.list-group-numbered{list-style-type:none;counter-reset:section}.list-group-numbered>li::before{content:counters(section, ".") ". ";counter-increment:section}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#495057;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:1rem 1.5rem;color:#212529;text-decoration:none;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#3459e6;border-color:#3459e6}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:-1px;border-top-width:1px}.list-group-horizontal{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}@media (min-width:576px){.list-group-horizontal-sm{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:768px){.list-group-horizontal-md{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:992px){.list-group-horizontal-lg{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1200px){.list-group-horizontal-xl{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1400px){.list-group-horizontal-xxl{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-xxl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xxl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xxl>.list-group-item.active{margin-top:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{color:#1f358a;background-color:#d6defa}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#1f358a;background-color:#c1c8e1}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#1f358a;border-color:#1f358a}.list-group-item-secondary{color:#999;background-color:#fff}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#999;background-color:#e6e6e6}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#999;border-color:#999}.list-group-item-success{color:#1c6b4d;background-color:#d5f0e6}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#1c6b4d;background-color:#c0d8cf}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#1c6b4d;border-color:#1c6b4d}.list-group-item-info{color:#184a6d;background-color:#d4e5f0}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#184a6d;background-color:#bfced8}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#184a6d;border-color:#184a6d}.list-group-item-warning{color:#92713a;background-color:#fdf2df}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#92713a;background-color:#e4dac9}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#92713a;border-color:#92713a}.list-group-item-danger{color:#83191c;background-color:#f8d4d5}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#83191c;background-color:#dfbfc0}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#83191c;border-color:#83191c}.list-group-item-light{color:#959596;background-color:#fefefe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#959596;background-color:#e5e5e5}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#959596;border-color:#959596}.list-group-item-dark{color:#141619;background-color:#d3d3d4}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#141619;background-color:#bebebf}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#141619;border-color:#141619}.btn-close{box-sizing:content-box;width:1em;height:1em;padding:.25em .25em;color:#000;background:transparent url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e") center/1em auto no-repeat;border:0;border-radius:.25rem;opacity:.5}.btn-close:hover{color:#000;text-decoration:none;opacity:.75}.btn-close:focus{outline:0;box-shadow:0 0 0 .25rem rgba(52,89,230,.25);opacity:1}.btn-close.disabled,.btn-close:disabled{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;opacity:.25}.btn-close-white{-webkit-filter:invert(1) grayscale(100%) brightness(200%);filter:invert(1) grayscale(100%) brightness(200%)}.toast{width:350px;max-width:100%;font-size:.875rem;pointer-events:auto;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 1px 2px rgba(0,0,0,.05);border-radius:.25rem}.toast.showing{opacity:0}.toast:not(.show){display:none}.toast-container{width:-webkit-max-content;width:-moz-max-content;width:max-content;max-width:100%;pointer-events:none}.toast-container>:not(:last-child){margin-bottom:.75rem}.toast-header{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;padding:.5rem .75rem;color:#212529;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05);border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.toast-header .btn-close{margin-right:-.375rem;margin-left:.75rem}.toast-body{padding:.75rem;word-wrap:break-word}.modal{position:fixed;top:0;left:0;z-index:1055;display:none;width:100%;height:100%;overflow-x:hidden;overflow-y:auto;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:-webkit-transform .3s ease-out;transition:transform .3s ease-out;transition:transform .3s ease-out,-webkit-transform .3s ease-out;-webkit-transform:translate(0,-50px);transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{-webkit-transform:none;transform:none}.modal.modal-static .modal-dialog{-webkit-transform:scale(1.02);transform:scale(1.02)}.modal-dialog-scrollable{height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:100%;overflow:hidden}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;min-height:calc(100% - 1rem)}.modal-content{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid #dee2e6;border-radius:.3rem;box-shadow:0 .125rem .25rem rgba(0,0,0,.075);outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1050;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:-ms-flexbox;display:flex;-ms-flex-negative:0;flex-shrink:0;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between;padding:1rem 1rem;border-bottom:0 solid #dee2e6;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.modal-header .btn-close{padding:.5rem .5rem;margin:-.5rem -.5rem -.5rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;-ms-flex:1 1 auto;flex:1 1 auto;padding:1rem}.modal-footer{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-negative:0;flex-shrink:0;-ms-flex-align:center;align-items:center;-ms-flex-pack:end;justify-content:flex-end;padding:.75rem;border-top:0 solid #dee2e6;border-bottom-right-radius:calc(.3rem - 1px);border-bottom-left-radius:calc(.3rem - 1px)}.modal-footer>*{margin:.25rem}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{height:calc(100% - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-content{box-shadow:0 1px 2px rgba(0,0,0,.05)}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width:1200px){.modal-xl{max-width:1140px}}.modal-fullscreen{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen .modal-header{border-radius:0}.modal-fullscreen .modal-body{overflow-y:auto}.modal-fullscreen .modal-footer{border-radius:0}@media (max-width:575.98px){.modal-fullscreen-sm-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-sm-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-sm-down .modal-header{border-radius:0}.modal-fullscreen-sm-down .modal-body{overflow-y:auto}.modal-fullscreen-sm-down .modal-footer{border-radius:0}}@media (max-width:767.98px){.modal-fullscreen-md-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-md-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-md-down .modal-header{border-radius:0}.modal-fullscreen-md-down .modal-body{overflow-y:auto}.modal-fullscreen-md-down .modal-footer{border-radius:0}}@media (max-width:991.98px){.modal-fullscreen-lg-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-lg-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-lg-down .modal-header{border-radius:0}.modal-fullscreen-lg-down .modal-body{overflow-y:auto}.modal-fullscreen-lg-down .modal-footer{border-radius:0}}@media (max-width:1199.98px){.modal-fullscreen-xl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xl-down .modal-header{border-radius:0}.modal-fullscreen-xl-down .modal-body{overflow-y:auto}.modal-fullscreen-xl-down .modal-footer{border-radius:0}}@media (max-width:1399.98px){.modal-fullscreen-xxl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xxl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xxl-down .modal-header{border-radius:0}.modal-fullscreen-xxl-down .modal-body{overflow-y:auto}.modal-fullscreen-xxl-down .modal-footer{border-radius:0}}.tooltip{position:absolute;z-index:1080;display:block;margin:0;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .tooltip-arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .tooltip-arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[data-popper-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow,.bs-tooltip-top .tooltip-arrow{bottom:0}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before,.bs-tooltip-top .tooltip-arrow::before{top:-1px;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[data-popper-placement^=right],.bs-tooltip-end{padding:0 .4rem}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow,.bs-tooltip-end .tooltip-arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before,.bs-tooltip-end .tooltip-arrow::before{right:-1px;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[data-popper-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow,.bs-tooltip-bottom .tooltip-arrow{top:0}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before,.bs-tooltip-bottom .tooltip-arrow::before{bottom:-1px;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[data-popper-placement^=left],.bs-tooltip-start{padding:0 .4rem}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow,.bs-tooltip-start .tooltip-arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before,.bs-tooltip-start .tooltip-arrow::before{left:-1px;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0;z-index:1070;display:block;max-width:276px;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;box-shadow:0 1px 2px rgba(0,0,0,.05)}.popover .popover-arrow{position:absolute;display:block;width:1rem;height:.5rem}.popover .popover-arrow::after,.popover .popover-arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow,.bs-popover-top>.popover-arrow{bottom:calc(-.5rem - 1px)}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before,.bs-popover-top>.popover-arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after,.bs-popover-top>.popover-arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow,.bs-popover-end>.popover-arrow{left:calc(-.5rem - 1px);width:.5rem;height:1rem}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before,.bs-popover-end>.popover-arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after,.bs-popover-end>.popover-arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow,.bs-popover-bottom>.popover-arrow{top:calc(-.5rem - 1px)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before,.bs-popover-bottom>.popover-arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after,.bs-popover-bottom>.popover-arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-auto[data-popper-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f0f0f0}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow,.bs-popover-start>.popover-arrow{right:calc(-.5rem - 1px);width:.5rem;height:1rem}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before,.bs-popover-start>.popover-arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after,.bs-popover-start>.popover-arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem 1rem;margin-bottom:0;font-size:1rem;color:#212529;background-color:#f0f0f0;border-bottom:1px solid rgba(0,0,0,.2);border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:1rem 1rem;color:#495057}.carousel{position:relative}.carousel.pointer-event{-ms-touch-action:pan-y;touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:-webkit-transform .6s ease-in-out;transition:transform .6s ease-in-out;transition:transform .6s ease-in-out,-webkit-transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-end,.carousel-item-next:not(.carousel-item-start){-webkit-transform:translateX(100%);transform:translateX(100%)}.active.carousel-item-start,.carousel-item-prev:not(.carousel-item-end){-webkit-transform:translateX(-100%);transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;-webkit-transform:none;transform:none}.carousel-fade .carousel-item-next.carousel-item-start,.carousel-fade .carousel-item-prev.carousel-item-end,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;width:15%;padding:0;color:#fff;text-align:center;background:0 0;border:0;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:2rem;height:2rem;background-repeat:no-repeat;background-position:50%;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:2;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;padding:0;margin-right:15%;margin-bottom:1rem;margin-left:15%;list-style:none}.carousel-indicators [data-bs-target]{box-sizing:content-box;-ms-flex:0 1 auto;flex:0 1 auto;width:30px;height:3px;padding:0;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border:0;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators [data-bs-target]{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:1.25rem;left:15%;padding-top:1.25rem;padding-bottom:1.25rem;color:#fff;text-align:center}.carousel-dark .carousel-control-next-icon,.carousel-dark .carousel-control-prev-icon{-webkit-filter:invert(1) grayscale(100);filter:invert(1) grayscale(100)}.carousel-dark .carousel-indicators [data-bs-target]{background-color:#000}.carousel-dark .carousel-caption{color:#000}@-webkit-keyframes spinner-border{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinner-border{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;border:.25em solid currentColor;border-right-color:transparent;border-radius:50%;-webkit-animation:.75s linear infinite spinner-border;animation:.75s linear infinite spinner-border}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@-webkit-keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1;-webkit-transform:none;transform:none}}@keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1;-webkit-transform:none;transform:none}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;background-color:currentColor;border-radius:50%;opacity:0;-webkit-animation:.75s linear infinite spinner-grow;animation:.75s linear infinite spinner-grow}.spinner-grow-sm{width:1rem;height:1rem}@media (prefers-reduced-motion:reduce){.spinner-border,.spinner-grow{-webkit-animation-duration:1.5s;animation-duration:1.5s}}.offcanvas{position:fixed;bottom:0;z-index:1045;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;max-width:100%;visibility:hidden;background-color:#fff;background-clip:padding-box;outline:0;box-shadow:0 .125rem .25rem rgba(0,0,0,.075);transition:-webkit-transform .3s ease-in-out;transition:transform .3s ease-in-out;transition:transform .3s ease-in-out,-webkit-transform .3s ease-in-out}@media (prefers-reduced-motion:reduce){.offcanvas{transition:none}}.offcanvas-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.offcanvas-backdrop.fade{opacity:0}.offcanvas-backdrop.show{opacity:.5}.offcanvas-header{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between;padding:1rem 1rem}.offcanvas-header .btn-close{padding:.5rem .5rem;margin-top:-.5rem;margin-right:-.5rem;margin-bottom:-.5rem}.offcanvas-title{margin-bottom:0;line-height:1.5}.offcanvas-body{-ms-flex-positive:1;flex-grow:1;padding:1rem 1rem;overflow-y:auto}.offcanvas-start{top:0;left:0;width:400px;border-right:1px solid #dee2e6;-webkit-transform:translateX(-100%);transform:translateX(-100%)}.offcanvas-end{top:0;right:0;width:400px;border-left:1px solid #dee2e6;-webkit-transform:translateX(100%);transform:translateX(100%)}.offcanvas-top{top:0;right:0;left:0;height:30vh;max-height:100%;border-bottom:1px solid #dee2e6;-webkit-transform:translateY(-100%);transform:translateY(-100%)}.offcanvas-bottom{right:0;left:0;height:30vh;max-height:100%;border-top:1px solid #dee2e6;-webkit-transform:translateY(100%);transform:translateY(100%)}.offcanvas.show{-webkit-transform:none;transform:none}.placeholder{display:inline-block;min-height:1em;vertical-align:middle;cursor:wait;background-color:currentColor;opacity:.5}.placeholder.btn::before{display:inline-block;content:""}.placeholder-xs{min-height:.6em}.placeholder-sm{min-height:.8em}.placeholder-lg{min-height:1.2em}.placeholder-glow .placeholder{-webkit-animation:placeholder-glow 2s ease-in-out infinite;animation:placeholder-glow 2s ease-in-out infinite}@-webkit-keyframes placeholder-glow{50%{opacity:.2}}@keyframes placeholder-glow{50%{opacity:.2}}.placeholder-wave{-webkit-mask-image:linear-gradient(130deg,#000 55%,rgba(0,0,0,.8) 75%,#000 95%);mask-image:linear-gradient(130deg,#000 55%,rgba(0,0,0,.8) 75%,#000 95%);-webkit-mask-size:200% 100%;mask-size:200% 100%;-webkit-animation:placeholder-wave 2s linear infinite;animation:placeholder-wave 2s linear infinite}@-webkit-keyframes placeholder-wave{100%{-webkit-mask-position:-200% 0;mask-position:-200% 0}}@keyframes placeholder-wave{100%{-webkit-mask-position:-200% 0;mask-position:-200% 0}}.clearfix::after{display:block;clear:both;content:""}.link-primary{color:#3459e6}.link-primary:focus,.link-primary:hover{color:#2a47b8}.link-secondary{color:#fff}.link-secondary:focus,.link-secondary:hover{color:#fff}.link-success{color:#2fb380}.link-success:focus,.link-success:hover{color:#268f66}.link-info{color:#287bb5}.link-info:focus,.link-info:hover{color:#206291}.link-warning{color:#f4bd61}.link-warning:focus,.link-warning:hover{color:#c3974e}.link-danger{color:#da292e}.link-danger:focus,.link-danger:hover{color:#ae2125}.link-light{color:#f8f9fa}.link-light:focus,.link-light:hover{color:#f9fafb}.link-dark{color:#212529}.link-dark:focus,.link-dark:hover{color:#1a1e21}.ratio{position:relative;width:100%}.ratio::before{display:block;padding-top:var(--bs-aspect-ratio);content:""}.ratio>*{position:absolute;top:0;left:0;width:100%;height:100%}.ratio-1x1{--bs-aspect-ratio:100%}.ratio-4x3{--bs-aspect-ratio:calc(3 / 4 * 100%)}.ratio-16x9{--bs-aspect-ratio:calc(9 / 16 * 100%)}.ratio-21x9{--bs-aspect-ratio:calc(9 / 21 * 100%)}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}@media (min-width:576px){.sticky-sm-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:768px){.sticky-md-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:992px){.sticky-lg-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:1200px){.sticky-xl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:1400px){.sticky-xxl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.hstack{display:-ms-flexbox;display:flex;-ms-flex-direction:row;flex-direction:row;-ms-flex-align:center;align-items:center;-ms-flex-item-align:stretch;align-self:stretch}.vstack{display:-ms-flexbox;display:flex;-ms-flex:1 1 auto;flex:1 1 auto;-ms-flex-direction:column;flex-direction:column;-ms-flex-item-align:stretch;align-self:stretch}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.vr{display:inline-block;-ms-flex-item-align:stretch;align-self:stretch;width:1px;min-height:1em;background-color:currentColor;opacity:.25}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.float-start{float:left!important}.float-end{float:right!important}.float-none{float:none!important}.opacity-0{opacity:0!important}.opacity-25{opacity:.25!important}.opacity-50{opacity:.5!important}.opacity-75{opacity:.75!important}.opacity-100{opacity:1!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.overflow-visible{overflow:visible!important}.overflow-scroll{overflow:scroll!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-grid{display:grid!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:-ms-flexbox!important;display:flex!important}.d-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}.d-none{display:none!important}.shadow{box-shadow:0 1px 2px rgba(0,0,0,.05)!important}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow-lg{box-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px 0 rgba(0,0,0,.06)!important}.shadow-none{box-shadow:none!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.top-0{top:0!important}.top-50{top:50%!important}.top-100{top:100%!important}.bottom-0{bottom:0!important}.bottom-50{bottom:50%!important}.bottom-100{bottom:100%!important}.start-0{left:0!important}.start-50{left:50%!important}.start-100{left:100%!important}.end-0{right:0!important}.end-50{right:50%!important}.end-100{right:100%!important}.translate-middle{-webkit-transform:translate(-50%,-50%)!important;transform:translate(-50%,-50%)!important}.translate-middle-x{-webkit-transform:translateX(-50%)!important;transform:translateX(-50%)!important}.translate-middle-y{-webkit-transform:translateY(-50%)!important;transform:translateY(-50%)!important}.border{border:1px solid #dee2e6!important}.border-0{border:0!important}.border-top{border-top:1px solid #dee2e6!important}.border-top-0{border-top:0!important}.border-end{border-right:1px solid #dee2e6!important}.border-end-0{border-right:0!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-bottom-0{border-bottom:0!important}.border-start{border-left:1px solid #dee2e6!important}.border-start-0{border-left:0!important}.border-primary{border-color:#3459e6!important}.border-secondary{border-color:#fff!important}.border-success{border-color:#2fb380!important}.border-info{border-color:#287bb5!important}.border-warning{border-color:#f4bd61!important}.border-danger{border-color:#da292e!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#212529!important}.border-white{border-color:#fff!important}.border-1{border-width:1px!important}.border-2{border-width:2px!important}.border-3{border-width:3px!important}.border-4{border-width:4px!important}.border-5{border-width:5px!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.mw-100{max-width:100%!important}.vw-100{width:100vw!important}.min-vw-100{min-width:100vw!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mh-100{max-height:100%!important}.vh-100{height:100vh!important}.min-vh-100{min-height:100vh!important}.flex-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.flex-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.gap-0{gap:0!important}.gap-1{gap:.25rem!important}.gap-2{gap:.5rem!important}.gap-3{gap:1rem!important}.gap-4{gap:1.5rem!important}.gap-5{gap:3rem!important}.justify-content-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.justify-content-evenly{-ms-flex-pack:space-evenly!important;justify-content:space-evenly!important}.align-items-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-center{-ms-flex-align:center!important;align-items:center!important}.align-items-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}.order-first{-ms-flex-order:-1!important;order:-1!important}.order-0{-ms-flex-order:0!important;order:0!important}.order-1{-ms-flex-order:1!important;order:1!important}.order-2{-ms-flex-order:2!important;order:2!important}.order-3{-ms-flex-order:3!important;order:3!important}.order-4{-ms-flex-order:4!important;order:4!important}.order-5{-ms-flex-order:5!important;order:5!important}.order-last{-ms-flex-order:6!important;order:6!important}.m-0{margin:0!important}.m-1{margin:.25rem!important}.m-2{margin:.5rem!important}.m-3{margin:1rem!important}.m-4{margin:1.5rem!important}.m-5{margin:3rem!important}.m-auto{margin:auto!important}.mx-0{margin-right:0!important;margin-left:0!important}.mx-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-3{margin-right:1rem!important;margin-left:1rem!important}.mx-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-5{margin-right:3rem!important;margin-left:3rem!important}.mx-auto{margin-right:auto!important;margin-left:auto!important}.my-0{margin-top:0!important;margin-bottom:0!important}.my-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-0{margin-top:0!important}.mt-1{margin-top:.25rem!important}.mt-2{margin-top:.5rem!important}.mt-3{margin-top:1rem!important}.mt-4{margin-top:1.5rem!important}.mt-5{margin-top:3rem!important}.mt-auto{margin-top:auto!important}.me-0{margin-right:0!important}.me-1{margin-right:.25rem!important}.me-2{margin-right:.5rem!important}.me-3{margin-right:1rem!important}.me-4{margin-right:1.5rem!important}.me-5{margin-right:3rem!important}.me-auto{margin-right:auto!important}.mb-0{margin-bottom:0!important}.mb-1{margin-bottom:.25rem!important}.mb-2{margin-bottom:.5rem!important}.mb-3{margin-bottom:1rem!important}.mb-4{margin-bottom:1.5rem!important}.mb-5{margin-bottom:3rem!important}.mb-auto{margin-bottom:auto!important}.ms-0{margin-left:0!important}.ms-1{margin-left:.25rem!important}.ms-2{margin-left:.5rem!important}.ms-3{margin-left:1rem!important}.ms-4{margin-left:1.5rem!important}.ms-5{margin-left:3rem!important}.ms-auto{margin-left:auto!important}.p-0{padding:0!important}.p-1{padding:.25rem!important}.p-2{padding:.5rem!important}.p-3{padding:1rem!important}.p-4{padding:1.5rem!important}.p-5{padding:3rem!important}.px-0{padding-right:0!important;padding-left:0!important}.px-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-3{padding-right:1rem!important;padding-left:1rem!important}.px-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-5{padding-right:3rem!important;padding-left:3rem!important}.py-0{padding-top:0!important;padding-bottom:0!important}.py-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-0{padding-top:0!important}.pt-1{padding-top:.25rem!important}.pt-2{padding-top:.5rem!important}.pt-3{padding-top:1rem!important}.pt-4{padding-top:1.5rem!important}.pt-5{padding-top:3rem!important}.pe-0{padding-right:0!important}.pe-1{padding-right:.25rem!important}.pe-2{padding-right:.5rem!important}.pe-3{padding-right:1rem!important}.pe-4{padding-right:1.5rem!important}.pe-5{padding-right:3rem!important}.pb-0{padding-bottom:0!important}.pb-1{padding-bottom:.25rem!important}.pb-2{padding-bottom:.5rem!important}.pb-3{padding-bottom:1rem!important}.pb-4{padding-bottom:1.5rem!important}.pb-5{padding-bottom:3rem!important}.ps-0{padding-left:0!important}.ps-1{padding-left:.25rem!important}.ps-2{padding-left:.5rem!important}.ps-3{padding-left:1rem!important}.ps-4{padding-left:1.5rem!important}.ps-5{padding-left:3rem!important}.font-monospace{font-family:var(--bs-font-monospace)!important}.fs-1{font-size:calc(1.375rem + 1.5vw)!important}.fs-2{font-size:calc(1.325rem + .9vw)!important}.fs-3{font-size:calc(1.3rem + .6vw)!important}.fs-4{font-size:calc(1.275rem + .3vw)!important}.fs-5{font-size:1.25rem!important}.fs-6{font-size:1rem!important}.fst-italic{font-style:italic!important}.fst-normal{font-style:normal!important}.fw-light{font-weight:300!important}.fw-lighter{font-weight:lighter!important}.fw-normal{font-weight:400!important}.fw-bold{font-weight:700!important}.fw-bolder{font-weight:bolder!important}.lh-1{line-height:1!important}.lh-sm{line-height:1.25!important}.lh-base{line-height:1.5!important}.lh-lg{line-height:2!important}.text-start{text-align:left!important}.text-end{text-align:right!important}.text-center{text-align:center!important}.text-decoration-none{text-decoration:none!important}.text-decoration-underline{text-decoration:underline!important}.text-decoration-line-through{text-decoration:line-through!important}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-break{word-wrap:break-word!important;word-break:break-word!important}.text-primary{--bs-text-opacity:1;color:rgba(var(--bs-primary-rgb),var(--bs-text-opacity))!important}.text-secondary{--bs-text-opacity:1;color:rgba(var(--bs-secondary-rgb),var(--bs-text-opacity))!important}.text-success{--bs-text-opacity:1;color:rgba(var(--bs-success-rgb),var(--bs-text-opacity))!important}.text-info{--bs-text-opacity:1;color:rgba(var(--bs-info-rgb),var(--bs-text-opacity))!important}.text-warning{--bs-text-opacity:1;color:rgba(var(--bs-warning-rgb),var(--bs-text-opacity))!important}.text-danger{--bs-text-opacity:1;color:rgba(var(--bs-danger-rgb),var(--bs-text-opacity))!important}.text-light{--bs-text-opacity:1;color:rgba(var(--bs-light-rgb),var(--bs-text-opacity))!important}.text-dark{--bs-text-opacity:1;color:rgba(var(--bs-dark-rgb),var(--bs-text-opacity))!important}.text-black{--bs-text-opacity:1;color:rgba(var(--bs-black-rgb),var(--bs-text-opacity))!important}.text-white{--bs-text-opacity:1;color:rgba(var(--bs-white-rgb),var(--bs-text-opacity))!important}.text-body{--bs-text-opacity:1;color:rgba(var(--bs-body-color-rgb),var(--bs-text-opacity))!important}.text-muted{--bs-text-opacity:1;color:#6c757d!important}.text-black-50{--bs-text-opacity:1;color:rgba(0,0,0,.5)!important}.text-white-50{--bs-text-opacity:1;color:rgba(255,255,255,.5)!important}.text-reset{--bs-text-opacity:1;color:inherit!important}.text-opacity-25{--bs-text-opacity:0.25}.text-opacity-50{--bs-text-opacity:0.5}.text-opacity-75{--bs-text-opacity:0.75}.text-opacity-100{--bs-text-opacity:1}.bg-primary{--bs-bg-opacity:1;background-color:rgba(var(--bs-primary-rgb),var(--bs-bg-opacity))!important}.bg-secondary{--bs-bg-opacity:1;background-color:rgba(var(--bs-secondary-rgb),var(--bs-bg-opacity))!important}.bg-success{--bs-bg-opacity:1;background-color:rgba(var(--bs-success-rgb),var(--bs-bg-opacity))!important}.bg-info{--bs-bg-opacity:1;background-color:rgba(var(--bs-info-rgb),var(--bs-bg-opacity))!important}.bg-warning{--bs-bg-opacity:1;background-color:rgba(var(--bs-warning-rgb),var(--bs-bg-opacity))!important}.bg-danger{--bs-bg-opacity:1;background-color:rgba(var(--bs-danger-rgb),var(--bs-bg-opacity))!important}.bg-light{--bs-bg-opacity:1;background-color:rgba(var(--bs-light-rgb),var(--bs-bg-opacity))!important}.bg-dark{--bs-bg-opacity:1;background-color:rgba(var(--bs-dark-rgb),var(--bs-bg-opacity))!important}.bg-black{--bs-bg-opacity:1;background-color:rgba(var(--bs-black-rgb),var(--bs-bg-opacity))!important}.bg-white{--bs-bg-opacity:1;background-color:rgba(var(--bs-white-rgb),var(--bs-bg-opacity))!important}.bg-body{--bs-bg-opacity:1;background-color:rgba(var(--bs-body-bg-rgb),var(--bs-bg-opacity))!important}.bg-transparent{--bs-bg-opacity:1;background-color:transparent!important}.bg-opacity-10{--bs-bg-opacity:0.1}.bg-opacity-25{--bs-bg-opacity:0.25}.bg-opacity-50{--bs-bg-opacity:0.5}.bg-opacity-75{--bs-bg-opacity:0.75}.bg-opacity-100{--bs-bg-opacity:1}.bg-gradient{background-image:var(--bs-gradient)!important}.user-select-all{-webkit-user-select:all!important;-moz-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;-moz-user-select:auto!important;-ms-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;-moz-user-select:none!important;-ms-user-select:none!important;user-select:none!important}.pe-none{pointer-events:none!important}.pe-auto{pointer-events:auto!important}.rounded{border-radius:.25rem!important}.rounded-0{border-radius:0!important}.rounded-1{border-radius:.2rem!important}.rounded-2{border-radius:.25rem!important}.rounded-3{border-radius:.3rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-end{border-top-right-radius:.25rem!important;border-bottom-right-radius:.25rem!important}.rounded-bottom{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-start{border-bottom-left-radius:.25rem!important;border-top-left-radius:.25rem!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media (min-width:576px){.float-sm-start{float:left!important}.float-sm-end{float:right!important}.float-sm-none{float:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-grid{display:grid!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:-ms-flexbox!important;display:flex!important}.d-sm-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}.d-sm-none{display:none!important}.flex-sm-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-sm-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-sm-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-sm-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-sm-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-sm-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-sm-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-sm-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-sm-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.flex-sm-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-sm-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-sm-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.gap-sm-0{gap:0!important}.gap-sm-1{gap:.25rem!important}.gap-sm-2{gap:.5rem!important}.gap-sm-3{gap:1rem!important}.gap-sm-4{gap:1.5rem!important}.gap-sm-5{gap:3rem!important}.justify-content-sm-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-sm-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-sm-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-sm-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-sm-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.justify-content-sm-evenly{-ms-flex-pack:space-evenly!important;justify-content:space-evenly!important}.align-items-sm-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-sm-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-sm-center{-ms-flex-align:center!important;align-items:center!important}.align-items-sm-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-sm-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-sm-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-sm-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-sm-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-sm-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-sm-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-sm-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-sm-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-sm-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-sm-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-sm-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-sm-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-sm-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}.order-sm-first{-ms-flex-order:-1!important;order:-1!important}.order-sm-0{-ms-flex-order:0!important;order:0!important}.order-sm-1{-ms-flex-order:1!important;order:1!important}.order-sm-2{-ms-flex-order:2!important;order:2!important}.order-sm-3{-ms-flex-order:3!important;order:3!important}.order-sm-4{-ms-flex-order:4!important;order:4!important}.order-sm-5{-ms-flex-order:5!important;order:5!important}.order-sm-last{-ms-flex-order:6!important;order:6!important}.m-sm-0{margin:0!important}.m-sm-1{margin:.25rem!important}.m-sm-2{margin:.5rem!important}.m-sm-3{margin:1rem!important}.m-sm-4{margin:1.5rem!important}.m-sm-5{margin:3rem!important}.m-sm-auto{margin:auto!important}.mx-sm-0{margin-right:0!important;margin-left:0!important}.mx-sm-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-sm-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-sm-3{margin-right:1rem!important;margin-left:1rem!important}.mx-sm-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-sm-5{margin-right:3rem!important;margin-left:3rem!important}.mx-sm-auto{margin-right:auto!important;margin-left:auto!important}.my-sm-0{margin-top:0!important;margin-bottom:0!important}.my-sm-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-sm-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-sm-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-sm-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-sm-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-sm-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-sm-0{margin-top:0!important}.mt-sm-1{margin-top:.25rem!important}.mt-sm-2{margin-top:.5rem!important}.mt-sm-3{margin-top:1rem!important}.mt-sm-4{margin-top:1.5rem!important}.mt-sm-5{margin-top:3rem!important}.mt-sm-auto{margin-top:auto!important}.me-sm-0{margin-right:0!important}.me-sm-1{margin-right:.25rem!important}.me-sm-2{margin-right:.5rem!important}.me-sm-3{margin-right:1rem!important}.me-sm-4{margin-right:1.5rem!important}.me-sm-5{margin-right:3rem!important}.me-sm-auto{margin-right:auto!important}.mb-sm-0{margin-bottom:0!important}.mb-sm-1{margin-bottom:.25rem!important}.mb-sm-2{margin-bottom:.5rem!important}.mb-sm-3{margin-bottom:1rem!important}.mb-sm-4{margin-bottom:1.5rem!important}.mb-sm-5{margin-bottom:3rem!important}.mb-sm-auto{margin-bottom:auto!important}.ms-sm-0{margin-left:0!important}.ms-sm-1{margin-left:.25rem!important}.ms-sm-2{margin-left:.5rem!important}.ms-sm-3{margin-left:1rem!important}.ms-sm-4{margin-left:1.5rem!important}.ms-sm-5{margin-left:3rem!important}.ms-sm-auto{margin-left:auto!important}.p-sm-0{padding:0!important}.p-sm-1{padding:.25rem!important}.p-sm-2{padding:.5rem!important}.p-sm-3{padding:1rem!important}.p-sm-4{padding:1.5rem!important}.p-sm-5{padding:3rem!important}.px-sm-0{padding-right:0!important;padding-left:0!important}.px-sm-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-sm-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-sm-3{padding-right:1rem!important;padding-left:1rem!important}.px-sm-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-sm-5{padding-right:3rem!important;padding-left:3rem!important}.py-sm-0{padding-top:0!important;padding-bottom:0!important}.py-sm-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-sm-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-sm-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-sm-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-sm-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-sm-0{padding-top:0!important}.pt-sm-1{padding-top:.25rem!important}.pt-sm-2{padding-top:.5rem!important}.pt-sm-3{padding-top:1rem!important}.pt-sm-4{padding-top:1.5rem!important}.pt-sm-5{padding-top:3rem!important}.pe-sm-0{padding-right:0!important}.pe-sm-1{padding-right:.25rem!important}.pe-sm-2{padding-right:.5rem!important}.pe-sm-3{padding-right:1rem!important}.pe-sm-4{padding-right:1.5rem!important}.pe-sm-5{padding-right:3rem!important}.pb-sm-0{padding-bottom:0!important}.pb-sm-1{padding-bottom:.25rem!important}.pb-sm-2{padding-bottom:.5rem!important}.pb-sm-3{padding-bottom:1rem!important}.pb-sm-4{padding-bottom:1.5rem!important}.pb-sm-5{padding-bottom:3rem!important}.ps-sm-0{padding-left:0!important}.ps-sm-1{padding-left:.25rem!important}.ps-sm-2{padding-left:.5rem!important}.ps-sm-3{padding-left:1rem!important}.ps-sm-4{padding-left:1.5rem!important}.ps-sm-5{padding-left:3rem!important}.text-sm-start{text-align:left!important}.text-sm-end{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.float-md-start{float:left!important}.float-md-end{float:right!important}.float-md-none{float:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-grid{display:grid!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:-ms-flexbox!important;display:flex!important}.d-md-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}.d-md-none{display:none!important}.flex-md-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-md-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-md-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-md-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-md-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-md-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-md-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-md-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-md-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.flex-md-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-md-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-md-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.gap-md-0{gap:0!important}.gap-md-1{gap:.25rem!important}.gap-md-2{gap:.5rem!important}.gap-md-3{gap:1rem!important}.gap-md-4{gap:1.5rem!important}.gap-md-5{gap:3rem!important}.justify-content-md-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-md-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-md-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-md-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-md-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.justify-content-md-evenly{-ms-flex-pack:space-evenly!important;justify-content:space-evenly!important}.align-items-md-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-md-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-md-center{-ms-flex-align:center!important;align-items:center!important}.align-items-md-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-md-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-md-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-md-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-md-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-md-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-md-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-md-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-md-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-md-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-md-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-md-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-md-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-md-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}.order-md-first{-ms-flex-order:-1!important;order:-1!important}.order-md-0{-ms-flex-order:0!important;order:0!important}.order-md-1{-ms-flex-order:1!important;order:1!important}.order-md-2{-ms-flex-order:2!important;order:2!important}.order-md-3{-ms-flex-order:3!important;order:3!important}.order-md-4{-ms-flex-order:4!important;order:4!important}.order-md-5{-ms-flex-order:5!important;order:5!important}.order-md-last{-ms-flex-order:6!important;order:6!important}.m-md-0{margin:0!important}.m-md-1{margin:.25rem!important}.m-md-2{margin:.5rem!important}.m-md-3{margin:1rem!important}.m-md-4{margin:1.5rem!important}.m-md-5{margin:3rem!important}.m-md-auto{margin:auto!important}.mx-md-0{margin-right:0!important;margin-left:0!important}.mx-md-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-md-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-md-3{margin-right:1rem!important;margin-left:1rem!important}.mx-md-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-md-5{margin-right:3rem!important;margin-left:3rem!important}.mx-md-auto{margin-right:auto!important;margin-left:auto!important}.my-md-0{margin-top:0!important;margin-bottom:0!important}.my-md-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-md-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-md-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-md-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-md-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-md-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-md-0{margin-top:0!important}.mt-md-1{margin-top:.25rem!important}.mt-md-2{margin-top:.5rem!important}.mt-md-3{margin-top:1rem!important}.mt-md-4{margin-top:1.5rem!important}.mt-md-5{margin-top:3rem!important}.mt-md-auto{margin-top:auto!important}.me-md-0{margin-right:0!important}.me-md-1{margin-right:.25rem!important}.me-md-2{margin-right:.5rem!important}.me-md-3{margin-right:1rem!important}.me-md-4{margin-right:1.5rem!important}.me-md-5{margin-right:3rem!important}.me-md-auto{margin-right:auto!important}.mb-md-0{margin-bottom:0!important}.mb-md-1{margin-bottom:.25rem!important}.mb-md-2{margin-bottom:.5rem!important}.mb-md-3{margin-bottom:1rem!important}.mb-md-4{margin-bottom:1.5rem!important}.mb-md-5{margin-bottom:3rem!important}.mb-md-auto{margin-bottom:auto!important}.ms-md-0{margin-left:0!important}.ms-md-1{margin-left:.25rem!important}.ms-md-2{margin-left:.5rem!important}.ms-md-3{margin-left:1rem!important}.ms-md-4{margin-left:1.5rem!important}.ms-md-5{margin-left:3rem!important}.ms-md-auto{margin-left:auto!important}.p-md-0{padding:0!important}.p-md-1{padding:.25rem!important}.p-md-2{padding:.5rem!important}.p-md-3{padding:1rem!important}.p-md-4{padding:1.5rem!important}.p-md-5{padding:3rem!important}.px-md-0{padding-right:0!important;padding-left:0!important}.px-md-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-md-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-md-3{padding-right:1rem!important;padding-left:1rem!important}.px-md-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-md-5{padding-right:3rem!important;padding-left:3rem!important}.py-md-0{padding-top:0!important;padding-bottom:0!important}.py-md-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-md-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-md-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-md-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-md-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-md-0{padding-top:0!important}.pt-md-1{padding-top:.25rem!important}.pt-md-2{padding-top:.5rem!important}.pt-md-3{padding-top:1rem!important}.pt-md-4{padding-top:1.5rem!important}.pt-md-5{padding-top:3rem!important}.pe-md-0{padding-right:0!important}.pe-md-1{padding-right:.25rem!important}.pe-md-2{padding-right:.5rem!important}.pe-md-3{padding-right:1rem!important}.pe-md-4{padding-right:1.5rem!important}.pe-md-5{padding-right:3rem!important}.pb-md-0{padding-bottom:0!important}.pb-md-1{padding-bottom:.25rem!important}.pb-md-2{padding-bottom:.5rem!important}.pb-md-3{padding-bottom:1rem!important}.pb-md-4{padding-bottom:1.5rem!important}.pb-md-5{padding-bottom:3rem!important}.ps-md-0{padding-left:0!important}.ps-md-1{padding-left:.25rem!important}.ps-md-2{padding-left:.5rem!important}.ps-md-3{padding-left:1rem!important}.ps-md-4{padding-left:1.5rem!important}.ps-md-5{padding-left:3rem!important}.text-md-start{text-align:left!important}.text-md-end{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.float-lg-start{float:left!important}.float-lg-end{float:right!important}.float-lg-none{float:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-grid{display:grid!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:-ms-flexbox!important;display:flex!important}.d-lg-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}.d-lg-none{display:none!important}.flex-lg-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-lg-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-lg-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-lg-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-lg-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-lg-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-lg-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-lg-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-lg-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.flex-lg-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-lg-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-lg-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.gap-lg-0{gap:0!important}.gap-lg-1{gap:.25rem!important}.gap-lg-2{gap:.5rem!important}.gap-lg-3{gap:1rem!important}.gap-lg-4{gap:1.5rem!important}.gap-lg-5{gap:3rem!important}.justify-content-lg-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-lg-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-lg-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-lg-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-lg-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.justify-content-lg-evenly{-ms-flex-pack:space-evenly!important;justify-content:space-evenly!important}.align-items-lg-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-lg-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-lg-center{-ms-flex-align:center!important;align-items:center!important}.align-items-lg-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-lg-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-lg-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-lg-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-lg-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-lg-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-lg-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-lg-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-lg-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-lg-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-lg-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-lg-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-lg-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-lg-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}.order-lg-first{-ms-flex-order:-1!important;order:-1!important}.order-lg-0{-ms-flex-order:0!important;order:0!important}.order-lg-1{-ms-flex-order:1!important;order:1!important}.order-lg-2{-ms-flex-order:2!important;order:2!important}.order-lg-3{-ms-flex-order:3!important;order:3!important}.order-lg-4{-ms-flex-order:4!important;order:4!important}.order-lg-5{-ms-flex-order:5!important;order:5!important}.order-lg-last{-ms-flex-order:6!important;order:6!important}.m-lg-0{margin:0!important}.m-lg-1{margin:.25rem!important}.m-lg-2{margin:.5rem!important}.m-lg-3{margin:1rem!important}.m-lg-4{margin:1.5rem!important}.m-lg-5{margin:3rem!important}.m-lg-auto{margin:auto!important}.mx-lg-0{margin-right:0!important;margin-left:0!important}.mx-lg-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-lg-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-lg-3{margin-right:1rem!important;margin-left:1rem!important}.mx-lg-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-lg-5{margin-right:3rem!important;margin-left:3rem!important}.mx-lg-auto{margin-right:auto!important;margin-left:auto!important}.my-lg-0{margin-top:0!important;margin-bottom:0!important}.my-lg-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-lg-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-lg-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-lg-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-lg-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-lg-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-lg-0{margin-top:0!important}.mt-lg-1{margin-top:.25rem!important}.mt-lg-2{margin-top:.5rem!important}.mt-lg-3{margin-top:1rem!important}.mt-lg-4{margin-top:1.5rem!important}.mt-lg-5{margin-top:3rem!important}.mt-lg-auto{margin-top:auto!important}.me-lg-0{margin-right:0!important}.me-lg-1{margin-right:.25rem!important}.me-lg-2{margin-right:.5rem!important}.me-lg-3{margin-right:1rem!important}.me-lg-4{margin-right:1.5rem!important}.me-lg-5{margin-right:3rem!important}.me-lg-auto{margin-right:auto!important}.mb-lg-0{margin-bottom:0!important}.mb-lg-1{margin-bottom:.25rem!important}.mb-lg-2{margin-bottom:.5rem!important}.mb-lg-3{margin-bottom:1rem!important}.mb-lg-4{margin-bottom:1.5rem!important}.mb-lg-5{margin-bottom:3rem!important}.mb-lg-auto{margin-bottom:auto!important}.ms-lg-0{margin-left:0!important}.ms-lg-1{margin-left:.25rem!important}.ms-lg-2{margin-left:.5rem!important}.ms-lg-3{margin-left:1rem!important}.ms-lg-4{margin-left:1.5rem!important}.ms-lg-5{margin-left:3rem!important}.ms-lg-auto{margin-left:auto!important}.p-lg-0{padding:0!important}.p-lg-1{padding:.25rem!important}.p-lg-2{padding:.5rem!important}.p-lg-3{padding:1rem!important}.p-lg-4{padding:1.5rem!important}.p-lg-5{padding:3rem!important}.px-lg-0{padding-right:0!important;padding-left:0!important}.px-lg-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-lg-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-lg-3{padding-right:1rem!important;padding-left:1rem!important}.px-lg-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-lg-5{padding-right:3rem!important;padding-left:3rem!important}.py-lg-0{padding-top:0!important;padding-bottom:0!important}.py-lg-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-lg-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-lg-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-lg-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-lg-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-lg-0{padding-top:0!important}.pt-lg-1{padding-top:.25rem!important}.pt-lg-2{padding-top:.5rem!important}.pt-lg-3{padding-top:1rem!important}.pt-lg-4{padding-top:1.5rem!important}.pt-lg-5{padding-top:3rem!important}.pe-lg-0{padding-right:0!important}.pe-lg-1{padding-right:.25rem!important}.pe-lg-2{padding-right:.5rem!important}.pe-lg-3{padding-right:1rem!important}.pe-lg-4{padding-right:1.5rem!important}.pe-lg-5{padding-right:3rem!important}.pb-lg-0{padding-bottom:0!important}.pb-lg-1{padding-bottom:.25rem!important}.pb-lg-2{padding-bottom:.5rem!important}.pb-lg-3{padding-bottom:1rem!important}.pb-lg-4{padding-bottom:1.5rem!important}.pb-lg-5{padding-bottom:3rem!important}.ps-lg-0{padding-left:0!important}.ps-lg-1{padding-left:.25rem!important}.ps-lg-2{padding-left:.5rem!important}.ps-lg-3{padding-left:1rem!important}.ps-lg-4{padding-left:1.5rem!important}.ps-lg-5{padding-left:3rem!important}.text-lg-start{text-align:left!important}.text-lg-end{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.float-xl-start{float:left!important}.float-xl-end{float:right!important}.float-xl-none{float:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-grid{display:grid!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:-ms-flexbox!important;display:flex!important}.d-xl-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}.d-xl-none{display:none!important}.flex-xl-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-xl-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-xl-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-xl-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-xl-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-xl-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-xl-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-xl-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-xl-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.flex-xl-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-xl-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-xl-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.gap-xl-0{gap:0!important}.gap-xl-1{gap:.25rem!important}.gap-xl-2{gap:.5rem!important}.gap-xl-3{gap:1rem!important}.gap-xl-4{gap:1.5rem!important}.gap-xl-5{gap:3rem!important}.justify-content-xl-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-xl-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-xl-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-xl-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-xl-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.justify-content-xl-evenly{-ms-flex-pack:space-evenly!important;justify-content:space-evenly!important}.align-items-xl-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-xl-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-xl-center{-ms-flex-align:center!important;align-items:center!important}.align-items-xl-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-xl-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-xl-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-xl-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-xl-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-xl-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-xl-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-xl-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-xl-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-xl-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-xl-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-xl-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-xl-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-xl-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}.order-xl-first{-ms-flex-order:-1!important;order:-1!important}.order-xl-0{-ms-flex-order:0!important;order:0!important}.order-xl-1{-ms-flex-order:1!important;order:1!important}.order-xl-2{-ms-flex-order:2!important;order:2!important}.order-xl-3{-ms-flex-order:3!important;order:3!important}.order-xl-4{-ms-flex-order:4!important;order:4!important}.order-xl-5{-ms-flex-order:5!important;order:5!important}.order-xl-last{-ms-flex-order:6!important;order:6!important}.m-xl-0{margin:0!important}.m-xl-1{margin:.25rem!important}.m-xl-2{margin:.5rem!important}.m-xl-3{margin:1rem!important}.m-xl-4{margin:1.5rem!important}.m-xl-5{margin:3rem!important}.m-xl-auto{margin:auto!important}.mx-xl-0{margin-right:0!important;margin-left:0!important}.mx-xl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xl-auto{margin-right:auto!important;margin-left:auto!important}.my-xl-0{margin-top:0!important;margin-bottom:0!important}.my-xl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xl-0{margin-top:0!important}.mt-xl-1{margin-top:.25rem!important}.mt-xl-2{margin-top:.5rem!important}.mt-xl-3{margin-top:1rem!important}.mt-xl-4{margin-top:1.5rem!important}.mt-xl-5{margin-top:3rem!important}.mt-xl-auto{margin-top:auto!important}.me-xl-0{margin-right:0!important}.me-xl-1{margin-right:.25rem!important}.me-xl-2{margin-right:.5rem!important}.me-xl-3{margin-right:1rem!important}.me-xl-4{margin-right:1.5rem!important}.me-xl-5{margin-right:3rem!important}.me-xl-auto{margin-right:auto!important}.mb-xl-0{margin-bottom:0!important}.mb-xl-1{margin-bottom:.25rem!important}.mb-xl-2{margin-bottom:.5rem!important}.mb-xl-3{margin-bottom:1rem!important}.mb-xl-4{margin-bottom:1.5rem!important}.mb-xl-5{margin-bottom:3rem!important}.mb-xl-auto{margin-bottom:auto!important}.ms-xl-0{margin-left:0!important}.ms-xl-1{margin-left:.25rem!important}.ms-xl-2{margin-left:.5rem!important}.ms-xl-3{margin-left:1rem!important}.ms-xl-4{margin-left:1.5rem!important}.ms-xl-5{margin-left:3rem!important}.ms-xl-auto{margin-left:auto!important}.p-xl-0{padding:0!important}.p-xl-1{padding:.25rem!important}.p-xl-2{padding:.5rem!important}.p-xl-3{padding:1rem!important}.p-xl-4{padding:1.5rem!important}.p-xl-5{padding:3rem!important}.px-xl-0{padding-right:0!important;padding-left:0!important}.px-xl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xl-0{padding-top:0!important;padding-bottom:0!important}.py-xl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xl-0{padding-top:0!important}.pt-xl-1{padding-top:.25rem!important}.pt-xl-2{padding-top:.5rem!important}.pt-xl-3{padding-top:1rem!important}.pt-xl-4{padding-top:1.5rem!important}.pt-xl-5{padding-top:3rem!important}.pe-xl-0{padding-right:0!important}.pe-xl-1{padding-right:.25rem!important}.pe-xl-2{padding-right:.5rem!important}.pe-xl-3{padding-right:1rem!important}.pe-xl-4{padding-right:1.5rem!important}.pe-xl-5{padding-right:3rem!important}.pb-xl-0{padding-bottom:0!important}.pb-xl-1{padding-bottom:.25rem!important}.pb-xl-2{padding-bottom:.5rem!important}.pb-xl-3{padding-bottom:1rem!important}.pb-xl-4{padding-bottom:1.5rem!important}.pb-xl-5{padding-bottom:3rem!important}.ps-xl-0{padding-left:0!important}.ps-xl-1{padding-left:.25rem!important}.ps-xl-2{padding-left:.5rem!important}.ps-xl-3{padding-left:1rem!important}.ps-xl-4{padding-left:1.5rem!important}.ps-xl-5{padding-left:3rem!important}.text-xl-start{text-align:left!important}.text-xl-end{text-align:right!important}.text-xl-center{text-align:center!important}}@media (min-width:1400px){.float-xxl-start{float:left!important}.float-xxl-end{float:right!important}.float-xxl-none{float:none!important}.d-xxl-inline{display:inline!important}.d-xxl-inline-block{display:inline-block!important}.d-xxl-block{display:block!important}.d-xxl-grid{display:grid!important}.d-xxl-table{display:table!important}.d-xxl-table-row{display:table-row!important}.d-xxl-table-cell{display:table-cell!important}.d-xxl-flex{display:-ms-flexbox!important;display:flex!important}.d-xxl-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}.d-xxl-none{display:none!important}.flex-xxl-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-xxl-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-xxl-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-xxl-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-xxl-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-xxl-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-xxl-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-xxl-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-xxl-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.flex-xxl-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-xxl-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-xxl-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.gap-xxl-0{gap:0!important}.gap-xxl-1{gap:.25rem!important}.gap-xxl-2{gap:.5rem!important}.gap-xxl-3{gap:1rem!important}.gap-xxl-4{gap:1.5rem!important}.gap-xxl-5{gap:3rem!important}.justify-content-xxl-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-xxl-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-xxl-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-xxl-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-xxl-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.justify-content-xxl-evenly{-ms-flex-pack:space-evenly!important;justify-content:space-evenly!important}.align-items-xxl-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-xxl-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-xxl-center{-ms-flex-align:center!important;align-items:center!important}.align-items-xxl-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-xxl-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-xxl-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-xxl-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-xxl-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-xxl-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-xxl-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-xxl-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-xxl-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-xxl-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-xxl-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-xxl-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-xxl-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-xxl-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}.order-xxl-first{-ms-flex-order:-1!important;order:-1!important}.order-xxl-0{-ms-flex-order:0!important;order:0!important}.order-xxl-1{-ms-flex-order:1!important;order:1!important}.order-xxl-2{-ms-flex-order:2!important;order:2!important}.order-xxl-3{-ms-flex-order:3!important;order:3!important}.order-xxl-4{-ms-flex-order:4!important;order:4!important}.order-xxl-5{-ms-flex-order:5!important;order:5!important}.order-xxl-last{-ms-flex-order:6!important;order:6!important}.m-xxl-0{margin:0!important}.m-xxl-1{margin:.25rem!important}.m-xxl-2{margin:.5rem!important}.m-xxl-3{margin:1rem!important}.m-xxl-4{margin:1.5rem!important}.m-xxl-5{margin:3rem!important}.m-xxl-auto{margin:auto!important}.mx-xxl-0{margin-right:0!important;margin-left:0!important}.mx-xxl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xxl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xxl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xxl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xxl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xxl-auto{margin-right:auto!important;margin-left:auto!important}.my-xxl-0{margin-top:0!important;margin-bottom:0!important}.my-xxl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xxl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xxl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xxl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xxl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xxl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xxl-0{margin-top:0!important}.mt-xxl-1{margin-top:.25rem!important}.mt-xxl-2{margin-top:.5rem!important}.mt-xxl-3{margin-top:1rem!important}.mt-xxl-4{margin-top:1.5rem!important}.mt-xxl-5{margin-top:3rem!important}.mt-xxl-auto{margin-top:auto!important}.me-xxl-0{margin-right:0!important}.me-xxl-1{margin-right:.25rem!important}.me-xxl-2{margin-right:.5rem!important}.me-xxl-3{margin-right:1rem!important}.me-xxl-4{margin-right:1.5rem!important}.me-xxl-5{margin-right:3rem!important}.me-xxl-auto{margin-right:auto!important}.mb-xxl-0{margin-bottom:0!important}.mb-xxl-1{margin-bottom:.25rem!important}.mb-xxl-2{margin-bottom:.5rem!important}.mb-xxl-3{margin-bottom:1rem!important}.mb-xxl-4{margin-bottom:1.5rem!important}.mb-xxl-5{margin-bottom:3rem!important}.mb-xxl-auto{margin-bottom:auto!important}.ms-xxl-0{margin-left:0!important}.ms-xxl-1{margin-left:.25rem!important}.ms-xxl-2{margin-left:.5rem!important}.ms-xxl-3{margin-left:1rem!important}.ms-xxl-4{margin-left:1.5rem!important}.ms-xxl-5{margin-left:3rem!important}.ms-xxl-auto{margin-left:auto!important}.p-xxl-0{padding:0!important}.p-xxl-1{padding:.25rem!important}.p-xxl-2{padding:.5rem!important}.p-xxl-3{padding:1rem!important}.p-xxl-4{padding:1.5rem!important}.p-xxl-5{padding:3rem!important}.px-xxl-0{padding-right:0!important;padding-left:0!important}.px-xxl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xxl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xxl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xxl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xxl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xxl-0{padding-top:0!important;padding-bottom:0!important}.py-xxl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xxl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xxl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xxl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xxl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xxl-0{padding-top:0!important}.pt-xxl-1{padding-top:.25rem!important}.pt-xxl-2{padding-top:.5rem!important}.pt-xxl-3{padding-top:1rem!important}.pt-xxl-4{padding-top:1.5rem!important}.pt-xxl-5{padding-top:3rem!important}.pe-xxl-0{padding-right:0!important}.pe-xxl-1{padding-right:.25rem!important}.pe-xxl-2{padding-right:.5rem!important}.pe-xxl-3{padding-right:1rem!important}.pe-xxl-4{padding-right:1.5rem!important}.pe-xxl-5{padding-right:3rem!important}.pb-xxl-0{padding-bottom:0!important}.pb-xxl-1{padding-bottom:.25rem!important}.pb-xxl-2{padding-bottom:.5rem!important}.pb-xxl-3{padding-bottom:1rem!important}.pb-xxl-4{padding-bottom:1.5rem!important}.pb-xxl-5{padding-bottom:3rem!important}.ps-xxl-0{padding-left:0!important}.ps-xxl-1{padding-left:.25rem!important}.ps-xxl-2{padding-left:.5rem!important}.ps-xxl-3{padding-left:1rem!important}.ps-xxl-4{padding-left:1.5rem!important}.ps-xxl-5{padding-left:3rem!important}.text-xxl-start{text-align:left!important}.text-xxl-end{text-align:right!important}.text-xxl-center{text-align:center!important}}@media (min-width:1200px){.fs-1{font-size:2.5rem!important}.fs-2{font-size:2rem!important}.fs-3{font-size:1.75rem!important}.fs-4{font-size:1.5rem!important}}@media print{.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-grid{display:grid!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:-ms-flexbox!important;display:flex!important}.d-print-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}.d-print-none{display:none!important}}.navbar{font-size:.875rem;font-weight:500}.navbar .nav-item{margin-left:.5rem;margin-right:.5rem}.navbar .navbar-nav .nav-link{border-radius:.25rem}.navbar-dark .navbar-nav .nav-link:hover{background-color:rgba(255,255,255,.1)}.navbar-dark .navbar-nav .nav-link.active{background-color:rgba(0,0,0,.5)}.navbar-light .navbar-nav .nav-link:hover{background-color:rgba(0,0,0,.03)}.navbar-light .navbar-nav .nav-link.active{background-color:rgba(0,0,0,.05)}.btn-light,.btn-outline-light,.btn-outline-secondary,.btn-secondary{color:#212529}.btn-light.disabled,.btn-light:disabled,.btn-outline-light.disabled,.btn-outline-light:disabled,.btn-outline-secondary.disabled,.btn-outline-secondary:disabled,.btn-secondary.disabled,.btn-secondary:disabled{border:1px solid #e6e6e6}.btn-outline-secondary,.btn-secondary{border-color:#e6e6e6}.btn-outline-secondary:active,.btn-outline-secondary:hover,.btn-secondary:active,.btn-secondary:hover{background-color:#e6e6e6;border-color:#e6e6e6}.btn-light,.btn-outline-light{border-color:#dfe0e1}.btn-light:active,.btn-light:hover,.btn-outline-light:active,.btn-outline-light:hover{background-color:#dfe0e1;border-color:#dfe0e1}.table{box-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px 0 rgba(0,0,0,.06);font-size:.875rem}thead th{text-transform:uppercase;font-size:.875rem}.input-group-text{box-shadow:0 1px 2px rgba(0,0,0,.05)}.nav-tabs{font-weight:500}.nav-tabs .nav-link{padding-top:1rem;padding-bottom:1rem;border-width:0 0 1px}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{box-shadow:inset 0 -2px 0 #3459e6}.nav-pills{font-weight:500}.pagination{font-size:.875rem;font-weight:500}.pagination .page-link{box-shadow:0 1px 2px rgba(0,0,0,.05)}.breadcrumb{border:1px solid #dee2e6;border-radius:.25rem;box-shadow:0 1px 2px rgba(0,0,0,.05);font-size:.875rem;font-weight:500}.breadcrumb-item{padding:1rem .5rem 1rem 0}.breadcrumb-item+.breadcrumb-item::before{padding-right:1rem}.alert .btn-close{color:inherit}.badge.bg-light,.badge.bg-secondary{color:#212529}.list-group{box-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px 0 rgba(0,0,0,.06)}.card{box-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px 0 rgba(0,0,0,.06)}.card-title{color:inherit}.modal-footer{background-color:#f8f9fa}.modal-content{box-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px 0 rgba(0,0,0,.06)} \ No newline at end of file diff --git a/atsebayt/static/favicon.png b/atsebayt/static/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..825b9e65af7c104cfb07089bb28659393b4f2097 GIT binary patch literal 1571 zcmV+;2Hg3HP)Px)-AP12RCwC$UE6KzI1p6{F2N z1VK2vi|pOpn{~#djwYcWXTI_im_u^TJgMZ4JMOsSj!0ma>B?-(Hr@X&W@|R-$}W@Z zgj#$x=!~7LGqHW?IO8+*oE1MyDp!G=L0#^lUx?;!fXv@l^6SvTnf^ac{5OurzC#ZMYc20lI%HhX816AYVs1T3heS1*WaWH z%;x>)-J}YB5#CLzU@GBR6sXYrD>Vw(Fmt#|JP;+}<#6b63Ike{Fuo!?M{yEffez;| zp!PfsuaC)>h>-AdbnwN13g*1LowNjT5?+lFVd#9$!8Z9HA|$*6dQ8EHLu}U|obW6f z2%uGv?vr=KNq7YYa2Roj;|zooo<)lf=&2yxM@e`kM$CmCR#x>gI>I|*Ubr({5Y^rb zghxQU22N}F51}^yfDSt786oMTc!W&V;d?76)9KXX1 z+6Okem(d}YXmmOiZq$!IPk5t8nnS{%?+vDFz3BevmFNgpIod~R{>@#@5x9zJKEHLHv!gHeK~n)Ld!M8DB|Kfe%~123&Hz1Z(86nU7*G5chmyDe ziV7$pB7pJ=96hpxHv9rCR29%bLOXlKU<_13_M8x)6;P8E1Kz6G<&P?$P^%c!M5`2` zfY2zg;VK5~^>TJGQzc+33-n~gKt{{of8GzUkWmU110IgI0DLxRIM>0US|TsM=L|@F z0Bun8U!cRB7-2apz=y-7*UxOxz@Z0)@QM)9wSGki1AZ38ceG7Q72z5`i;i=J`ILzL z@iUO?SBBG-0cQuo+an4TsLy-g-x;8P4UVwk|D8{W@U1Zi z!M)+jqy@nQ$p?5tsHp-6J304Q={v-B>66$P0IDx&YT(`IcZ~bZfmn11#rXd7<5s}y zBi9eim&zQc0Dk|2>$bs0PnLmDfMP5lcXRY&cvJ=zKxI^f0%-d$tD!`LBf9^jMSYUA zI8U?CWdY@}cRq6{5~y+)#h1!*-HcGW@+gZ4B};0OnC~`xQOyH19z*TA!!BJ%9s0V3F?CAJ{hTd#*tf+ur-W9MOURF-@B77_-OshsY}6 zOXRY=5%C^*26z?l)1=$bz30!so5tfABdSYzO+H=CpV~aaUefmjvfZ3Ttu9W&W3Iu6 zROlh0MFA5h;my}8lB0tAV-Rvc2Zs_CCSJnx@d`**$idgy-iMob4dJWWw|21b4NB=LfsYp0Aeh{Ov)yztQi;eL4y5 zMi>8^SzKqk8~k?UiQK^^-5d8c%bV?$F8%X~czyiaKCI2=UHFH*?V!Ecgrn~Dq|IamZT`w-r zd(M6C`&TDiS?Mh%8YvnCL6~x~QmWu<-+!N|$l(7nt@3XW1Q%o>DXA$^=q%Z)vzJWL0&^k){Iz zsUkH{h|KgTbdki55xV=w*9d8Aor_7Z>JsY_F$@3yv; z#%QGt!|~vVN*!c2)=W@6rucM)dVIk;Tzx!kle(LIC^-iKKKDaX%$g^$*8N8+u9^{*YN=|k&M`W>G)#5t+4i_FJ|C=>Cu zb?fY~{Br40cw+wu(V;E*oNKx{0!(e+EV zg7E_amQ*xN~jrdkydsn(YTa~Mi`b|GRX}4cAR*N4_774cA+u9t`$+98;D-v*Dc%2PF>Wo{r zobkP_r;#45a0C(j>P$)IRWljw4$aAJ?7T%%@v=cdI9#Md=Jk!EhF9OaR>dK)`n@G8 zm*%?Z^)uOm@$miKz#p-8=~9gsc==R}$d{&JF!SoB_mZmGxd-hLmce$akM+DPM2ojQ%}V z3K&G?3*r71E%QdRL<0{ko7H3C-6s{MBv}=F?Rh#$YUgtuj|Xl(2r4fxN2MtnP*BM& zH@<*J@e~T|>~puP(h3Ut&5!~4(Z-{?2t4!&`czyr9pD8wMSox6WIbXj_c^6sVyG}v zWJ>B2EBpIh->!pH@1c8h^JSG5x!8-vdwnMFOA81}FL%*>k*HW7^$JgJhAGkC{lMPM zlT(K^8>y<{Fs;3z?=q`dwKzb5UQyuX;y6*uw<#1}q=??e%SOXYiZ4(aT)2bt>GYFF zs#RJPAz1TKDmQ(B$Pg$##92W4_v$n_E@S3lj2lfA6zZrhf6FH8q|mqXMBFJ%FFza$*`WU+(3&b=UqRtEf|Xp-$_KpHN_ry_F`}^ zasdf~#Sq5a0U;=JkE_JBODo_PU|ja$)pZ z+jDI#DS3D7p(*O<;vxq@`>wpZ7s%%gM^7u2c+w>c3kO$1hf&V{a#7+HtB$PX8*Fb% z@TF^KurMsRwFmV}-sBHy9bUI7(Dz!_!q;k-E&4r0gNk`F=dIHl4who{vV?<_OQuo> zHc?XoJ9e}#ZxK-7WYX1{2-wq@?R_ZiZrZfVI&~?&>tWv5o)JaDw8}L1cE2qmU=SDG z2p!V(A$D^7=(Y#t%#_%#QZ!+_CiE-4Kc6N^{+F?*4ic}OMzaX`~#yd z_f}b`evilt5}o|=DT+4a$ouWy&x0esxN=N!i4V{p4UES`P}y53vlW>MjW20W`c;c% zIn%gaCCN*%eyDW0wyW}8$EN(6HhpqUg8yzdCCxg0#-_Z@DVia-+vL}|=}(@#rM-_2 zB8g+;E$!yah%h66M9E%J2UkM6aH*(N#bHDUGGbWfz!NK;eCa`QhaA|r{-AtaPhAN^@Iy-9F=ts+;NSTf)J%TcZEx^J#f@iAH*lOSNK#3{z6>MAb2-6kVfkj1=vo zzYSXXHrKoHpmZersDXb2rf-IpvtUYprkl6O|7@c5ZL;!J>|s-GkhFfvv&f2_uRRaG z|Gdap81^sY#tRFT1{GpV>>oA!bnkf(>N}>Dx^d>7(At*}8Ohwg$=WBq?!u$fUN1{0 zc6vkJZ=#w%;dyy_1?M+&U@HbGGieO*rzzHjOsCoMBIC zrrvZ+`>z6c{y1f7SkRiY{iRct{me;{EdP=^$ZyO%ME4M0zJL~jWD8UoQwB1P*}vsA zWmVF@PUb9-SjEG^#YcPDP4+Vdr*HMxh;9v~RvQz!b-nk&ze!NLSUpFJ*>;vu+jqOu zgcG?ywb*u0heA(-BS(0eS3ba5t+>a*MrO(oV?nieM!ogVCMW2RLw{qk6f1anG*b7a zYw#nb)0^AlI#g0beBdFeis~mXPJ$cXR}m7$>JiAT7f-v$Sm9CuL_`*hN%9-FH6p_C zH$w@3q@UrE@3*IAMmfafsQ<+rJ3HgkxuA_AO_J>vV@=7Muf5i*D6(}P_`LfG2?A%7 zZ|WP_l2s=#^$Z!E`0>U?`mYN(}E zw2IYbMLvCbLl&J(7(2ryMysCmPMPU%jZ{T%>-`9PXWl0}cGOIU3ha4jZQ(68CoxpG zU|l#Oz5RrYx%U$WHRAo2E(QYLSelV{%gA5@7B4we@sYR`6X#DC%t)yx^y$FF1gqI? z#Gzg?72kuWb_*#^*mhcT1qJ;~C2sjPx=XM{8+0U z^l%eIwBXkXL#EUjL+6QjW?YK%LF`xh#tv{r!6%GN$^{ck`qN7E&k-T9oYbyv_ip|n zPdrjt#z$n^3tjW1jAk`G4BAJJ9D8ZxECwRJnO8q0RG!@qg%?_keAmyDSd9P&Y|WAN zaob`zn}TAnJ~Wb-OsX$r)<%&dtKiO2%Vz~U@1!-``0-MNz3=_i`*(Ihh=!s+E~2F? zlqqARtoil`UJC8qOt&5e!n4SChxjRPk%s&3J>Pe~y;74Fp?k({V^C>#+S&8_&+y^% zB`UNx7236?>qd+X!QC!)wU$FHHImI2(ZLtu1uJEo<5bqnrTd2>!g%R_P70^Ijh5}2 z{h5OY6R$UfH&iRrga`(FdZ`YqUB0F=aU{Xs|%e7-qh%mJYfUroudBP9YK& zN;Z!5<9a?eYZVd55mQ%Zh**1+0hC zKzYJ#)WynCMui|3fkg$l0`D@|R}p(1aS6pcnd>-GFV3(1>TK7?8~ur#kl&OgthTnc z0yM*bux`s*3Hn4hiN3)fpAN{dK<$a-_j7$?;g&=8p6Kk9jSumkzZay8ePWhY>AQdU zH};}fmJYss8Jj7|vasM;wo_{N^<+NrWd8g$n;N2wGk%h#gM&lvrJt^T1?_Lyu(LBK zPEk>8$PX(NflAazw^*~J+kjn6BpanlOQ^c;*|Hh^I%RhHWOw0Ixq^bTI8+%!%l&(m zU5Yk7d}z$5xTw;!`a)H}ww^3n=7knBVVD%HLG53s6}4jg#xF;diMs-7OXaR{QR%GA z_;NCe#=jF6Sj?rSf2;%i2X^MUn%%j_Lns`?%Ekr$-pGXLIM3#0@c5|I!4lRyi&)Mkd8~ys;JmeQDQ2uXXr;Yatv5!-~PwDm6a6+ z29E7ZqvL(_O-nVa`xUKB(WBi?-e%i^ROQWn% zU6ChxzdXAFV~z#Z} z7wQP8a2PxKbK;=lWGSJ5pUDm;DY6I}ij92#Lk0KdKk(B}mK26jVUhk{A>^0) zgK)*Ut46aV_+vf~(Q)f+Wd@>k>U-nT#zy`gF>IH+VX%5%0z0YY=o9I(M-m+y6)bB> z0e=oU5ld5`_c~dF5zTjYj{g2FF|b^r-|AQ^h(^p^@u{H4Rqh-dxNw;~@mBrVjEoFX z{@2uLt_N!wQEwN&8k$Wp+A;L7ez&;o+2jVpT$735hdL9*S&bGmpgWo0zB&H<0FCbV z=1lBpm-llgk8IzqJU-m9JdFHz5E~mBsCS&xCoO9+%QSWDme%c8{{H>D>@gw!JCevM zK|xUQm5ZaJ1eD$?9zvTvas>x{moI=Xe1jjlIO@F0nLk~BsbmJ7-dTYS4YcF`kZO|0wa!%gEO3p@C-GPki?>C;BG;h?@qA~uo9gLi1b@Hy4qVk4nlo6H7VeH+Ob@4!~j zLSh)m2>577e8R#=PzN~8Z0K*3(!Sx7G$W?zhZFcDlw~S0=<4d8KYY_Hk%q*gWhUy2 z`Ytc)f-OGx#LZeCHpM7}h3i*r-)9cAktC}YTL7;)IQK4Wym#B zjBuEBADSR+jiMNLF`S+jMS@a?Ogq<4#9q|5WEZ_k9@x}wn^ulRea%-Tcj9(m7Q(yl zbddeA?C%NZOGV!8QDv5R@5=r+vh=Rjqd&&^@QG1=9uDj7FRkq|#40`BetNI!aa@pc z>(y2f+O?W(Vdev%)utl}{a8#?E~B;6P6DgtjMU$arB+WjFRz8%C6~QZPo0`j!xx>! zhOB%R_Q!H4C@4qlKT*UMh0!4`))YhVLECz3o=!i=kD-H9Jzo*{e6DMwSFX3W*Tl-o zD(OHuZeX(|6a!tvQoZNG!)t)@>eNgIdYfrifh?J(5CS?hA{5oTPMz2fu;(2~ z58ux2ZUk44zf|th<^KMD>8wM_I|?!eVei{>BpnHYNNEE!`QvAJt9;lpnJuFB$DBAq z9wR0-3(hJ-UTnWX0n4mg!#g#SLy)z#^*#KAn)f)6C{Ufohxb?XE9#3rSiyH=As7nT z3vq5a=gYP`$V9-#&1<5_5-W9HVawyi)6fPj8vFYChVo~a<7-hjH#dLE+9D@ajcCwi zJ-+e!JBZ2+Bd!NS_D|$y`=#DL+gqKNcF1iZ=Ki*&XbCe}fiN6Rh{{T)oR+2WTX2TGgd_R_@wB6lf)9vcue?UY? zRKeS1Hu4SWM*oHDOTzZ$Cl$&p0u~t~E<$=&!7rT-brh;!==(V+x3Nx)-WEF9EJ?n@ zsh*9%dV3^7s9lEjjE0dgk|Rxk$b?F4ODtx9FSd6b2P>2zX)O{A?DmO+v&6*bzsa%2 zxrQSB2MY%V-Z_@76BS}dQIc8EJ1w1l;mJ7silfP3$?aj^eCTals$Xh*fQ}bV+`Vo{ zf1Z)}YZl|A;e*eD0ekeuuZ+r~!m7TsWR%L5mP{j0`yT6STD4aNL{$jLH#~9k1*q8s zl=C-5%8WRWSW#D`KpACPsq*DnWY2FpLeUqUqbIS^7&w=RO|@+7#0tt~nsW8{YO$U- zK(4$-U=s=JZ5$!E)~rbV-f=0Q@t<&}E*k!mMkM=a&&3c>)%D_ISr;$+@gb!PV7D4e!KSyk34nA+{YfA^U)jrqqk+34O0 z%O^6|f@OHfnV+crrTR`MB9h~#%Z4L=YIZi26?{+L+gLi#JVmSP>Uiu82m~D>c3z*J zZoab;$!1V3J{MF_ehGaaGw=4iI#jHie{J=_mW{Nt2>%&ktPdF!1`a7uI4G|kVs8;b zpQ88b^{*bNNL*V8dF@0I^opj8=`(EIKNU^n>6QnXb4_|JuO3q?POuz3gVlnA!;vxM zxFu3~`l5&DEg0fpvUbj6h338B^@fbzAZ*(mEw{Iyt-&HOFFgS!nMaUh!h5%rJ>MNHP27Dt|1rG$MJ83Pl z4G+UF=@y*mGcD%7QfS!B#xqv zm({*CpS0v|=+hJ9BDIxI5sx5M$VlC#*w@2>4BXut2mfBYRe4W}hxV=j4^ScVZo|b? z+nSYUx0`W;E)!JD|<$nGSI-eT)oR(rov`9iKF5a zvdXZK;%u9}iq+|g)f0d|`WtbDpF$r#qcjs@!r>JooD`y_5w%Nz?k!yQG0Pp$h&avyV&z z7Iw*$K!aK_9ml^$iez+bLK8_j99Fcq_Zz32%NV>I8D$i^NjM3)q|bC&U^u%=JSb+J zyN0J7(zLj@509oUQh}Z9b$_S_4e6_2V@V9A7Aqv>830CW%&O zLP~Ku`N-Q(9-|vdQsq9`p3TGq$I1FaAU+H?x8T~&V_d4e%9pP1t)B_N6EI{^T*MfwD>7Ym(p#*eRrw6v0~8?eX@gv%wIML z=SGdXA(pk1gE{~?fI*P+p>S`MCv0k$m{@G}c#oe3E@+3Y`PZ-U`l?o4)=>Qdsad>Y zmAyK-v$Hb_n`bgKmxqG{%KYCKVKZ1)z1(cBL1%Oe{dsZXc6Y*cV-(@na*evSRiftt zMHrAQQxcsqf-EgAQnYrU(zb*-zOchw{*+~Wp+#y&Mj&__+MdO<2ba%?0Qi3V0Cl(` zOj+~rlB8PH^DqTV(9|)xsWnHk|4awq|F+?;i)xjne#(`p*ew{}G)oX?$FYkp#{bEZthh_bI=@E&#F9AT6`Wnn5{}QOD34Pv4Z4XraeoNedC)Ui9WZxKp=_bPLolS^9KVr#Dw$J z_^9182&cl#MX%_MpG3}$Mh^4v($dn=xn;>V|c>gvXK@WrJh zP&+RpJRdt&edrTuqes_!U@nGjL>AuO4-_&;yDw}SeD16in3E=Ug5Jhb%FPBBlJw89 zuoV)KM9a_xeOE1(s+hjxhyGKK75+#M##j@72}51R3yK`k9fp}<*aO{MZ2h=WyDUMw zOa_=(^*FdQt12(#aCm5F;|I2&Tl~#j#^22)#$Uc%ErX}~p;=N1K=aIjr!I^{AArjn zWP4dYa{5>*(ela+Yo56}%%M?y=^R~F6e&6&nVILgnj_`4I3juvTH)uD2oF4V_Y`t17h2pj9*+`V?uBA zsdtKSqw6y4&q8P@!h>5WIF>lSY1ij_sSp&!ufFPr) zrB}$LwWgd998{^jFFWB}Ejh*>8a|N^gTb)5i9Pc7__Bsgr`m>whNv3*xo7wZCi2Co zsV$s@?oWwZY^DWyVi|UUB-wM}&NIIpGhxY`ZT5TP4nAKe0Y8Hr>-~ftVPtmgzidRr zG%BR`*&+{17x#8pMq5%o78cK3U0ok1n2TvxM#6Ds=I8JJ{Fnx0{S(kifZs)x@*37j z7xtfbTm-jw;_f`>aTFy>H+q@|GeYP|goz9jk#cl~S`%0hr)OkHN(D_#O&vvPQ`^o> z9@dh6keE5!9`Vf?8lxe`%w0?ykKX4Yx*T}QDUY^W+rAADwO=t~lEIM>qmB2QsEUK2 z@iNQs(LNK-10He=d(bqq3`t5Iekx*NV~Yw|HJf<8zw}w<;(bZ~Ne^da{@*P@Mep{` zdQC^Q`Hzp$Rnh`Xyk7*NU(Hu^%br9$2(+MPC4R@Ny$g*wJVyKbCRd&5iSSH-FwzF& zSzLCyD|&zf-d(2AXx0Ti*N}{;w?@&*z%onnuSfl5i%Ajs6Sr-WWKWK#FQJ^{&uG91 zhP2*Z2H|{_r321|3ec>LP86#K1|f*N@4qa7YNdjsgM$k44=O@TPt^MA>a!zL0`db5 zl2-jAt)l{A^Y|mCIP2Z*&fS~AFWHD8aHxi(J&(PziAGhjKqwbKsvPuRo|~Iv>mZW? zzlDv36&-G|R2qjwju}9)|!XF}$196293o1};{Dmm7|5|ca&RRdG zF#As>q>M$uL`LusqSOW0Xbxb{uU?DwL#9>^pJJN-tTD&Q7s$9OJw@~Sv$Z`{+$JHy zd}y`HH1pJ%0!>bJ%?o7ihP}1s78bk&i7|Xe4@H2*`eNHwVWq9pv|6#0zgU5O{SRFF z9O?Ng2awt1O2Ag-$36faBUDrW1t=rrqcqMRor2#D~&2M)Z$A4dFiNb1YXh3CPV0fWtWoQWZJRA}DUNf_^J8Rsy$b6-m zR@L`$TFl?fx0f97XEVaKR`eeg)`T#Ee-rRW>N7OmAfUX0WZ^m=AE;0fW17pn(IUWI}z4vjg}WZE=y zl&L>__^@$$nxLz%pCFMV2Hfy!H_B#=nB^n0KX4}!3J+{Dn`HGm^?}%kb+3NiyutBu zU#BI=Ae;Y&d9v=+$3# z%vuIw3_OTh!R4z0Jpyu|;cm(5*^P+7@0nK->3>?$38?YH$$|fQ)O_+rSmhxU%MT}D zsHVxB8Q#h1HoIb;h)7HYA5Wq(zA!9J0S&&hs~0SfB^PM`u*}r?=`g9f_gA_IwaJoz zK4pkTm#_ND?fQ6CXM+3*2-h}*>q;<6yq^&S0GTkK+zRq88L)lf5rFV8f423t)XgvA zptjw23D~2Ka$$z`HBtxR#L0aQ_*e=aH3-MlEW2)lc5rp}@bIv{h?})p_M(Otndjnl z7MEgrh8l=KyaiN^>cZ8M4|xEEAB0|Pwk~F8xt(w&ro^ce{q@bD=0!~XkzJh*H2zmf z@478d?va-x*PQ=SpzmAP`_zDW!ZFOx&H`_q9s>GscILToMQLewD(w_)ytyS=S0Dq;y7(=r^BTs?HR@*zKUa zwoWcZhsROth(fAXpt{=degJ>V?1Hy&8uXZuMbiaCl(@==zcVn)G$E9fc_bRiOorQ*6m*^A*<~}}@ z47fhDiTxt5mT%6m*sQE9Vym}L#TJ}qo&~9wZLcTbLuI8flaPo|+pej}-C1IQ$8Y@$ zlU?Zv{V}|2RXWyllPI(WMPEWNO*Ny{9E^6g&J`0TQ$n<0vJ#U2WCq|6bq5C{ff7}^ z_}NVxFPomU35M&$1lE8Z$`hTo8Ft+(qh9)L2bQO5aUVfh!hNVpt7JYtj0$hN(|7Et zdHSQS7ad7BY|USy{Oa*<1Z+_A1c|%3W3JdXAKW(d3@)5k~bHOzIJQhquFY+W&8k#Lx<(3 zPG@?gARPQ~DaLqN<(yQ>$k~ts@9{hLO7p(DzxZ=AGh6C^{)w>a{@1i*^4yun67xn& zK2)I6AYx%*Jr-S#kWvUkLkgS)9>#bA^%#d=|4?Ub@3&?Gz7F77_-G$+-b-~- zd7V&^Sg<6?tI#E!r%+pCcRU{Ue=REz@d}*GXv9v?Milw*x6` zWzpj~K{lUG(ioQ8T=Opds7e%7vE6Bvw!Op)b`L9Gr3~PNJM#$5EzDTT9Z?^(Klwx! zg%5l-trp-QMZY@J_Lna}-poWE&UN|Z)xM_`F@syiLfQDNL2tenFA@fn7zWo0baZr} z{)u*%U%?Q#vrANqgEGhwqPhaP89(jRcnq3Mbnj?gdlWn0yh-`s&Pa%^up07hnv&7% z*Ww&Q5HMx}fQw=;umuO9Do$*y(Y~AeHE84|)T+fSGq@GA#56=)<^UI1MU%>{QmpAz^n&Z`~dm{4pg9}_31vje%9WqqFh9p{j12@6qsw}pYHuw2Icou+y=a(?e^Arb~K zO2&51*Ez;#1@nfs;LS6?OeB+QiTyR&U`U)(S80sp1*`m3-JO9zuT@ujf zrD$G0xY=?u6??IyLHBA9T}R%%HsPFuy(zbVPXEVo}p!a`2mYNRIs#Z_o``|Zf& z&5{`X#5^71`hp-bo_c+p>?V#L*n6d=r9n7VC1lr~fhgI$q|qQxp^`WDeaKWYB3FrY zi58?JWC2hH9yKa4d1XVx%8fay@8o4rO^w8l@f~*@K6fF? z4fvtT%V#u*S65fd877q1-%YqkbJUrlYrmif%KIOY-;xP4>XZMxshVoxg z#y7-;*m!LI6Ep8ymbm+`LlRP z%io7euC8BcS!(*(3FZ@}DHK*vQc%b>NH8}2TYJ)=!=r%=ULXds>)ECxCm*kNg;EiW zKeT$?aB}Z*&*(R+pc_WX>tbd5yp*!@@Q&CUl4tq3=9*I`64vvZy-3+?wu!3RzJKy! zr>aZjY1yXEgRO-r>k{o_0DCLzf6yIJ|Ity7v zw)(6@MAO(c+7!R$bsF{FeZmaU_XcW^Yd13R=}go)+mxnIUS>IwezIB^nr9@g^8M z(*}F0e*HS7T)vXR9d~fFtKuWRXp_S%4hJM!gNeIIFVc6@b@5KEqWOz));AthGJdQ9 z?0cGe%r$s051I#;`_)T3#b}eow>$f0`=7dOXK1C0$CQA0%*@7CY~V!BAsO7Xw%WXXx=%!pxx8fS0vHXG#OfjxwIf!iGad%X6?|l~YD< zp+ma6j|_!cJd$^JckRPhnE{;!Bd*iHlpHPTk6pBV3zC9BZrCalv|`(#iI=U!U7JiI zcg2Q>NHJtuZpdX)>7G**s}_%%bLC7lj+SZ})Hf>ACmDi=kA;JbPTtgU?6oUeCgWs$ zsGBZZ%k7P&+MWh}E$reWGvRa9`6lK0!n<(blmlC$7ra|el<@<6estiipe4!Q^eDZs z#CIrE+3PU*MHGcRt)m)F&dT;i8#G+V&uk3m>z|7`IZ%gD$ZFLQQkZYf>S2TqeB>+# z!+f_P$E;%>V}Q2{PIjVTQ+h3d-kR;TBuREw*Wfyl_K%8vhX|~C?gc&nKcJ~Q&*{SA zwMV1#jj|ZlgJ>t)MD4Gq!Jhy*iEg8Bz9kxijlg4zH`&ds%t&HC`B<%Vu*rLa1)Blf z&ogckXq;HFl)?#)1il~>*iMhg9dA{A31Yg~jSEQsg?LuYh0oA~-xGo9L>gU4-(Xog zHk>aoEFMB2o`qh~VN}JgytP!C9m!>RSI5do0}PH1+j`$^&{Mi5zCAAy;b2f~(mzJb zf4kgB8cnZIq(*^Kqa987i)c~3sI!N-B>vEuH7Ek+QQ%PTv~z}VPlx`?{LV|zVm?HRk%4*AI@>QuO}(cdd8RlaRbj=#{*jrW7y8TL zhCgWf8XgMjDjE*CMf4vX6QwkOv#z#`WY?JT#HE#})eino!;Fxk)z#KcpAojKos01k zmPrE4zlrx?TqyBa_ae`kUgPLeLo@>+!^O~B{n?ZnjP%rK=%_IheO38-l6yaQFd=>O z?52%1UiOg!`f61>hfNR(`!ulW7^hT}qg{4s&A4%P^Ed>0g5Y}kyA;u4J_A;EjCfW|up6JFrRmO* zBJY|_^q4l_`M;Krd{g$MJav(#wW?GpMqK~?ja)^D$o-`uP-eVz)pYbE%k_9Gij~e} z1p#OZIwLQa^N4eNIbiKa?b3y_&#!7 zwK!zf!mOL!&WGpdWEL330=XQz!@-G>x|ZU_lVVH+N9kWgn6p*#7GwoMyKiSAl=IE2 z|2Od)?LW5llK2q+p>r~_#4!$bWCTdm`L4gCk&w*g9K zK|tXdG_^_$O*LTzcNDU#+p}f6JqSPZx%r%@bOl}^v+}l{qBQs1TnDl}=BkjQw%TTw zQBbFRC^IC%e|cl$yFxuH(qn`43=(V9r9U7^ogVLJ^j4zL!n<#p~X05@_6M~`c+F~<4vMMPMGZj*3n`!3gBE%oAbz65xH^iG=9ws zeOK24 z@%6=avA1)Gg*5WPjVGWux~CZ*C17T3ZXP*3)X5he*ahIb-(R16u0K?GK-1^TQsNU5 zy3hI8`laq&JsJAvU(ayowTc*j7QE!A#mlSmag4K=H|gaa!5v+Sbbr3h;rK1;@fd`y zhKF9o^P>}UQCujh)B@fbD$MCegbu=iX)g9$Jd%3#M^&B5jJmkdGAKYYfx8o2^C06k zsugfp7&oRPh|Gz;I4CDE1Nc5o?Z?XQ?(O%|G2;y^I%d**vcp;?ns3DuCk~v}KpD?D z4nNYC{`3e+FZN*|e}sW+8P}!fp*8W_(Bp8T13c8dsV#&Ef;%q{#eQq&!I6B!;XJ|8 zlt4luf<5oN0s#kLW{Y%gE>QHkO*YuIkOfOj_ho<8<{jXY)!F?|9d1H+E6gNG8_#82 zZRGGoRdZEALX@q%ZZP*sWuGUo+qS;Q)7!Rs7I7jn)2@Hj-VL3wx|(F#j%&`Nr#6~% zORP$3qUA=!_%obX*9%z&#mtTmjQAb9U(aO%*Xc=z7}r%3GiMsYLrMw#q-;y*cD7cV z5l}=|h|$FgTO%jp9F|PuoPR#YYCz~UsA(MARiWr@m~=~ZZIa=_B-ys%T9}u75BfOx z=ZF&1X>NBlmnTr}P%Otb8l+LKgOnh%8%e6pD1lCNR5l#) zSvkkgIMY}y(dh5a+{-fRG>z|--Zx$@j(DVjr$d30`DsZN!D-TQ?E1_!>Smb)_6oQB zYSlgn2iRuG8mxtdNh8~yc7Hp@+AiynGC`I!X=Kq$Q={Cm5d$z-oU-8{OFIP}N0kW! z)iyB+6?OaM{Qjw_BOx2cZ0gtT20jO|uRH6Ucq^JdTp_dssvQsQIiP_)hb(qTWz zC#T%Pp|49Q$nF_*vs-6yE&X`Gjqh{hFzU2n$S9&#T%QFNMOkX)5_-w@bST@XWZ1qh z@KicCGvq*l9DVasOd?zKTahHQ*yg@RlkfdCacHyNU06PO5^7ZHQxnZSD1V0qZodsk z%}#wUvtcBWBUGwS^I8IJxcM1%5QbxpoK+Q0YUQFb;ar#T)(iS3HByJRWl4hHJDfzg z68HOr3lb=yIM-s#_{jJIvJmt$)gl}rJ`5vXyUa8@S>Vn7p}dOvSG^trL~FTc8cdPF zzr?EH@pv>Wi<5&KI0~>wxem781yct}S*2RQ2NW98%l0vqXvw0(kP<#}MwB?GPmnR- zlEDLiM3^VUG6mxeH4XA2bI@feOhSh%hwO+qfop0~YjLKUmc*2T{K6eAt|?0n^|KE^ z2}$xVn*guy7bK0~;jPoChR5oJ&!5kIwmGU)9C z!c&&+hw@;~n7K@7S0Dhv3^Ek+>-vvTL%ULCnvwX@hIv#b=pdKICmOq0tX@Bcyn}?7 zLwu~z{u}_!n|qgOwloE}su`D@dSH40iH8vF0~Fn%0DgDYnh-D~lWG!>QUznx=-Csv zR09Ws-}HHJ7tI+$JX�IQb!XDPu!a#MlnNZllea?~S3+>XRa%ezeWM9AsiQl?gyq zNoR+~EETvf@2u@B!}XkaGluX0!@o2aKAD09zlB$m@*`Ev(bCKI>BRji$Xg-bStDA z)yI`%eWXW|IE9ctv4h{sUBPC|p4vF=SI(7%u)Xatj{!lcwM|P- zfw0cwXG{+V4^`F^{cugBPehK;w=+q9FA-fHpj{>|f%@n3PpP{v=&%-Wdx*{wNfxYw zpT|YFs~?0;4X!v@sHKs%vpoj-jI#hQKRmxJHZ$u+w40aRbV^Y)5!$IgPDGR!$O;me zhy+T&f5EbU7c!`Cl|YPESAWa@T>yJo*1=!cTME4VUJI(O<4yUhy)QGnnGzf*(1nMN zZqGdhyDh&MGTfhhRZl>D(<{_o9a07ss8-#ygN-lK3=P5o@wF9Kwuf(`Zr4Et4K6>l zvSjM>9a>d+v8P2#HA}vOA~SVbg-O@V(l=_C1;DJyUdvLJdjHF-Cm^mzqi&@fTsquY zYM_e%S-U4weXFVDW%DPmrS)~|4<%#&r~wB8RfiM5amJ5_(dLmdP`)FbKrhztQWo5V zNm$%^s0QE89mBDhp_ZagoYJV1!e1!&^c7(Z?_|xjpZ9Zk4L3eQOpe6(U%-;pvA=zp z9KnhOB><#N&|wM*d-bZtEl-&{N@V#Nh{ftmnc36WY_Yz%58ip5bIOhQ%B-Ire)M`j z8Lz7sm`*`dLEZN6EaHNS3@I&vJ(U3+XDD72dxhhLBjVytTC#wWgfwx{>a9*H0&)_2 zhAXa<9=~xkRnXyvzvR_MkC#2QgIg&=4~7THS%{B}l-LfB(Y6(lL^F3m>L(D@Rl%*A zTJ4uR&Iy&BhFt0F)CC^ zHExNWCp}P58PyfS03VYmed0F2N61`KOe*(jU57h=?m%7&4z+L75ineX`ewm4D|X^4 z<-Wk7UBv$|-b=M@I((5EIKNxC&z$Z`wDBFX3W7)X$7Xl_Ydmu#t>zm%qt&f~=PBg% zStN&lS1@bEWop$LmfD5O2LzQD;7ooSq!GxF_Ovx2z{sovqbBrsOb1UtIuSz#PR4uU zcV0d26<_c+Vr~zAKBSgAu+LsKX>V?o|7TZzam_~bc=J%l;Ff!fJv)#U?K8bFFWj1l z_eG}3-h8z)U=So$hDrO5{wW101e3ai^z)rR2|}oekG?u~sYl~lVME{&G$bdjBvme9 G5b!@MJlKu^ literal 0 HcmV?d00001 diff --git a/atsebayt/svelte.config.js b/atsebayt/svelte.config.js new file mode 100644 index 0000000..39a4e55 --- /dev/null +++ b/atsebayt/svelte.config.js @@ -0,0 +1,13 @@ +import preprocess from 'svelte-preprocess'; + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + // Consult https://github.com/sveltejs/svelte-preprocess + // for more information about preprocessors + preprocess: preprocess(), + + kit: { + } +}; + +export default config; diff --git a/atsebayt/tsconfig.json b/atsebayt/tsconfig.json new file mode 100644 index 0000000..773bd1f --- /dev/null +++ b/atsebayt/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "moduleResolution": "node", + "module": "es2020", + "lib": ["es2020", "DOM"], + "target": "es2019", + /** + svelte-preprocess cannot figure out whether you have a value or a type, so tell TypeScript + to enforce using \`import type\` instead of \`import\` for Types. + */ + "importsNotUsedAsValues": "error", + "isolatedModules": true, + "resolveJsonModule": true, + /** + To have warnings/errors of the Svelte compiler at the correct position, + enable source maps by default. + */ + "sourceMap": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "baseUrl": ".", + "allowJs": true, + "checkJs": true, + "paths": { + "$lib": ["src/lib"], + "$lib/*": ["src/lib/*"] + } + }, + "include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.ts", "src/**/*.svelte"] +} diff --git a/auth.go b/auth.go index 8e6463f..71b08b7 100644 --- a/auth.go +++ b/auth.go @@ -3,6 +3,7 @@ package main import ( "encoding/base64" "encoding/json" + "fmt" "net/http" "time" @@ -26,39 +27,35 @@ func init() { } func validateAuthToken(u *User, _ httprouter.Params, _ []byte) HTTPResponse { - return APIResponse{u} + if u == nil { + return APIErrorResponse{status: http.StatusUnauthorized, err: fmt.Errorf("Not connected")} + } else { + return APIResponse{u} + } } func logout(w http.ResponseWriter, ps httprouter.Params, body []byte) HTTPResponse { - http.SetCookie(w, &http.Cookie{ - Name: "auth", - Value: "", - Path: baseURL + "/", - Expires: time.Unix(0, 0), - Secure: true, - HttpOnly: true, - SameSite: http.SameSiteStrictMode, - }) - + eraseCookie(w) return APIResponse{true} } -func completeAuth(w http.ResponseWriter, username string, email string, firstname string, lastname string, groups string, session *Session) (err error) { - var usr User +func completeAuth(w http.ResponseWriter, username string, email string, firstname string, lastname string, groups string, session *Session) (usr User, err error) { if !userExists(username) { if usr, err = NewUser(username, email, firstname, lastname, groups); err != nil { - return err + return } } else if usr, err = getUserByLogin(username); err != nil { - return err + return } - if len(groups) > 255 { - groups = groups[:255] - } - if usr.Groups != groups { - usr.Groups = groups - usr.Update() + if len(groups) > 0 { + if len(groups) > 255 { + groups = groups[:255] + } + if usr.Groups != groups { + usr.Groups = groups + usr.Update() + } } if session == nil { @@ -70,7 +67,7 @@ func completeAuth(w http.ResponseWriter, username string, email string, firstnam } if err != nil { - return err + return } http.SetCookie(w, &http.Cookie{ @@ -78,12 +75,12 @@ func completeAuth(w http.ResponseWriter, username string, email string, firstnam Value: base64.StdEncoding.EncodeToString(session.Id), Path: baseURL + "/", Expires: time.Now().Add(30 * 24 * time.Hour), - Secure: true, HttpOnly: true, SameSite: http.SameSiteStrictMode, + //Secure: true, }) - return nil + return } func dummyAuth(w http.ResponseWriter, _ httprouter.Params, body []byte) (interface{}, error) { @@ -92,5 +89,5 @@ func dummyAuth(w http.ResponseWriter, _ httprouter.Params, body []byte) (interfa return nil, err } - return map[string]string{"status": "OK"}, completeAuth(w, lf["login"], lf["email"], lf["firstname"], lf["lastname"], "", nil) + return completeAuth(w, lf["username"], lf["email"], lf["firstname"], lf["lastname"], "", nil) } diff --git a/auth_krb5.go b/auth_krb5.go index 636c7ea..012f09b 100644 --- a/auth_krb5.go +++ b/auth_krb5.go @@ -73,6 +73,6 @@ func checkAuthKrb5(w http.ResponseWriter, _ httprouter.Params, body []byte) (int } return nil, err } else { - return dummyAuth(w, nil, body) + return completeAuth(w, lf.Login, lf.Login+"@epita.fr", "", "", "", nil) } } diff --git a/auth_oidc.go b/auth_oidc.go index c9d1d7d..e9e8bf7 100644 --- a/auth_oidc.go +++ b/auth_oidc.go @@ -116,7 +116,7 @@ func OIDC_CRI_complete(w http.ResponseWriter, r *http.Request, ps httprouter.Par } } - if err := completeAuth(w, claims.Username, claims.Email, claims.Firstname, claims.Lastname, groups, &session); err != nil { + if _, err := completeAuth(w, claims.Username, claims.Email, claims.Firstname, claims.Lastname, groups, &session); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } diff --git a/handler.go b/handler.go index e28e993..d85cc61 100644 --- a/handler.go +++ b/handler.go @@ -8,6 +8,7 @@ import ( "io" "log" "net/http" + "time" "github.com/julienschmidt/httprouter" ) @@ -18,7 +19,6 @@ func Router() *httprouter.Router { return router } - type HTTPResponse interface { WriteResponse(http.ResponseWriter) } @@ -62,9 +62,19 @@ func (r APIErrorResponse) WriteResponse(w http.ResponseWriter) { http.Error(w, fmt.Sprintf("{\"errmsg\":%q}", r.err.Error()), r.status) } - type DispatchFunction func(httprouter.Params, []byte) HTTPResponse +func eraseCookie(w http.ResponseWriter) { + http.SetCookie(w, &http.Cookie{ + Name: "auth", + Value: "", + Path: baseURL + "/", + Expires: time.Unix(0, 0), + HttpOnly: true, + SameSite: http.SameSiteStrictMode, + }) +} + func rawHandler(f func(http.ResponseWriter, *http.Request, httprouter.Params, []byte), access ...func(*User, *http.Request) *APIErrorResponse) func(http.ResponseWriter, *http.Request, httprouter.Params) { return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { if addr := r.Header.Get("X-Forwarded-For"); addr != "" { @@ -76,16 +86,19 @@ func rawHandler(f func(http.ResponseWriter, *http.Request, httprouter.Params, [] var user *User = nil if cookie, err := r.Cookie("auth"); err == nil { if sessionid, err := base64.StdEncoding.DecodeString(cookie.Value); err != nil { + eraseCookie(w) w.Header().Set("Content-Type", "application/json") http.Error(w, fmt.Sprintf(`{"errmsg": %q}`, err.Error()), http.StatusNotAcceptable) return } else if session, err := getSession(sessionid); err != nil { + eraseCookie(w) w.Header().Set("Content-Type", "application/json") http.Error(w, fmt.Sprintf(`{"errmsg": %q}`, err.Error()), http.StatusUnauthorized) return } else if session.IdUser == nil { user = nil } else if std, err := getUser(int(*session.IdUser)); err != nil { + eraseCookie(w) w.Header().Set("Content-Type", "application/json") http.Error(w, fmt.Sprintf(`{"errmsg": %q}`, err.Error()), http.StatusUnauthorized) return @@ -134,22 +147,22 @@ func formatResponseHandler(f func(*http.Request, httprouter.Params, []byte) HTTP } func apiRawHandler(f func(http.ResponseWriter, httprouter.Params, []byte) HTTPResponse, access ...func(*User, *http.Request) *APIErrorResponse) func(http.ResponseWriter, *http.Request, httprouter.Params) { - return rawHandler(func (w http.ResponseWriter, r *http.Request, ps httprouter.Params, b []byte) { - formatResponseHandler(func (_ *http.Request, ps httprouter.Params, b []byte) HTTPResponse { + return rawHandler(func(w http.ResponseWriter, r *http.Request, ps httprouter.Params, b []byte) { + formatResponseHandler(func(_ *http.Request, ps httprouter.Params, b []byte) HTTPResponse { return f(w, ps, b) })(w, r, ps, b) }, access...) } func apiHandler(f DispatchFunction, access ...func(*User, *http.Request) *APIErrorResponse) func(http.ResponseWriter, *http.Request, httprouter.Params) { - return rawHandler(formatResponseHandler(func (_ *http.Request, ps httprouter.Params, b []byte) HTTPResponse { return f(ps, b) }), access...) + return rawHandler(formatResponseHandler(func(_ *http.Request, ps httprouter.Params, b []byte) HTTPResponse { return f(ps, b) }), access...) } func formatApiResponse(i interface{}, err error) HTTPResponse { if err != nil { return APIErrorResponse{ status: http.StatusBadRequest, - err: err, + err: err, } } else { return APIResponse{i} @@ -157,25 +170,25 @@ func formatApiResponse(i interface{}, err error) HTTPResponse { } func apiAuthHandler(f func(*User, httprouter.Params, []byte) HTTPResponse, access ...func(*User, *http.Request) *APIErrorResponse) func(http.ResponseWriter, *http.Request, httprouter.Params) { - return rawHandler(formatResponseHandler(func (r *http.Request, ps httprouter.Params, b []byte) HTTPResponse { + return rawHandler(formatResponseHandler(func(r *http.Request, ps httprouter.Params, b []byte) HTTPResponse { if cookie, err := r.Cookie("auth"); err != nil { return f(nil, ps, b) } else if sessionid, err := base64.StdEncoding.DecodeString(cookie.Value); err != nil { return APIErrorResponse{ status: http.StatusBadRequest, - err: err, + err: err, } } else if session, err := getSession(sessionid); err != nil { return APIErrorResponse{ status: http.StatusBadRequest, - err: err, + err: err, } } else if session.IdUser == nil { return f(nil, ps, b) } else if std, err := getUser(int(*session.IdUser)); err != nil { return APIErrorResponse{ status: http.StatusInternalServerError, - err: err, + err: err, } } else { return f(&std, ps, b) @@ -189,7 +202,7 @@ func loggedUser(u *User, r *http.Request) *APIErrorResponse { } else { ret := &APIErrorResponse{ status: http.StatusForbidden, - err: errors.New("Permission Denied"), + err: errors.New("Permission Denied"), } return ret } @@ -201,7 +214,7 @@ func adminRestricted(u *User, r *http.Request) *APIErrorResponse { } else { ret := &APIErrorResponse{ status: http.StatusForbidden, - err: errors.New("Permission Denied"), + err: errors.New("Permission Denied"), } return ret } diff --git a/main.go b/main.go index 1b2804f..7f4efeb 100644 --- a/main.go +++ b/main.go @@ -59,6 +59,7 @@ func StripPrefix(prefix string, h http.Handler) http.Handler { func main() { var bind = flag.String("bind", ":8081", "Bind port/socket") var dsn = flag.String("dsn", DSNGenerator(), "DSN to connect to the MySQL server") + var dummyauth = flag.Bool("dummy-auth", false, "If set, allow any authentication credentials") flag.StringVar(&DevProxy, "dev", DevProxy, "Proxify traffic to this host for static assets") flag.StringVar(&baseURL, "baseurl", baseURL, "URL prepended to each URL") flag.UintVar(¤tPromo, "current-promo", currentPromo, "Year of the current promotion") @@ -77,6 +78,10 @@ func main() { baseURL = "" } + if dummyauth != nil && *dummyauth == true { + LocalAuthFunc = dummyAuth + } + if DevProxy != "" { Router().GET("/.svelte-kit/*_", serveOrReverse("")) Router().GET("/node_modules/*_", serveOrReverse("")) diff --git a/static.go b/static.go index 2ea309e..1f6e67e 100644 --- a/static.go +++ b/static.go @@ -48,13 +48,15 @@ func serveOrReverse(forced_url string) func(w http.ResponseWriter, r *http.Reque } func init() { + Router().GET("/@fs/*_", serveOrReverse("")) Router().GET("/", serveOrReverse("")) - Router().GET("/auth", serveOrReverse("/")) - Router().GET("/grades", serveOrReverse("/")) - Router().GET("/surveys", serveOrReverse("/")) - Router().GET("/surveys/*_", serveOrReverse("/")) - Router().GET("/users", serveOrReverse("/")) - Router().GET("/users/*_", serveOrReverse("/")) + Router().GET("/auth", serveOrReverse("")) + Router().GET("/bug-bounty", serveOrReverse("")) + Router().GET("/grades", serveOrReverse("")) + Router().GET("/surveys", serveOrReverse("")) + Router().GET("/surveys/*_", serveOrReverse("")) + Router().GET("/users", serveOrReverse("")) + Router().GET("/users/*_", serveOrReverse("")) Router().GET("/css/*_", serveOrReverse("")) Router().GET("/fonts/*_", serveOrReverse("")) Router().GET("/img/*_", serveOrReverse("")) diff --git a/users.go b/users.go index d84bdca..8ed14a0 100644 --- a/users.go +++ b/users.go @@ -11,6 +11,10 @@ import ( var currentPromo uint = 0 func init() { + router.GET("/api/promos", apiHandler( + func(httprouter.Params, []byte) HTTPResponse { + return formatApiResponse(getPromos()) + }, adminRestricted)) router.GET("/api/users", apiHandler( func(httprouter.Params, []byte) HTTPResponse { return formatApiResponse(getUsers()) @@ -55,7 +59,7 @@ type User struct { } func getUsers() (users []User, err error) { - if rows, errr := DBQuery("SELECT id_user, login, email, firstname, lastname, time, promo, groups, is_admin FROM users"); errr != nil { + if rows, errr := DBQuery("SELECT id_user, login, email, firstname, lastname, time, promo, groups, is_admin FROM users ORDER BY promo DESC, id_user DESC"); errr != nil { return nil, errr } else { defer rows.Close() @@ -75,6 +79,27 @@ func getUsers() (users []User, err error) { } } +func getPromos() (promos []uint, err error) { + if rows, errr := DBQuery("SELECT DISTINCT promo FROM users ORDER BY promo DESC"); errr != nil { + return nil, errr + } else { + defer rows.Close() + + for rows.Next() { + var p uint + if err = rows.Scan(&p); err != nil { + return + } + promos = append(promos, p) + } + if err = rows.Err(); err != nil { + return + } + + return + } +} + func getUser(id int) (u User, err error) { err = DBQueryRow("SELECT id_user, login, email, firstname, lastname, time, promo, groups, is_admin FROM users WHERE id_user=?", id).Scan(&u.Id, &u.Login, &u.Email, &u.Firstname, &u.Lastname, &u.Time, &u.Promo, &u.Groups, &u.IsAdmin) return From 6a632238d52f282ebd7c39dd8dd2bebc89324331 Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Mon, 21 Feb 2022 07:13:39 +0100 Subject: [PATCH 04/16] Change update behaviour --- surveys.go | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/surveys.go b/surveys.go index 71334bf..bd6581a 100644 --- a/surveys.go +++ b/surveys.go @@ -157,13 +157,13 @@ func getSurvey(id int) (s Survey, err error) { return } -func NewSurvey(title string, promo uint, shown bool, startAvailability time.Time, endAvailability time.Time) (Survey, error) { +func NewSurvey(title string, promo uint, shown bool, startAvailability time.Time, endAvailability time.Time) (*Survey, error) { if res, err := DBExec("INSERT INTO surveys (title, promo, shown, start_availability, end_availability) VALUES (?, ?, ?, ?, ?)", title, promo, shown, startAvailability, endAvailability); err != nil { - return Survey{}, err + return nil, err } else if sid, err := res.LastInsertId(); err != nil { - return Survey{}, err + return nil, err } else { - return Survey{sid, title, promo, shown, false, startAvailability, endAvailability}, nil + return &Survey{sid, title, promo, shown, false, startAvailability, endAvailability}, nil } } @@ -200,13 +200,11 @@ func (s Survey) GetScores() (scores map[int64]*float64, err error) { return } -func (s Survey) Update() (int64, error) { - if res, err := DBExec("UPDATE surveys SET title = ?, promo = ?, shown = ?, corrected = ?, start_availability = ?, end_availability = ? WHERE id_survey = ?", s.Title, s.Promo, s.Shown, s.Corrected, s.StartAvailability, s.EndAvailability, s.Id); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err +func (s *Survey) Update() (*Survey, error) { + if _, err := DBExec("UPDATE surveys SET title = ?, promo = ?, shown = ?, corrected = ?, start_availability = ?, end_availability = ? WHERE id_survey = ?", s.Title, s.Promo, s.Shown, s.Corrected, s.StartAvailability, s.EndAvailability, s.Id); err != nil { + return nil, err } else { - return nb, err + return s, err } } From d4c809abed2275bef4297aeec1307f7c48196da5 Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Mon, 21 Feb 2022 07:16:25 +0100 Subject: [PATCH 05/16] Add missing proposals route --- proposals.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/proposals.go b/proposals.go index 2889516..cd45acd 100644 --- a/proposals.go +++ b/proposals.go @@ -12,6 +12,27 @@ func init() { func(q Question, u *User, _ []byte) HTTPResponse { return formatApiResponse(q.GetProposals()) }), loggedUser)) + router.POST("/api/questions/:qid/proposals", apiAuthHandler(questionAuthHandler(func(q Question, u *User, body []byte) HTTPResponse { + var new Proposal + if err := json.Unmarshal(body, &new); err != nil { + return APIErrorResponse{err: err} + } + + return formatApiResponse(q.NewProposal(new.Label)) + }), adminRestricted)) + router.PUT("/api/questions/:qid/proposals/:pid", apiAuthHandler(proposalAuthHandler(func(current Proposal, u *User, body []byte) HTTPResponse { + var new Proposal + if err := json.Unmarshal(body, &new); err != nil { + return APIErrorResponse{err: err} + } + + new.Id = current.Id + return formatApiResponse(new.Update()) + }), adminRestricted)) + router.DELETE("/api/questions/:qid/proposals/:pid", apiAuthHandler(proposalAuthHandler(func(p Proposal, u *User, body []byte) HTTPResponse { + return formatApiResponse(p.Delete()) + }), adminRestricted)) + router.GET("/api/surveys/:sid/questions/:qid/proposals", apiAuthHandler(questionAuthHandler( func(q Question, u *User, _ []byte) HTTPResponse { return formatApiResponse(q.GetProposals()) From 588089d11bea5d96631a72245c11d1621056ce83 Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Mon, 21 Feb 2022 07:21:11 +0100 Subject: [PATCH 06/16] Allow a 5 minutes grace time to send responses --- responses.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/responses.go b/responses.go index cc6ae4a..e41a62d 100644 --- a/responses.go +++ b/responses.go @@ -20,7 +20,7 @@ func init() { now := time.Now() if now.Before(s.StartAvailability) { return APIErrorResponse{err: fmt.Errorf("Le questionnaire n'a pas encore commencé")} - } else if now.After(s.EndAvailability) { + } else if now.After(s.EndAvailability.Add(5 * time.Minute)) { return APIErrorResponse{err: fmt.Errorf("Le questionnaire n'est plus ouvert")} } From b9dca498947775876d1b8822391c09066efd81a6 Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Mon, 21 Feb 2022 08:53:27 +0100 Subject: [PATCH 07/16] Can restrict survey to group --- atsebayt/src/components/SurveyAdmin.svelte | 9 ++++++ atsebayt/src/lib/surveys.js | 3 +- surveys.go | 34 +++++++++++++++------- 3 files changed, 35 insertions(+), 11 deletions(-) diff --git a/atsebayt/src/components/SurveyAdmin.svelte b/atsebayt/src/components/SurveyAdmin.svelte index b12c8f3..dc28e7d 100644 --- a/atsebayt/src/components/SurveyAdmin.svelte +++ b/atsebayt/src/components/SurveyAdmin.svelte @@ -51,6 +51,15 @@ +
+
+ +
+
+ +
+
+
diff --git a/atsebayt/src/lib/surveys.js b/atsebayt/src/lib/surveys.js index d1b6702..88d1669 100644 --- a/atsebayt/src/lib/surveys.js +++ b/atsebayt/src/lib/surveys.js @@ -8,10 +8,11 @@ class Survey { } } - update({ id, title, promo, shown, corrected, start_availability, end_availability }) { + update({ id, title, promo, group, shown, corrected, start_availability, end_availability }) { this.id = id; this.title = title; this.promo = promo; + this.group = group; this.shown = shown; this.corrected = corrected; if (this.start_availability != start_availability) { diff --git a/surveys.go b/surveys.go index bd6581a..83f135a 100644 --- a/surveys.go +++ b/surveys.go @@ -6,6 +6,7 @@ import ( "fmt" "net/http" "strconv" + "strings" "time" "github.com/julienschmidt/httprouter" @@ -19,7 +20,19 @@ func init() { } else if u.IsAdmin { return formatApiResponse(getSurveys("ORDER BY promo DESC, start_availability ASC")) } else { - return formatApiResponse(getSurveys(fmt.Sprintf("WHERE shown = TRUE AND promo = %d ORDER BY start_availability ASC", u.Promo))) + surveys, err := getSurveys(fmt.Sprintf("WHERE shown = TRUE AND promo = %d ORDER BY start_availability ASC", u.Promo)) + if err != nil { + return APIErrorResponse{err: err} + } + + var response []Survey + for _, s := range surveys { + if s.Group == "" || strings.Contains(u.Groups, ","+s.Group+",") { + response = append(response, s) + } + } + + return formatApiResponse(response, nil) } })) router.POST("/api/surveys", apiHandler(func(_ httprouter.Params, body []byte) HTTPResponse { @@ -32,11 +45,11 @@ func init() { new.Promo = currentPromo } - return formatApiResponse(NewSurvey(new.Title, new.Promo, new.Shown, new.StartAvailability, new.EndAvailability)) + return formatApiResponse(NewSurvey(new.Title, new.Promo, new.Group, new.Shown, new.StartAvailability, new.EndAvailability)) }, adminRestricted)) router.GET("/api/surveys/:sid", apiAuthHandler(surveyAuthHandler( func(s Survey, u *User, _ []byte) HTTPResponse { - if (s.Promo == u.Promo && s.Shown) || (u != nil && u.IsAdmin) { + if (s.Promo == u.Promo && (s.Group == "" || (u != nil && strings.Contains(u.Groups, ","+s.Group+",")) && s.Shown)) || (u != nil && u.IsAdmin) { return APIResponse{s} } else { return APIErrorResponse{ @@ -125,6 +138,7 @@ type Survey struct { Id int64 `json:"id"` Title string `json:"title"` Promo uint `json:"promo"` + Group string `json:"group"` Shown bool `json:"shown"` Corrected bool `json:"corrected"` StartAvailability time.Time `json:"start_availability"` @@ -132,14 +146,14 @@ type Survey struct { } func getSurveys(cnd string, param ...interface{}) (surveys []Survey, err error) { - if rows, errr := DBQuery("SELECT id_survey, title, promo, shown, corrected, start_availability, end_availability FROM surveys "+cnd, param...); errr != nil { + if rows, errr := DBQuery("SELECT id_survey, title, promo, grp, shown, corrected, start_availability, end_availability FROM surveys "+cnd, param...); errr != nil { return nil, errr } else { defer rows.Close() for rows.Next() { var s Survey - if err = rows.Scan(&s.Id, &s.Title, &s.Promo, &s.Shown, &s.Corrected, &s.StartAvailability, &s.EndAvailability); err != nil { + if err = rows.Scan(&s.Id, &s.Title, &s.Promo, &s.Group, &s.Shown, &s.Corrected, &s.StartAvailability, &s.EndAvailability); err != nil { return } surveys = append(surveys, s) @@ -153,17 +167,17 @@ func getSurveys(cnd string, param ...interface{}) (surveys []Survey, err error) } func getSurvey(id int) (s Survey, err error) { - err = DBQueryRow("SELECT id_survey, title, promo, shown, corrected, start_availability, end_availability FROM surveys WHERE id_survey=?", id).Scan(&s.Id, &s.Title, &s.Promo, &s.Shown, &s.Corrected, &s.StartAvailability, &s.EndAvailability) + err = DBQueryRow("SELECT id_survey, title, promo, grp, shown, corrected, start_availability, end_availability FROM surveys WHERE id_survey=?", id).Scan(&s.Id, &s.Title, &s.Promo, &s.Group, &s.Shown, &s.Corrected, &s.StartAvailability, &s.EndAvailability) return } -func NewSurvey(title string, promo uint, shown bool, startAvailability time.Time, endAvailability time.Time) (*Survey, error) { - if res, err := DBExec("INSERT INTO surveys (title, promo, shown, start_availability, end_availability) VALUES (?, ?, ?, ?, ?)", title, promo, shown, startAvailability, endAvailability); err != nil { +func NewSurvey(title string, promo uint, group string, shown bool, startAvailability time.Time, endAvailability time.Time) (*Survey, error) { + if res, err := DBExec("INSERT INTO surveys (title, promo, grp, shown, start_availability, end_availability) VALUES (?, ?, ?, ?, ?, ?)", title, promo, group, shown, startAvailability, endAvailability); err != nil { return nil, err } else if sid, err := res.LastInsertId(); err != nil { return nil, err } else { - return &Survey{sid, title, promo, shown, false, startAvailability, endAvailability}, nil + return &Survey{sid, title, promo, group, shown, false, startAvailability, endAvailability}, nil } } @@ -201,7 +215,7 @@ func (s Survey) GetScores() (scores map[int64]*float64, err error) { } func (s *Survey) Update() (*Survey, error) { - if _, err := DBExec("UPDATE surveys SET title = ?, promo = ?, shown = ?, corrected = ?, start_availability = ?, end_availability = ? WHERE id_survey = ?", s.Title, s.Promo, s.Shown, s.Corrected, s.StartAvailability, s.EndAvailability, s.Id); err != nil { + if _, err := DBExec("UPDATE surveys SET title = ?, promo = ?, grp = ?, shown = ?, corrected = ?, start_availability = ?, end_availability = ? WHERE id_survey = ?", s.Title, s.Promo, s.Group, s.Shown, s.Corrected, s.StartAvailability, s.EndAvailability, s.Id); err != nil { return nil, err } else { return s, err From 9f449d8188f512d68a93e830e78ed06c62b4f886 Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Mon, 21 Feb 2022 09:21:30 +0100 Subject: [PATCH 08/16] Display a message if not in the current promo --- atsebayt/src/routes/index.svelte | 26 ++++++++++++++++++++------ auth.go | 7 ++++++- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/atsebayt/src/routes/index.svelte b/atsebayt/src/routes/index.svelte index 24d274f..e1e991d 100644 --- a/atsebayt/src/routes/index.svelte +++ b/atsebayt/src/routes/index.svelte @@ -7,14 +7,28 @@
{#if $user} - avatar {$user.login} -

- Bienvenue {$user.firstname} ! -

-
+
+
+

+ Bienvenue {$user.firstname} ! +

+
+ + {#if $user.promo != $user.current_promo} + + {/if} + +

Tu as fait les rendus suivants :

+
+
+ avatar {$user.login} +
+
-

Tu as fait les rendus suivants :

+

Voici la liste des questionnaires :

{:else}

diff --git a/auth.go b/auth.go index 71b08b7..ddd0f4d 100644 --- a/auth.go +++ b/auth.go @@ -26,11 +26,16 @@ func init() { router.POST("/api/auth/logout", apiRawHandler(logout)) } +type authToken struct { + *User + CurrentPromo uint `json:"current_promo"` +} + func validateAuthToken(u *User, _ httprouter.Params, _ []byte) HTTPResponse { if u == nil { return APIErrorResponse{status: http.StatusUnauthorized, err: fmt.Errorf("Not connected")} } else { - return APIResponse{u} + return APIResponse{authToken{u, currentPromo}} } } From e3100e94e59baeff24d99230cccfe149c715ae65 Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Mon, 21 Feb 2022 12:11:44 +0100 Subject: [PATCH 09/16] Add need help page --- atsebayt/src/routes/help.svelte | 40 +++++++++++++++++++++++++++++++++ static.go | 1 + 2 files changed, 41 insertions(+) create mode 100644 atsebayt/src/routes/help.svelte diff --git a/atsebayt/src/routes/help.svelte b/atsebayt/src/routes/help.svelte new file mode 100644 index 0000000..ebd24c0 --- /dev/null +++ b/atsebayt/src/routes/help.svelte @@ -0,0 +1,40 @@ + + +

Besoin d'aide ?

+ +

+ Vous êtes nombreux et l'on n'est malheureusement pas en mesure de vous suivre régulièrement individuellement. + Nous restons néanmoins toujours disponibles lorsque vous avez besoin de notre aide. +

+ +

+ D'une manière générale, si vous avez des problèmes, n'hésitez pas à contacter le professeur, que ce soit en cours ou par mail. + Il vaut mieux mettre des mots soi-même sur un problème que l'on rencontre plutôt que d'attendre le dernier moment, en se disant qu'on aura le temps de trouver une solution. +

+ +

+ Peut-être que tu as raté·e plusieurs rendus, ou peut-être que tu ne te sens plus le courage de continuer les projets. +

+ +

+ Si tu souhaites me parler d'une situation qui t'a troublé·e, d'un problème que tu rencontres ou me faire une remarque, + n'hésite pas à venir me voir lors d'un cours, par exemple à la pause ou à la fin ; + je suis aussi joignable par e-mail ou bien sur Matrix ou Teams. +

+ +

+ Si tu souhaites juste avoir un peu plus d'attention, soit parce que tu te sens à l'écart, en difficulté ou autre : + +

+ +
diff --git a/static.go b/static.go index 1f6e67e..6cbca03 100644 --- a/static.go +++ b/static.go @@ -53,6 +53,7 @@ func init() { Router().GET("/auth", serveOrReverse("")) Router().GET("/bug-bounty", serveOrReverse("")) Router().GET("/grades", serveOrReverse("")) + Router().GET("/help", serveOrReverse("")) Router().GET("/surveys", serveOrReverse("")) Router().GET("/surveys/*_", serveOrReverse("")) Router().GET("/users", serveOrReverse("")) From df2da4221ed31076c2928ba6cb02bce1093b6865 Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Mon, 21 Feb 2022 14:59:27 +0100 Subject: [PATCH 10/16] Do grading page --- atsebayt/src/components/StudentGrades.svelte | 86 ++++++++++++++++---- atsebayt/src/lib/users.js | 8 ++ 2 files changed, 77 insertions(+), 17 deletions(-) diff --git a/atsebayt/src/components/StudentGrades.svelte b/atsebayt/src/components/StudentGrades.svelte index 44d9e46..d9668b1 100644 --- a/atsebayt/src/components/StudentGrades.svelte +++ b/atsebayt/src/components/StudentGrades.svelte @@ -1,25 +1,77 @@ +{#await getPromos() then promos} +
+ +
+{/await}

- Étudiants {#if promo}{promo}{/if} + Étudiants {#if promo !== null}{promo}{/if} Notes

- - - - - - - - - - - - - - - -
IDLogin{ survey.title }
{ user.id }{ user.login }{ grades[user.id][survey.id] }
+{#await getSurveys()} +
+
+ Chargement des questionnaires corrigés… +
+{:then surveys} + {#await getGrades()} +
+
+ Chargement des notes… +
+ {:then grades} +
+ + + + + + {#each surveys as survey (survey.id)} + {#if survey.corrected && (promo === null || survey.promo == promo)} + + {/if} + {/each} + + + + {#await getUsers()} + + + + {:then users} + {#each users as user (user.id)} + {#if promo === null || user.promo === promo} + + + + {#each surveys as survey (survey.id)} + {#if survey.corrected && (promo === null || survey.promo == promo)} + + {/if} + {/each} + + {/if} + {/each} + {/await} + +
IDLogin{survey.title}
+
+
+ Chargement des étudiants… +
+
{user.id}{user.login}{grades[user.id][survey.id]?grades[user.id][survey.id]:"N/A"}
+
+ {/await} +{/await} diff --git a/atsebayt/src/lib/users.js b/atsebayt/src/lib/users.js index a6e00d8..51847e2 100644 --- a/atsebayt/src/lib/users.js +++ b/atsebayt/src/lib/users.js @@ -25,6 +25,14 @@ export async function getUser(uid) { } } +export async function getGrades(uid, survey) { + const res = await fetch(`api/grades`, {headers: {'Accept': 'application/json'}}) + if (res.status == 200) { + return await res.json(); + } else { + throw new Error((await res.json()).errmsg); + } +} export async function getUserGrade(uid, survey) { const res = await fetch(`api/users/${uid}/surveys/${survey.id}/grades`, {headers: {'Accept': 'application/json'}}) if (res.status == 200) { From 11c49bcb344a8e382deb6028dc493bad23ca8a0d Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Mon, 28 Feb 2022 10:52:27 +0100 Subject: [PATCH 11/16] Able to make corrections --- atsebayt/src/components/Correction.svelte | 47 ++++++ .../src/components/CorrectionReference.svelte | 112 +++++++++++++ .../CorrectionResponseFooter.svelte | 124 ++++++++++++++ .../src/components/CorrectionResponses.svelte | 130 +++++++++++++++ atsebayt/src/components/QuestionForm.svelte | 50 +++--- atsebayt/src/components/QuestionHeader.svelte | 44 +++++ .../src/components/QuestionProposals.svelte | 16 +- .../src/components/ResponseCorrected.svelte | 52 ++++++ atsebayt/src/components/SurveyList.svelte | 2 +- .../src/components/SurveyQuestions.svelte | 15 +- atsebayt/src/lib/correctionTemplates.js | 88 ++++++++++ atsebayt/src/lib/questions.js | 23 +++ atsebayt/src/lib/response.js | 34 ++++ atsebayt/src/lib/surveys.js | 1 - atsebayt/src/routes/__layout.svelte | 4 + .../src/routes/surveys/[sid]/__layout.svelte | 39 +++++ .../{[sid].svelte => [sid]/index.svelte} | 37 ++--- .../surveys/[sid]/responses/[rid].svelte | 156 ++++++++++++++++++ .../surveys/[sid]/responses/index.svelte | 85 ++++++++++ corrections.go | 89 +++++++++- db.go | 1 + responses.go | 16 ++ 22 files changed, 1085 insertions(+), 80 deletions(-) create mode 100644 atsebayt/src/components/Correction.svelte create mode 100644 atsebayt/src/components/CorrectionReference.svelte create mode 100644 atsebayt/src/components/CorrectionResponseFooter.svelte create mode 100644 atsebayt/src/components/CorrectionResponses.svelte create mode 100644 atsebayt/src/components/QuestionHeader.svelte create mode 100644 atsebayt/src/components/ResponseCorrected.svelte create mode 100644 atsebayt/src/lib/correctionTemplates.js create mode 100644 atsebayt/src/lib/response.js create mode 100644 atsebayt/src/routes/surveys/[sid]/__layout.svelte rename atsebayt/src/routes/surveys/{[sid].svelte => [sid]/index.svelte} (56%) create mode 100644 atsebayt/src/routes/surveys/[sid]/responses/[rid].svelte create mode 100644 atsebayt/src/routes/surveys/[sid]/responses/index.svelte diff --git a/atsebayt/src/components/Correction.svelte b/atsebayt/src/components/Correction.svelte new file mode 100644 index 0000000..3caae1a --- /dev/null +++ b/atsebayt/src/components/Correction.svelte @@ -0,0 +1,47 @@ + + +{#if question && (question.kind == 'mcq' || question.kind == 'ucq')} + {#await question.getProposals()} +
+
+ Récupération des propositions… +
+ {:then proposals} + dispatch('nb_responses', v.detail)} + /> + {/await} +{:else} + dispatch('nb_responses', v.detail)} + /> +{/if} diff --git a/atsebayt/src/components/CorrectionReference.svelte b/atsebayt/src/components/CorrectionReference.svelte new file mode 100644 index 0000000..83b62fd --- /dev/null +++ b/atsebayt/src/components/CorrectionReference.svelte @@ -0,0 +1,112 @@ + + +
+ {#each templates as template (template.id)} +
submitTemplate(template)}> +
+
+ + +
+
+
+ +
+
+ +
+
+ +
+
+
+ {#if cts && template.id && cts[template.id.toString()]} + {Math.trunc(Object.keys(cts[template.id.toString()]).length/nb_responses*1000)/10} % + {:else} + N/A + {/if} +
+
+ + +
+
+
+ {/each} + +
diff --git a/atsebayt/src/components/CorrectionResponseFooter.svelte b/atsebayt/src/components/CorrectionResponseFooter.svelte new file mode 100644 index 0000000..bcf3c8a --- /dev/null +++ b/atsebayt/src/components/CorrectionResponseFooter.svelte @@ -0,0 +1,124 @@ + + +
+
+ +
+
+
+ {#each templates as template (template.id)} +
+ {my_tpls[template.id] = !my_tpls[template.id]; autoCorrection(response.id_user, my_tpls).then((r) => my_correction = r); }} + checked={my_tpls[template.id]} + > + +
+ {/each} +
+
+
+ +
+ + {#if my_correction} + + {/if} + /100 +
+ + +
+
+{#if my_correction} +
100} + class:alert-success={my_correction.score >= 95 && my_correction.score <= 100} + class:alert-info={my_correction.score < 95 && my_correction.score >= 70} + class:alert-warning={my_correction.score < 70 && my_correction.score >= 45} + class:alert-danger={my_correction.score < 45} + > + + {my_correction.score} % + +
+ {my_correction.score_explaination} +
+
+{/if} diff --git a/atsebayt/src/components/CorrectionResponses.svelte b/atsebayt/src/components/CorrectionResponses.svelte new file mode 100644 index 0000000..e36b584 --- /dev/null +++ b/atsebayt/src/components/CorrectionResponses.svelte @@ -0,0 +1,130 @@ + + +{#await req_responses} +
+
+ Récupération des réponses… +
+{:then} + {#each filteredResponses as response, rid (response.id)} +
+
+
+
+ {#if question.kind == 'mcq' || question.kind == 'ucq'} + {#if !proposals} +
+ Une erreur s'est produite, aucune proposition n'a été chargée +
+ {:else} + + {/if} + {:else} +

+ {response.value} +

+ {/if} + +
+ +
+
+ {#if showStudent} +
+
+ {#await getUser(response.id_user)} +
+ {:then user} + + avatar {user.login} +
+ {user.login} +
+
+ {/await} +
+
+ {/if} +
+ {/each} +{/await} diff --git a/atsebayt/src/components/QuestionForm.svelte b/atsebayt/src/components/QuestionForm.svelte index 841292d..71b9449 100644 --- a/atsebayt/src/components/QuestionForm.svelte +++ b/atsebayt/src/components/QuestionForm.svelte @@ -1,16 +1,20 @@
-
- {#if $user.is_admin} + + {#if $user && $user.is_admin} @@ -43,34 +51,7 @@ {/if} {/if} - - {#if edit} -
- -
-
- {:else} -

{qid + 1}. {question.title}

- {/if} - - {#if edit} -
- -
- -
-
- - - {:else if question.description} -

{@html question.description}

- {/if} -
+
{#if false && response_history}
@@ -132,6 +113,13 @@ {/if} + {#if survey.corrected} + + {/if} + {#if false}
diff --git a/atsebayt/src/components/QuestionHeader.svelte b/atsebayt/src/components/QuestionHeader.svelte new file mode 100644 index 0000000..32ea803 --- /dev/null +++ b/atsebayt/src/components/QuestionHeader.svelte @@ -0,0 +1,44 @@ + + +
+ + + {#if edit} +
+ +
+
+ {:else} +

{#if qid !== null}{qid + 1}. {/if}{question.title}

+ {/if} + + {#if edit} +
+ +
+ +
+
+ + + {:else if question.description} +

{@html question.description}

+ {/if} +
diff --git a/atsebayt/src/components/QuestionProposals.svelte b/atsebayt/src/components/QuestionProposals.svelte index ab990f1..f3c0fb2 100644 --- a/atsebayt/src/components/QuestionProposals.svelte +++ b/atsebayt/src/components/QuestionProposals.svelte @@ -4,13 +4,13 @@ export let edit = false; export let proposals = []; export let kind = 'mcq'; + export let prefixid = ''; export let readonly = false; export let id_question = 0; export let value; let valueCheck = []; $: { - console.log(value); if (value) { valueCheck = value.split(','); } @@ -31,10 +31,10 @@ type="checkbox" class="form-check-input" disabled={readonly} - name={'proposal' + proposal.id_question} - id={'p' + proposal.id} + name={prefixid + 'proposal' + proposal.id_question} + id={prefixid + 'p' + proposal.id} bind:group={valueCheck} - value={String(proposal.id)} + value={proposal.id.toString()} on:change={() => { value = valueCheck.join(',')}} > {:else} @@ -42,10 +42,10 @@ type="radio" class="form-check-input" disabled={readonly} - name={'proposal' + proposal.id_question} - id={'p' + proposal.id} + name={prefixid + 'proposal' + proposal.id_question} + id={prefixid + 'p' + proposal.id} bind:group={value} - value={String(proposal.id)} + value={proposal.id.toString()} > {/if} {#if edit} @@ -80,7 +80,7 @@ {:else} diff --git a/atsebayt/src/components/ResponseCorrected.svelte b/atsebayt/src/components/ResponseCorrected.svelte new file mode 100644 index 0000000..54f3eea --- /dev/null +++ b/atsebayt/src/components/ResponseCorrected.svelte @@ -0,0 +1,52 @@ + + +{#if response.score !== undefined} +
= 95} + class:alert-info={response.score < 95 && response.score >= 70} + class:alert-warning={response.score < 70 && response.score >= 45} + class:alert-danger={response.score < 45} + > +
+ + {response.score} % + +
+
+ {#if response.score_explaination} + {response.score_explaination} + {:else if response.score === 100} + + {/if} +
+
+{:else if response && survey} + {#if response.value} +
+
+ 🤯 +
+
+ Oups, tu sembles être passé entre les mailles du filet ! + Cette question a bien été corrigée, mais une erreur s'est produite dans la correction de ta réponse. + Contacte ton enseignant au plus vite. +
+
+ {:else} +
+
+ 😟 +
+
+ Tu n'as pas répondu à cette question. + Que s'est-il passé ? +
+
+ {/if} +{/if} diff --git a/atsebayt/src/components/SurveyList.svelte b/atsebayt/src/components/SurveyList.svelte index 0c2e2b8..807f079 100644 --- a/atsebayt/src/components/SurveyList.svelte +++ b/atsebayt/src/components/SurveyList.svelte @@ -36,7 +36,7 @@ {/if} - goto(`surveys/${survey.id}`)}> + goto($user.is_admin?`surveys/${survey.id}/responses`:`surveys/${survey.id}`)}> {survey.title} diff --git a/atsebayt/src/components/SurveyQuestions.svelte b/atsebayt/src/components/SurveyQuestions.svelte index 3f5de62..a906418 100644 --- a/atsebayt/src/components/SurveyQuestions.svelte +++ b/atsebayt/src/components/SurveyQuestions.svelte @@ -68,6 +68,7 @@
{#each questions as question, qid (question.id)} - + {#if !survey.corrected || $user.is_admin} + + {/if} {#if $user && $user.is_admin} @@ -51,12 +48,4 @@ {:then questions} {/await} -{:catch error} -
-

- < - Questionnaire introuvable -

- {error} -
{/await} diff --git a/atsebayt/src/routes/surveys/[sid]/responses/[rid].svelte b/atsebayt/src/routes/surveys/[sid]/responses/[rid].svelte new file mode 100644 index 0000000..1303d6b --- /dev/null +++ b/atsebayt/src/routes/surveys/[sid]/responses/[rid].svelte @@ -0,0 +1,156 @@ + + + + +{#await surveyP then survey} + {#await getQuestion(rid)} +
+
+ Chargement de la question… +
+ {:then question} + {#await ctpls} +
+
+ Chargement de la question… +
+ {:then correctionTemplates} +
+ +
+
+

+ < + {survey.title} + Corrections +

+ +
+ +
+ + + {#if showResponses} + + {/if} + + + + {#if showResponses} + + {/if} +
+ + { nb_responses = v.detail; } } + /> + {/await} + {/await} +{/await} + +
diff --git a/atsebayt/src/routes/surveys/[sid]/responses/index.svelte b/atsebayt/src/routes/surveys/[sid]/responses/index.svelte new file mode 100644 index 0000000..0c344ba --- /dev/null +++ b/atsebayt/src/routes/surveys/[sid]/responses/index.svelte @@ -0,0 +1,85 @@ + + + + +{#await surveyP then survey} +
+

+ < + {survey.title} + Corrections +

+ +
+ + {#await getQuestions(survey.id)} +
+
+ Chargement des questions … +
+ {:then questions} +
+ + + + + + + + + + {#each questions as question (question.id)} + + + {#await question.getResponses()} + + {:then responses} + + + {/await} + + {/each} + + + + + + + +
QuestionRéponsesMoyenne
{question.title} +
+ Chargement … +
+ {#if responses} + {responses.filter((r) => !r.time_scored).length} / + {responses.length} + {:else} + 0 + {/if} + + {#if responses && responses.filter((r) => r.time_scored).length} + {responses.reduce((p, c) => (p + c.score?c.score:0), 0)/responses.filter((r) => r.time_scored).length} + {:else} + -- % + {/if} +
Moyenne %
+
+ {/await} +{/await} diff --git a/corrections.go b/corrections.go index bed8721..8402e37 100644 --- a/corrections.go +++ b/corrections.go @@ -23,7 +23,7 @@ func init() { return APIErrorResponse{err: err} } - return formatApiResponse(q.NewCorrectionTemplate(new.Label, new.Score, new.ScoreExplaination)) + return formatApiResponse(q.NewCorrectionTemplate(new.Label, new.RegExp, new.Score, new.ScoreExplaination)) }), adminRestricted)) router.GET("/api/surveys/:sid/questions/:qid/corrections/:cid", apiHandler(correctionHandler( @@ -51,6 +51,48 @@ func init() { return formatApiResponse(ct.Delete()) }), adminRestricted)) + router.GET("/api/questions/:qid/corrections", apiHandler(questionHandler( + func(q Question, _ []byte) HTTPResponse { + if cts, err := q.GetCorrectionTemplates(); err != nil { + return APIErrorResponse{err: err} + } else { + return APIResponse{cts} + } + }), adminRestricted)) + router.POST("/api/questions/:qid/corrections", apiHandler(questionHandler(func(q Question, body []byte) HTTPResponse { + var new CorrectionTemplate + if err := json.Unmarshal(body, &new); err != nil { + return APIErrorResponse{err: err} + } + + return formatApiResponse(q.NewCorrectionTemplate(new.Label, new.RegExp, new.Score, new.ScoreExplaination)) + }), adminRestricted)) + + router.GET("/api/questions/:qid/corrections/:cid", apiHandler(correctionHandler( + func(ct CorrectionTemplate, _ []byte) HTTPResponse { + if users, err := ct.GetUserCorrected(); err != nil { + return APIErrorResponse{err: err} + } else { + return APIResponse{users} + } + }), adminRestricted)) + router.PUT("/api/questions/:qid/corrections/:cid", apiHandler(correctionHandler(func(current CorrectionTemplate, body []byte) HTTPResponse { + var new CorrectionTemplate + if err := json.Unmarshal(body, &new); err != nil { + return APIErrorResponse{err: err} + } + + new.Id = current.Id + if err := new.Update(); err != nil { + return APIErrorResponse{err: err} + } else { + return APIResponse{new} + } + }), adminRestricted)) + router.DELETE("/api/questions/:qid/corrections/:cid", apiHandler(correctionHandler(func(ct CorrectionTemplate, body []byte) HTTPResponse { + return formatApiResponse(ct.Delete()) + }), adminRestricted)) + router.GET("/api/users/:uid/questions/:qid", apiAuthHandler(func(u *User, ps httprouter.Params, body []byte) HTTPResponse { return userHandler(func(u User, _ []byte) HTTPResponse { if qid, err := strconv.Atoi(string(ps.ByName("qid"))); err != nil { @@ -75,6 +117,14 @@ func init() { return formatApiResponse(u.NewCorrection(new.IdTemplate)) }), adminRestricted)) + router.PUT("/api/users/:uid/corrections", apiHandler(userHandler(func(u User, body []byte) HTTPResponse { + var new map[int64]bool + if err := json.Unmarshal(body, &new); err != nil { + return APIErrorResponse{err: err} + } + + return formatApiResponse(u.EraseCorrections(new)) + }), adminRestricted)) router.DELETE("/api/users/:uid/corrections/:cid", apiHandler(userCorrectionHandler(func(u User, uc UserCorrection, body []byte) HTTPResponse { return formatApiResponse(uc.Delete(u)) }), adminRestricted)) @@ -112,19 +162,20 @@ type CorrectionTemplate struct { Id int64 `json:"id"` IdQuestion int64 `json:"id_question"` Label string `json:"label"` + RegExp string `json:"regexp"` Score int `json:"score"` ScoreExplaination string `json:"score_explaination,omitempty"` } func (q *Question) GetCorrectionTemplates() (ct []CorrectionTemplate, err error) { - if rows, errr := DBQuery("SELECT id_template, id_question, label, score, score_explanation FROM correction_templates WHERE id_question=?", q.Id); errr != nil { + if rows, errr := DBQuery("SELECT id_template, id_question, label, re, score, score_explanation FROM correction_templates WHERE id_question=?", q.Id); errr != nil { return nil, errr } else { defer rows.Close() for rows.Next() { var c CorrectionTemplate - if err = rows.Scan(&c.Id, &c.IdQuestion, &c.Label, &c.Score, &c.ScoreExplaination); err != nil { + if err = rows.Scan(&c.Id, &c.IdQuestion, &c.Label, &c.RegExp, &c.Score, &c.ScoreExplaination); err != nil { return } ct = append(ct, c) @@ -138,27 +189,27 @@ func (q *Question) GetCorrectionTemplates() (ct []CorrectionTemplate, err error) } func (q *Question) GetCorrectionTemplate(id int) (c CorrectionTemplate, err error) { - err = DBQueryRow("SELECT id_template, id_question, label, score, score_explanation FROM correction_templates WHERE id_question=? AND id_template=?", q.Id, id).Scan(&c.Id, &c.IdQuestion, &c.Label, &c.Score, &c.ScoreExplaination) + err = DBQueryRow("SELECT id_template, id_question, label, re, score, score_explanation FROM correction_templates WHERE id_question=? AND id_template=?", q.Id, id).Scan(&c.Id, &c.IdQuestion, &c.Label, &c.RegExp, &c.Score, &c.ScoreExplaination) return } func GetCorrectionTemplate(id int64) (c CorrectionTemplate, err error) { - err = DBQueryRow("SELECT id_template, id_question, label, score, score_explanation FROM correction_templates WHERE id_template=?", id).Scan(&c.Id, &c.IdQuestion, &c.Label, &c.Score, &c.ScoreExplaination) + err = DBQueryRow("SELECT id_template, id_question, label, re, score, score_explanation FROM correction_templates WHERE id_template=?", id).Scan(&c.Id, &c.IdQuestion, &c.Label, &c.RegExp, &c.Score, &c.ScoreExplaination) return } -func (q *Question) NewCorrectionTemplate(label string, score int, score_explaination string) (CorrectionTemplate, error) { - if res, err := DBExec("INSERT INTO correction_templates (id_question, label, score, score_explanation) VALUES (?, ?, ?, ?)", q.Id, label, score, score_explaination); err != nil { +func (q *Question) NewCorrectionTemplate(label string, regexp string, score int, score_explaination string) (CorrectionTemplate, error) { + if res, err := DBExec("INSERT INTO correction_templates (id_question, label, re, score, score_explanation) VALUES (?, ?, ?, ?, ?)", q.Id, label, regexp, score, score_explaination); err != nil { return CorrectionTemplate{}, err } else if cid, err := res.LastInsertId(); err != nil { return CorrectionTemplate{}, err } else { - return CorrectionTemplate{cid, q.Id, label, score, score_explaination}, nil + return CorrectionTemplate{cid, q.Id, label, regexp, score, score_explaination}, nil } } func (t *CorrectionTemplate) Update() error { - _, err := DBExec("UPDATE correction_templates SET id_question = ?, label = ?, score = ?, score_explanation = ? WHERE id_template = ?", t.IdQuestion, t.Label, t.Score, t.ScoreExplaination, t.Id) + _, err := DBExec("UPDATE correction_templates SET id_question = ?, label = ?, re = ?, score = ?, score_explanation = ? WHERE id_template = ?", t.IdQuestion, t.Label, t.RegExp, t.Score, t.ScoreExplaination, t.Id) return err } @@ -269,6 +320,26 @@ func (u *User) NewCorrection(id int64) (*UserCorrectionSummary, error) { } } +func (u *User) EraseCorrections(ids map[int64]bool) (*UserCorrectionSummary, error) { + var lastid int64 + + for id, st := range ids { + lastid = id + if st { + DBExec("INSERT INTO student_corrected (id_user, id_template) VALUES (?, ?)", u.Id, id) + } else { + DBExec("DELETE FROM student_corrected WHERE id_user = ? AND id_template = ?", u.Id, id) + } + + } + + if ucs, err := u.ComputeScoreQuestion(lastid); err != nil { + return nil, err + } else { + return ucs, nil + } +} + func (c *UserCorrection) Delete(u User) (*UserCorrectionSummary, error) { if res, err := DBExec("DELETE FROM student_corrected WHERE id_correction = ?", c.Id); err != nil { return nil, err diff --git a/db.go b/db.go index 1c52d66..2226db8 100644 --- a/db.go +++ b/db.go @@ -136,6 +136,7 @@ CREATE TABLE IF NOT EXISTS correction_templates( id_template INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, id_question INTEGER NOT NULL, label VARCHAR(255) NOT NULL, + re VARCHAR(255) NOT NULL, score INTEGER, score_explanation TEXT, FOREIGN KEY(id_question) REFERENCES survey_quests(id_question) diff --git a/responses.go b/responses.go index e41a62d..d89f13a 100644 --- a/responses.go +++ b/responses.go @@ -85,6 +85,22 @@ func init() { new.TimeScored = &now } + new.Id = current.Id + new.IdUser = current.IdUser + return formatApiResponse(new.Update()) + }), adminRestricted)) + router.PUT("/api/questions/:qid/responses/:rid", apiAuthHandler(responseAuthHandler(func(current Response, u *User, body []byte) HTTPResponse { + var new Response + if err := json.Unmarshal(body, &new); err != nil { + return APIErrorResponse{err: err} + } + + if new.Score != nil && (current.Score == nil || *new.Score != *current.Score) { + now := time.Now() + new.IdCorrector = &u.Id + new.TimeScored = &now + } + new.Id = current.Id new.IdUser = current.IdUser return formatApiResponse(new.Update()) From 1e17c7bb4001158c5a9ceb37fc9a00a74221da26 Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Mon, 28 Feb 2022 17:45:30 +0100 Subject: [PATCH 12/16] Improve score retrieval --- db.go | 2 +- surveys.go | 18 +++++++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/db.go b/db.go index 2226db8..98883bb 100644 --- a/db.go +++ b/db.go @@ -156,7 +156,7 @@ CREATE TABLE IF NOT EXISTS student_corrected( return err } if _, err := db.Exec(` -CREATE VIEW IF NOT EXISTS student_scores AS SELECT U.id_user, id_survey, Q.id_question, MAX(R.score) as score FROM survey_quests Q CROSS JOIN users U LEFT JOIN survey_responses R ON Q.id_question = R.id_question AND R.id_user = U.id_user GROUP BY Q.id_question, U.id_user; +CREATE VIEW IF NOT EXISTS student_scores AS SELECT T.id_user, T.id_survey, Q.id_question, MAX(R.score) AS score FROM (SELECT DISTINCT R.id_user, S.id_survey FROM survey_responses R INNER JOIN survey_quests Q ON R.id_question = Q.id_question INNER JOIN surveys S ON Q.id_survey = S.id_survey) T LEFT OUTER JOIN survey_quests Q ON T.id_survey = Q.id_survey LEFT OUTER JOIN survey_responses R ON R.id_user = T.id_user AND Q.id_question = R.id_question GROUP BY id_user, id_survey, id_question; `); err != nil { return err } diff --git a/surveys.go b/surveys.go index 83f135a..872d971 100644 --- a/surveys.go +++ b/surveys.go @@ -12,6 +12,10 @@ import ( "github.com/julienschmidt/httprouter" ) +var ( + _score_cache = map[int64]map[int64]*float64{} +) + func init() { router.GET("/api/surveys", apiAuthHandler( func(u *User, _ httprouter.Params, _ []byte) HTTPResponse { @@ -182,9 +186,17 @@ func NewSurvey(title string, promo uint, group string, shown bool, startAvailabi } func (s Survey) GetScore(u *User) (score *float64, err error) { - err = DBQueryRow("SELECT SUM(score)/COUNT(*) FROM student_scores WHERE id_survey=? AND id_user=?", s.Id, u.Id).Scan(&score) - if score != nil { - *score = *score / 5.0 + if _, ok := _score_cache[u.Id]; !ok { + _score_cache[u.Id] = map[int64]*float64{} + } + if v, ok := _score_cache[u.Id][s.Id]; ok { + score = v + } else { + err = DBQueryRow("SELECT SUM(score)/COUNT(*) FROM student_scores WHERE id_survey=? AND id_user=?", s.Id, u.Id).Scan(&score) + if score != nil { + *score = *score / 5.0 + } + _score_cache[u.Id][s.Id] = score } return } From 0e5961c406f0fab726a456cb6e15fe995a7666cf Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Mon, 28 Feb 2022 19:00:30 +0100 Subject: [PATCH 13/16] Working on live surveys --- atsebayt/src/components/QuestionForm.svelte | 21 +- .../src/components/QuestionProposal.svelte | 21 - .../src/components/QuestionProposals.svelte | 7 +- atsebayt/src/components/SurveyAdmin.svelte | 19 + atsebayt/src/components/SurveyBadge.svelte | 3 +- atsebayt/src/components/SurveyList.svelte | 15 +- atsebayt/src/lib/surveys.js | 3 +- atsebayt/src/routes/index.svelte | 10 +- .../src/routes/surveys/[sid]/admin.svelte | 370 ++++++++++++++++++ .../src/routes/surveys/[sid]/index.svelte | 19 +- atsebayt/src/routes/surveys/[sid]/live.svelte | 118 ++++++ db.go | 1 + direct.go | 358 +++++++++++++++++ go.mod | 1 + go.sum | 26 ++ handler.go | 8 +- questions.go | 17 +- responses.go | 8 +- surveys.go | 37 +- 19 files changed, 1014 insertions(+), 48 deletions(-) delete mode 100644 atsebayt/src/components/QuestionProposal.svelte create mode 100644 atsebayt/src/routes/surveys/[sid]/admin.svelte create mode 100644 atsebayt/src/routes/surveys/[sid]/live.svelte create mode 100644 direct.go diff --git a/atsebayt/src/components/QuestionForm.svelte b/atsebayt/src/components/QuestionForm.svelte index 71b9449..27d7144 100644 --- a/atsebayt/src/components/QuestionForm.svelte +++ b/atsebayt/src/components/QuestionForm.svelte @@ -52,6 +52,7 @@ {/if} {/if} +
{#if false && response_history}
@@ -88,6 +89,7 @@ {proposals} readonly bind:value={value} + on:change={() => { dispatch("change"); }} /> {/await} {/if} @@ -103,17 +105,30 @@ {proposals} {readonly} bind:value={value} + on:change={() => { dispatch("change"); }} /> {/await} {:else if readonly}

{value}

{:else if question.kind == 'int'} - + { dispatch("change"); }} + > {:else} - + {/if} - {#if survey.corrected} + {#if survey && survey.corrected} - export let proposal = null; - export let kind = 'mcq'; - export let value; - - let inputType = 'checkbox'; - $: { - switch(kind) { - case 'mcq': - inputType = 'checkbox'; - break; - default: - inputType = 'radio'; - } - } - - - + import { createEventDispatcher } from 'svelte'; + import { QuestionProposal } from '../lib/questions'; export let edit = false; @@ -16,6 +18,8 @@ } } + const dispatch = createEventDispatcher(); + function addProposal() { const p = new QuestionProposal(); p.id_question = id_question; @@ -35,7 +39,7 @@ id={prefixid + 'p' + proposal.id} bind:group={valueCheck} value={proposal.id.toString()} - on:change={() => { value = valueCheck.join(',')}} + on:change={() => { value = valueCheck.join(','); dispatch("change"); }} > {:else} { dispatch("change"); }} > {/if} {#if edit} diff --git a/atsebayt/src/components/SurveyAdmin.svelte b/atsebayt/src/components/SurveyAdmin.svelte index dc28e7d..0ab89cc 100644 --- a/atsebayt/src/components/SurveyAdmin.svelte +++ b/atsebayt/src/components/SurveyAdmin.svelte @@ -2,6 +2,8 @@ import { createEventDispatcher } from 'svelte'; import { goto } from '$app/navigation'; + import { getQuestions } from '../lib/questions'; + const dispatch = createEventDispatcher(); export let survey = null; @@ -60,6 +62,23 @@
+
+
+ +
+
+ {#await getQuestions(survey.id) then questions} + + {/await} +
+
+
diff --git a/atsebayt/src/components/SurveyBadge.svelte b/atsebayt/src/components/SurveyBadge.svelte index 4b28e0d..2d87b5a 100644 --- a/atsebayt/src/components/SurveyBadge.svelte +++ b/atsebayt/src/components/SurveyBadge.svelte @@ -4,7 +4,8 @@ export { className as class }; -{#if survey.startAvailability() > Date.now()}Prévu> +{#if survey.direct}Direct +{:else if survey.startAvailability() > Date.now()}Prévu {:else if survey.endAvailability() > Date.now()}En cours {:else if !survey.corrected}Terminé {:else}Corrigé diff --git a/atsebayt/src/components/SurveyList.svelte b/atsebayt/src/components/SurveyList.svelte index 807f079..cd16519 100644 --- a/atsebayt/src/components/SurveyList.svelte +++ b/atsebayt/src/components/SurveyList.svelte @@ -6,6 +6,17 @@ import SurveyBadge from '../components/SurveyBadge.svelte'; import { getSurveys } from '../lib/surveys'; import { getScore } from '../lib/users'; + + let req_surveys = getSurveys(); + export let direct = null; + + req_surveys.then((surveys) => { + for (const survey of surveys) { + if (survey.direct) { + direct = survey; + } + } + }); @@ -18,7 +29,7 @@ {/if} - {#await getSurveys()} + {#await req_surveys} {/if} - goto($user.is_admin?`surveys/${survey.id}/responses`:`surveys/${survey.id}`)}> + goto(survey.direct?`surveys/${survey.id}/live`:$user.is_admin?`surveys/${survey.id}/responses`:`surveys/${survey.id}`)}> {#each surveys as survey, sid (survey.id)} - {#if survey.shown && (!$user || (!$user.was_admin || $user.promo == survey.promo) || $user.is_admin)} + {#if (survey.shown || survey.direct != null) && (!$user || (!$user.was_admin || $user.promo == survey.promo) || $user.is_admin)} {#if $user && $user.is_admin && (sid == 0 || surveys[sid-1].promo != survey.promo)} {/if} - goto(survey.direct?`surveys/${survey.id}/live`:$user.is_admin?`surveys/${survey.id}/responses`:`surveys/${survey.id}`)}> + goto(survey.direct != null ?`surveys/${survey.id}/live`:$user.is_admin?`surveys/${survey.id}/responses`:`surveys/${survey.id}`)}> @@ -213,7 +213,7 @@ disabled={question.id === current_question || !ws_up} on:click={() => { ws.send('{"action":"new_question", "question":' + question.id + '}')} } > - Lancer cette question + diff --git a/atsebayt/src/routes/surveys/[sid]/live.svelte b/atsebayt/src/routes/surveys/[sid]/live.svelte index 1aa027f..d408b48 100644 --- a/atsebayt/src/routes/surveys/[sid]/live.svelte +++ b/atsebayt/src/routes/surveys/[sid]/live.svelte @@ -25,6 +25,26 @@ let show_question = null; let value; + let req_question; + let nosend = false; + + function afterQUpdate(q) { + value = undefined; + if (q) { + q.getMyResponse().then((response) => { + if (response && response.value) + value = response.value; + }) + } + } + + $: { + if (show_question) { + req_question = getQuestion(show_question); + req_question.then(afterQUpdate); + } + } + function wsconnect() { const ws = new WebSocket((window.location.protocol == 'https'?'wss://':'ws://') + window.location.host + `/api/surveys/${sid}/ws`); @@ -59,7 +79,7 @@ wsconnect(); function sendValue() { - if (show_question && value) { + if (show_question && value && !nosend) { survey.submitAnswers([{"id_question": show_question, "value": value}], $user.id_user).then((response) => { console.log("Vos réponses ont bien étés sauvegardées."); }, (error) => { @@ -91,8 +111,11 @@ {#if show_question} - {#await getQuestion(show_question)} - Please wait + {#await req_question} +
+
+ Chargement d'une nouvelle question … +
{:then question} + {#if question.kind != 'mcq' && question.kind != 'ucq'} + + {/if} {/await} {:else if ws_up}

diff --git a/responses.go b/responses.go index 7fa8055..d76368e 100644 --- a/responses.go +++ b/responses.go @@ -64,6 +64,10 @@ func init() { func(s Survey, u *User, _ []byte) HTTPResponse { return formatApiResponse(s.GetMyResponses(u, s.Corrected)) }), loggedUser)) + router.GET("/api/questions/:qid/response", apiAuthHandler(questionAuthHandler( + func(q Question, u *User, _ []byte) HTTPResponse { + return formatApiResponse(q.GetMyResponse(u, false)) + }), loggedUser)) router.GET("/api/users/:uid/surveys/:sid/responses", apiAuthHandler(func(u *User, ps httprouter.Params, body []byte) HTTPResponse { return surveyAuthHandler(func(s Survey, u *User, _ []byte) HTTPResponse { return userHandler(func(u User, _ []byte) HTTPResponse { @@ -207,6 +211,15 @@ func (s *Survey) GetMyResponses(u *User, showScore bool) (responses []Response, } } +func (q *Question) GetMyResponse(u *User, showScore bool) (r Response, err error) { + err = DBQueryRow("SELECT R.id_response, R.id_question, R.id_user, R.answer, R.time_submit, R.score, R.score_explanation, R.id_corrector, R.time_scored FROM survey_responses R WHERE R.id_question=? AND R.id_user=? ORDER BY time_submit DESC LIMIT 1", q.Id, u.Id).Scan(&r.Id, &r.IdQuestion, &r.IdUser, &r.Answer, &r.TimeSubmit, &r.Score, &r.ScoreExplaination, &r.IdCorrector, &r.TimeScored) + if !showScore { + r.Score = nil + r.ScoreExplaination = nil + } + return +} + func (q *Question) GetResponses() (responses []Response, err error) { if rows, errr := DBQuery("SELECT id_response, id_question, S.id_user, answer, S.time_submit, score, score_explanation, id_corrector, time_scored FROM (SELECT id_user, MAX(time_submit) AS time_submit FROM survey_responses WHERE id_question=? GROUP BY id_user) R INNER JOIN survey_responses S ON S.id_user = R.id_user AND S.time_submit = R.time_submit AND S.id_question=?", q.Id, q.Id); errr != nil { return nil, errr From 894358df20ad083d9830d5940d9d642a60794089 Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Tue, 1 Mar 2022 13:16:20 +0100 Subject: [PATCH 15/16] Add toaster --- atsebayt/src/components/SurveyAdmin.svelte | 13 ++++-- .../src/components/SurveyQuestions.svelte | 11 ++++- atsebayt/src/components/Toaster.svelte | 18 ++++++++ atsebayt/src/routes/__layout.svelte | 4 ++ atsebayt/src/routes/surveys/[sid]/live.svelte | 5 ++- atsebayt/src/stores/toasts.js | 41 +++++++++++++++++++ 6 files changed, 86 insertions(+), 6 deletions(-) create mode 100644 atsebayt/src/components/Toaster.svelte create mode 100644 atsebayt/src/stores/toasts.js diff --git a/atsebayt/src/components/SurveyAdmin.svelte b/atsebayt/src/components/SurveyAdmin.svelte index 0ab89cc..c0148e5 100644 --- a/atsebayt/src/components/SurveyAdmin.svelte +++ b/atsebayt/src/components/SurveyAdmin.svelte @@ -3,6 +3,7 @@ import { goto } from '$app/navigation'; import { getQuestions } from '../lib/questions'; + import { ToastsStore } from '../stores/toasts'; const dispatch = createEventDispatcher(); export let survey = null; @@ -11,7 +12,9 @@ survey.save().then((response) => { dispatch('saved'); }, (error) => { - console.log(error) + ToastsStore.addErrorToast({ + msg: error.errmsg, + }); }) } @@ -19,7 +22,9 @@ survey.delete().then((response) => { goto(`surveys`); }, (error) => { - console.log(error) + ToastsStore.addErrorToast({ + msg: error.errmsg, + }); }) } @@ -27,7 +32,9 @@ survey.duplicate().then((response) => { goto(`surveys/${response.id}`); }).catch((error) => { - console.log(error) + ToastsStore.addErrorToast({ + msg: error.errmsg, + }); }) } diff --git a/atsebayt/src/components/SurveyQuestions.svelte b/atsebayt/src/components/SurveyQuestions.svelte index a906418..1051e60 100644 --- a/atsebayt/src/components/SurveyQuestions.svelte +++ b/atsebayt/src/components/SurveyQuestions.svelte @@ -1,5 +1,6 @@ + +
+ {#each $ToastsStore.toasts as toast} + + {/each} +
diff --git a/atsebayt/src/routes/__layout.svelte b/atsebayt/src/routes/__layout.svelte index db852dc..df45380 100644 --- a/atsebayt/src/routes/__layout.svelte +++ b/atsebayt/src/routes/__layout.svelte @@ -42,6 +42,8 @@

@@ -36,7 +47,7 @@
{survey.title} diff --git a/atsebayt/src/lib/surveys.js b/atsebayt/src/lib/surveys.js index 1fe9040..9a9fc62 100644 --- a/atsebayt/src/lib/surveys.js +++ b/atsebayt/src/lib/surveys.js @@ -8,12 +8,13 @@ class Survey { } } - update({ id, title, promo, group, shown, corrected, start_availability, end_availability }) { + update({ id, title, promo, group, shown, direct, corrected, start_availability, end_availability }) { this.id = id; this.title = title; this.promo = promo; this.group = group; this.shown = shown; + this.direct = direct; this.corrected = corrected; if (this.start_availability != start_availability) { this.start_availability = start_availability; diff --git a/atsebayt/src/routes/index.svelte b/atsebayt/src/routes/index.svelte index e1e991d..270af16 100644 --- a/atsebayt/src/routes/index.svelte +++ b/atsebayt/src/routes/index.svelte @@ -2,6 +2,8 @@ import { user } from '../stores/user'; import SurveyList from '../components/SurveyList.svelte'; import ValidateSubmissions from '../components/ValidateSubmissions.svelte'; + + let direct = null;
@@ -20,6 +22,12 @@
{/if} + {#if direct} + + {/if} +

Tu as fait les rendus suivants :

@@ -38,6 +46,6 @@ Vous devez vous identifier pour accéder au contenu.

{/if} - +
diff --git a/atsebayt/src/routes/surveys/[sid]/admin.svelte b/atsebayt/src/routes/surveys/[sid]/admin.svelte new file mode 100644 index 0000000..3abb015 --- /dev/null +++ b/atsebayt/src/routes/surveys/[sid]/admin.svelte @@ -0,0 +1,370 @@ + + + + +{#await surveyP then survey} + {#if $user && $user.is_admin} + {#if survey.direct !== null} + + + {/if} + + {/if} +
+

+ < + {survey.title} + + Administration + +

+ {#if survey.direct !== null} +
+ {#if ws_up}Connecté{:else}Déconnecté{/if} +
+ {:else} + + {/if} +
+ + {#if survey.direct === null} + + {:else} + {#await req_questions} +
+
+ Chargement des questions … +
+ {:then questions} +
+ + + + + + + + + + {#each questions as question (question.id)} + + + + + + {/each} + +
+ Question + + + Réponses + + Actions + +
+ {#if responses[question.id]} + + {question.title} + + {:else} + {question.title} + {/if} + + {#if responses[question.id]} + {Object.keys(responses[question.id]).length} + {:else} + 0 + {/if} + {#if wsstats}/ {wsstats.nb_clients}{/if} + + +
+
+ {/await} + {/if} + +
+ +

+ Réponses +

+ {#if Object.keys(responses).length} + {#each Object.keys(responses) as q, qid (qid)} + {#await req_questions then questions} + {#each questions as question} + {#if question.id == q} +

+ {question.title} +

+ {#if question.kind == 'ucq'} + {#await question.getProposals()} +
+
+ Chargement des propositions … +
+ {:then proposals} +
+ + + {#each proposals as proposal (proposal.id)} + + + + + + {/each} + +
+ {proposal.label} + + {responsesbyid[q].filter((e) => e == proposal.id.toString()).length}/{responsesbyid[q].length} + + {Math.trunc(responsesbyid[q].filter((e) => e == proposal.id.toString()).length / responsesbyid[q].length * 1000)/10} % +
+
+ {/await} + {:else if question.kind == 'mcq'} + {#await question.getProposals()} +
+
+ Chargement des propositions … +
+ {:then proposals} +
+ + + {#each proposals as proposal (proposal.id)} + + + + + + {/each} + +
+ {proposal.label} + + {responsesbyid[q].filter((e) => e.indexOf(proposal.id.toString()) >= 0).length}/{responsesbyid[q].length} + + {Math.trunc(responsesbyid[q].filter((e) => e.indexOf(proposal.id.toString()) >= 0).length / responsesbyid[q].length * 1000)/10} % +
+
+ {/await} + {:else} +
+ +
+ {/if} + {/if} + {/each} + {/await} + {/each} + {/if} + +
+ + +

+ Connectés + {#if wsstats} + {wsstats.nb_clients} utilisateurs + {/if} +

+ {#if wsstats} +
+ {#each wsstats.users as login, lid (lid)} +
+
+ {login} + +
+
+ {/each} +
+ {/if} + +{/await} diff --git a/atsebayt/src/routes/surveys/[sid]/index.svelte b/atsebayt/src/routes/surveys/[sid]/index.svelte index 02899f4..8df6da5 100644 --- a/atsebayt/src/routes/surveys/[sid]/index.svelte +++ b/atsebayt/src/routes/surveys/[sid]/index.svelte @@ -11,6 +11,8 @@ {#await surveyP then survey} {#if $user && $user.is_admin} - - + + + {#if survey.direct} + + {/if} {/if}

diff --git a/atsebayt/src/routes/surveys/[sid]/live.svelte b/atsebayt/src/routes/surveys/[sid]/live.svelte new file mode 100644 index 0000000..1aa027f --- /dev/null +++ b/atsebayt/src/routes/surveys/[sid]/live.svelte @@ -0,0 +1,118 @@ + + + + +{#await surveyP then survey} + {#if $user && $user.is_admin} + + + {/if} +
+

+ < + {survey.title} +

+
+ {#if ws_up}Connecté{:else}Déconnecté{/if} +
+
+ + + {#if show_question} + {#await getQuestion(show_question)} + Please wait + {:then question} + + + + {/await} + {:else if ws_up} +

+ Pas de question actuellement +

+ {:else} +

+ La session est terminée. On se retrouve une prochaine fois… +

+ {/if} + +{/await} diff --git a/db.go b/db.go index 98883bb..b5fc410 100644 --- a/db.go +++ b/db.go @@ -83,6 +83,7 @@ CREATE TABLE IF NOT EXISTS surveys( title VARCHAR(255), promo MEDIUMINT NOT NULL, shown BOOLEAN NOT NULL DEFAULT FALSE, + direct INTEGER DEFAULT NULL, corrected BOOLEAN NOT NULL DEFAULT FALSE, start_availability TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, end_availability TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP diff --git a/direct.go b/direct.go new file mode 100644 index 0000000..2e45835 --- /dev/null +++ b/direct.go @@ -0,0 +1,358 @@ +package main + +import ( + "context" + "log" + "net/http" + "strconv" + "sync" + "time" + + "github.com/julienschmidt/httprouter" + "nhooyr.io/websocket" + "nhooyr.io/websocket/wsjson" +) + +var ( + WSClients = map[int64][]WSClient{} + WSClientsMutex = sync.RWMutex{} + WSAdmin = []WSClient{} + WSAdminMutex = sync.RWMutex{} +) + +func init() { + router.GET("/api/surveys/:sid/ws", rawAuthHandler(SurveyWS, loggedUser)) + router.GET("/api/surveys/:sid/ws-admin", rawAuthHandler(SurveyWSAdmin, adminRestricted)) + + router.GET("/api/surveys/:sid/ws/stats", apiHandler(surveyHandler(func(s Survey, body []byte) HTTPResponse { + return APIResponse{ + WSSurveyStats(s.Id), + } + }), adminRestricted)) +} + +func WSSurveyStats(sid int64) map[string]interface{} { + var users []string + var nb int + + WSClientsMutex.RLock() + defer WSClientsMutex.RUnlock() + if w, ok := WSClients[sid]; ok { + nb = len(w) + for _, ws := range w { + users = append(users, ws.u.Login) + } + } + + return map[string]interface{}{ + "nb_clients": nb, + "users": users, + } +} + +type WSClient struct { + ws *websocket.Conn + c chan WSMessage + u *User + sid int64 +} + +func SurveyWS_run(ws *websocket.Conn, c chan WSMessage, sid int64, u *User) { + for { + msg, ok := <-c + if !ok { + break + } + + ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) + defer cancel() + + err := wsjson.Write(ctx, ws, msg) + if err != nil { + log.Println("Error on WebSocket:", err) + ws.Close(websocket.StatusInternalError, "error on write") + break + } + } + ws.Close(websocket.StatusNormalClosure, "end") + + WSClientsMutex.Lock() + defer WSClientsMutex.Unlock() + + for i, clt := range WSClients[sid] { + if clt.ws == ws { + WSClients[sid][i] = WSClients[sid][len(WSClients[sid])-1] + WSClients[sid] = WSClients[sid][:len(WSClients[sid])-1] + break + } + } + log.Println(u.Login, "disconnected") +} + +func msgCurrentState(survey *Survey) (msg WSMessage) { + if *survey.Direct == 0 { + msg = WSMessage{ + Action: "pause", + } + } else { + msg = WSMessage{ + Action: "new_question", + QuestionId: survey.Direct, + } + } + return +} + +func SurveyWS(w http.ResponseWriter, r *http.Request, ps httprouter.Params, u *User, body []byte) { + if sid, err := strconv.Atoi(string(ps.ByName("sid"))); err != nil { + http.Error(w, "{\"errmsg\": \"Invalid survey identifier\"}", http.StatusBadRequest) + return + } else if survey, err := getSurvey(sid); err != nil { + http.Error(w, "{\"errmsg\": \"Survey not found\"}", http.StatusNotFound) + return + } else if survey.Direct == nil { + http.Error(w, "{\"errmsg\": \"Not a direct survey\"}", http.StatusBadRequest) + return + } else { + ws, err := websocket.Accept(w, r, nil) + if err != nil { + log.Fatal("error get connection", err) + } + + log.Println(u.Login, "is now connected to WS", sid) + + c := make(chan WSMessage, 1) + + WSClientsMutex.Lock() + defer WSClientsMutex.Unlock() + WSClients[survey.Id] = append(WSClients[survey.Id], WSClient{ws, c, u, survey.Id}) + + // Send current state + c <- msgCurrentState(&survey) + + go SurveyWS_run(ws, c, survey.Id, u) + } +} + +func WSWriteAll(message WSMessage) { + WSClientsMutex.RLock() + defer WSClientsMutex.RUnlock() + + for _, wss := range WSClients { + for _, ws := range wss { + ws.c <- message + } + } +} + +type WSMessage struct { + Action string `json:"action"` + SurveyId *int64 `json:"survey,omitempty"` + QuestionId *int64 `json:"question,omitempty"` + Stats map[string]interface{} `json:"stats,omitempty"` + UserId *int64 `json:"user,omitempty"` + Response string `json:"value,omitempty"` +} + +func (s *Survey) WSWriteAll(message WSMessage) { + WSClientsMutex.RLock() + defer WSClientsMutex.RUnlock() + + if wss, ok := WSClients[s.Id]; ok { + for _, ws := range wss { + ws.c <- message + } + } +} + +func (s *Survey) WSCloseAll(message string) { + WSClientsMutex.RLock() + defer WSClientsMutex.RUnlock() + + if wss, ok := WSClients[s.Id]; ok { + for _, ws := range wss { + close(ws.c) + } + } +} + +// Admin ////////////////////////////////////////////////////////////// + +func SurveyWSAdmin_run(ctx context.Context, ws *websocket.Conn, c chan WSMessage, sid int64, u *User) { + ct := time.Tick(25000 * time.Millisecond) +loopadmin: + for { + select { + case <-ct: + ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) + defer cancel() + + err := wsjson.Write(ctx, ws, WSMessage{ + Action: "stats", + Stats: WSSurveyStats(sid), + }) + if err != nil { + log.Println("Error on WebSocket:", err) + ws.Close(websocket.StatusInternalError, "error on write") + break + } + + case msg, ok := <-c: + if !ok { + break loopadmin + } + + ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) + defer cancel() + + err := wsjson.Write(ctx, ws, msg) + if err != nil { + log.Println("Error on WebSocket:", err) + ws.Close(websocket.StatusInternalError, "error on write") + break + } + } + } + ws.Close(websocket.StatusNormalClosure, "end") + + WSAdminMutex.Lock() + defer WSAdminMutex.Unlock() + + for i, clt := range WSAdmin { + if clt.ws == ws { + WSAdmin[i] = WSAdmin[len(WSAdmin)-1] + WSAdmin = WSAdmin[:len(WSAdmin)-1] + break + } + } + log.Println(u.Login, "admin disconnected") +} + +func SurveyWSAdmin(w http.ResponseWriter, r *http.Request, ps httprouter.Params, u *User, body []byte) { + if sid, err := strconv.Atoi(string(ps.ByName("sid"))); err != nil { + http.Error(w, "{\"errmsg\": \"Invalid survey identifier\"}", http.StatusBadRequest) + return + } else if survey, err := getSurvey(sid); err != nil { + http.Error(w, "{\"errmsg\": \"Survey not found\"}", http.StatusNotFound) + return + } else if survey.Direct == nil { + http.Error(w, "{\"errmsg\": \"Not a direct survey\"}", http.StatusBadRequest) + return + } else { + ws, err := websocket.Accept(w, r, nil) + if err != nil { + log.Fatal("error get connection", err) + } + + log.Println(u.Login, "is now connected to WS-admin", sid) + + c := make(chan WSMessage, 2) + + WSAdminMutex.Lock() + defer WSAdminMutex.Unlock() + WSAdmin = append(WSAdmin, WSClient{ws, c, u, survey.Id}) + + // Send current state + c <- msgCurrentState(&survey) + + go SurveyWSAdmin_run(r.Context(), ws, c, survey.Id, u) + go func(c chan WSMessage, sid int) { + var v WSMessage + var err error + for { + err = wsjson.Read(context.Background(), ws, &v) + if err != nil { + log.Println("Error when receiving message:", err) + close(c) + break + } + + if v.Action == "new_question" && v.QuestionId != nil { + if survey, err := getSurvey(sid); err != nil { + log.Println("Unable to retrieve survey:", err) + } else { + survey.Direct = v.QuestionId + _, err = survey.Update() + if err != nil { + log.Println("Unable to update survey:", err) + } + + survey.WSWriteAll(v) + v.SurveyId = &survey.Id + WSAdminWriteAll(v) + } + } else if v.Action == "pause" { + if survey, err := getSurvey(sid); err != nil { + log.Println("Unable to retrieve survey:", err) + } else { + var u int64 = 0 + survey.Direct = &u + _, err = survey.Update() + if err != nil { + log.Println("Unable to update survey:", err) + } + + survey.WSWriteAll(v) + v.SurveyId = &survey.Id + WSAdminWriteAll(v) + } + } else if v.Action == "end" { + if survey, err := getSurvey(sid); err != nil { + log.Println("Unable to retrieve survey:", err) + } else { + survey.Direct = nil + _, err = survey.Update() + if err != nil { + log.Println("Unable to update survey:", err) + } + + survey.WSCloseAll("Fin du live") + v.SurveyId = &survey.Id + WSAdminWriteAll(v) + } + } else if v.Action == "get_stats" { + err = wsjson.Write(context.Background(), ws, WSMessage{Action: "stats", Stats: WSSurveyStats(int64(sid))}) + } else if v.Action == "get_responses" { + if survey, err := getSurvey(sid); err != nil { + log.Println("Unable to retrieve survey:", err) + } else if questions, err := survey.GetQuestions(); err != nil { + log.Println("Unable to retrieve questions:", err) + } else { + for _, q := range questions { + if responses, err := q.GetResponses(); err != nil { + log.Println("Unable to retrieve questions:", err) + } else { + for _, r := range responses { + wsjson.Write(context.Background(), ws, WSMessage{Action: "new_response", UserId: &r.IdUser, QuestionId: &q.Id, Response: r.Answer}) + } + } + } + } + } else { + log.Println("Unknown admin action:", v.Action) + } + } + }(c, sid) + } +} + +func WSAdminWriteAll(message WSMessage) { + WSAdminMutex.RLock() + defer WSAdminMutex.RUnlock() + + for _, ws := range WSAdmin { + ws.c <- message + } +} + +func (s *Survey) WSAdminWriteAll(message WSMessage) { + WSAdminMutex.RLock() + defer WSAdminMutex.RUnlock() + + for _, ws := range WSAdmin { + log.Println("snd", message, ws.sid, s.Id) + if ws.sid == s.Id { + ws.c <- message + } + } +} diff --git a/go.mod b/go.mod index 530f842..0723079 100644 --- a/go.mod +++ b/go.mod @@ -15,4 +15,5 @@ require ( golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 google.golang.org/appengine v1.6.7 // indirect gopkg.in/square/go-jose.v2 v2.6.0 // indirect + nhooyr.io/websocket v1.8.7 ) diff --git a/go.sum b/go.sum index 03ec421..98853f3 100644 --- a/go.sum +++ b/go.sum @@ -43,15 +43,25 @@ github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjs github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= +github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -90,6 +100,7 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -104,6 +115,7 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -120,16 +132,23 @@ github.com/jcmturner/gokrb5/v8 v8.4.2 h1:6ZIM6b/JJN0X8UM43ZOM6Z4SJzla+a/u7scXFJz github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aWNYyvBVK62bc= github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.10.3 h1:OP96hzwJVBIHYU52pVTI6CczrxPvrGfgqF9N5eTO0Q8= +github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pquerna/cachecontrol v0.1.0 h1:yJMy84ti9h/+OEWa752kBTKv4XC30OtVVHYv/8cTqKc= @@ -139,9 +158,12 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -257,6 +279,7 @@ golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -411,6 +434,7 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -420,6 +444,8 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g= +nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/handler.go b/handler.go index d85cc61..c146cf2 100644 --- a/handler.go +++ b/handler.go @@ -76,6 +76,12 @@ func eraseCookie(w http.ResponseWriter) { } func rawHandler(f func(http.ResponseWriter, *http.Request, httprouter.Params, []byte), access ...func(*User, *http.Request) *APIErrorResponse) func(http.ResponseWriter, *http.Request, httprouter.Params) { + return rawAuthHandler(func(w http.ResponseWriter, r *http.Request, ps httprouter.Params, _ *User, body []byte) { + f(w, r, ps, body) + }, access...) +} + +func rawAuthHandler(f func(http.ResponseWriter, *http.Request, httprouter.Params, *User, []byte), access ...func(*User, *http.Request) *APIErrorResponse) func(http.ResponseWriter, *http.Request, httprouter.Params) { return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { if addr := r.Header.Get("X-Forwarded-For"); addr != "" { r.RemoteAddr = addr @@ -136,7 +142,7 @@ func rawHandler(f func(http.ResponseWriter, *http.Request, httprouter.Params, [] } } - f(w, r, ps, body) + f(w, r, ps, user, body) } } diff --git a/questions.go b/questions.go index eca2959..62181e9 100644 --- a/questions.go +++ b/questions.go @@ -3,6 +3,7 @@ package main import ( "encoding/json" "errors" + "fmt" "net/http" "strconv" "time" @@ -38,10 +39,18 @@ func init() { return formatApiResponse(s.NewQuestion(new.Title, new.DescriptionRaw, new.Placeholder, new.Kind)) }), adminRestricted)) - router.GET("/api/questions/:qid", apiHandler(questionHandler( - func(s Question, _ []byte) HTTPResponse { - return APIResponse{s} - }), adminRestricted)) + router.GET("/api/questions/:qid", apiAuthHandler(questionAuthHandler( + func(q Question, u *User, _ []byte) HTTPResponse { + if u.IsAdmin { + return APIResponse{q} + } else if s, err := getSurvey(int(q.IdSurvey)); err != nil { + return APIErrorResponse{err: err} + } else if s.Shown || (s.Direct != nil && *s.Direct == q.Id) { + return APIResponse{q} + } else { + return APIErrorResponse{err: fmt.Errorf("Not authorized"), status: http.StatusForbidden} + } + }), loggedUser)) router.GET("/api/surveys/:sid/questions/:qid", apiHandler(questionHandler( func(s Question, _ []byte) HTTPResponse { return APIResponse{s} diff --git a/responses.go b/responses.go index d89f13a..7fa8055 100644 --- a/responses.go +++ b/responses.go @@ -25,10 +25,16 @@ func init() { } for _, response := range responses { - if len(response.Answer) > 0 { + if !s.Shown && (s.Direct == nil || *s.Direct != response.IdQuestion) { + return APIErrorResponse{err: fmt.Errorf("Cette question n'est pas disponible")} + } else if len(response.Answer) > 0 { if _, err := s.NewResponse(response.IdQuestion, u.Id, response.Answer); err != nil { return APIErrorResponse{err: err} } + + if s.Direct != nil { + s.WSAdminWriteAll(WSMessage{Action: "new_response", UserId: &u.Id, QuestionId: &response.IdQuestion, Response: response.Answer}) + } } } diff --git a/surveys.go b/surveys.go index 872d971..e880652 100644 --- a/surveys.go +++ b/surveys.go @@ -20,11 +20,11 @@ func init() { router.GET("/api/surveys", apiAuthHandler( func(u *User, _ httprouter.Params, _ []byte) HTTPResponse { if u == nil { - return formatApiResponse(getSurveys(fmt.Sprintf("WHERE shown = TRUE AND NOW() > start_availability AND promo = %d ORDER BY start_availability ASC", currentPromo))) + return formatApiResponse(getSurveys(fmt.Sprintf("WHERE (shown = TRUE OR direct IS NOT NULL) AND NOW() > start_availability AND promo = %d ORDER BY start_availability ASC", currentPromo))) } else if u.IsAdmin { return formatApiResponse(getSurveys("ORDER BY promo DESC, start_availability ASC")) } else { - surveys, err := getSurveys(fmt.Sprintf("WHERE shown = TRUE AND promo = %d ORDER BY start_availability ASC", u.Promo)) + surveys, err := getSurveys(fmt.Sprintf("WHERE (shown = TRUE OR direct IS NOT NULL) AND promo = %d ORDER BY start_availability ASC", u.Promo)) if err != nil { return APIErrorResponse{err: err} } @@ -49,7 +49,7 @@ func init() { new.Promo = currentPromo } - return formatApiResponse(NewSurvey(new.Title, new.Promo, new.Group, new.Shown, new.StartAvailability, new.EndAvailability)) + return formatApiResponse(NewSurvey(new.Title, new.Promo, new.Group, new.Shown, new.Direct, new.StartAvailability, new.EndAvailability)) }, adminRestricted)) router.GET("/api/surveys/:sid", apiAuthHandler(surveyAuthHandler( func(s Survey, u *User, _ []byte) HTTPResponse { @@ -69,6 +69,22 @@ func init() { } new.Id = current.Id + + if new.Direct != current.Direct { + if new.Direct == nil { + current.WSCloseAll("") + } else if *new.Direct == 0 { + current.WSWriteAll(WSMessage{ + Action: "pause", + }) + } else { + current.WSWriteAll(WSMessage{ + Action: "new_question", + QuestionId: new.Direct, + }) + } + } + return formatApiResponse(new.Update()) }), adminRestricted)) router.DELETE("/api/surveys/:sid", apiHandler(surveyHandler( @@ -144,20 +160,21 @@ type Survey struct { Promo uint `json:"promo"` Group string `json:"group"` Shown bool `json:"shown"` + Direct *int64 `json:"direct"` Corrected bool `json:"corrected"` StartAvailability time.Time `json:"start_availability"` EndAvailability time.Time `json:"end_availability"` } func getSurveys(cnd string, param ...interface{}) (surveys []Survey, err error) { - if rows, errr := DBQuery("SELECT id_survey, title, promo, grp, shown, corrected, start_availability, end_availability FROM surveys "+cnd, param...); errr != nil { + if rows, errr := DBQuery("SELECT id_survey, title, promo, grp, shown, direct, corrected, start_availability, end_availability FROM surveys "+cnd, param...); errr != nil { return nil, errr } else { defer rows.Close() for rows.Next() { var s Survey - if err = rows.Scan(&s.Id, &s.Title, &s.Promo, &s.Group, &s.Shown, &s.Corrected, &s.StartAvailability, &s.EndAvailability); err != nil { + if err = rows.Scan(&s.Id, &s.Title, &s.Promo, &s.Group, &s.Shown, &s.Direct, &s.Corrected, &s.StartAvailability, &s.EndAvailability); err != nil { return } surveys = append(surveys, s) @@ -171,17 +188,17 @@ func getSurveys(cnd string, param ...interface{}) (surveys []Survey, err error) } func getSurvey(id int) (s Survey, err error) { - err = DBQueryRow("SELECT id_survey, title, promo, grp, shown, corrected, start_availability, end_availability FROM surveys WHERE id_survey=?", id).Scan(&s.Id, &s.Title, &s.Promo, &s.Group, &s.Shown, &s.Corrected, &s.StartAvailability, &s.EndAvailability) + err = DBQueryRow("SELECT id_survey, title, promo, grp, shown, direct, corrected, start_availability, end_availability FROM surveys WHERE id_survey=?", id).Scan(&s.Id, &s.Title, &s.Promo, &s.Group, &s.Shown, &s.Direct, &s.Corrected, &s.StartAvailability, &s.EndAvailability) return } -func NewSurvey(title string, promo uint, group string, shown bool, startAvailability time.Time, endAvailability time.Time) (*Survey, error) { - if res, err := DBExec("INSERT INTO surveys (title, promo, grp, shown, start_availability, end_availability) VALUES (?, ?, ?, ?, ?, ?)", title, promo, group, shown, startAvailability, endAvailability); err != nil { +func NewSurvey(title string, promo uint, group string, shown bool, direct *int64, startAvailability time.Time, endAvailability time.Time) (*Survey, error) { + if res, err := DBExec("INSERT INTO surveys (title, promo, grp, shown, direct, start_availability, end_availability) VALUES (?, ?, ?, ?, ?, ?, ?)", title, promo, group, shown, direct, startAvailability, endAvailability); err != nil { return nil, err } else if sid, err := res.LastInsertId(); err != nil { return nil, err } else { - return &Survey{sid, title, promo, group, shown, false, startAvailability, endAvailability}, nil + return &Survey{sid, title, promo, group, shown, direct, false, startAvailability, endAvailability}, nil } } @@ -227,7 +244,7 @@ func (s Survey) GetScores() (scores map[int64]*float64, err error) { } func (s *Survey) Update() (*Survey, error) { - if _, err := DBExec("UPDATE surveys SET title = ?, promo = ?, grp = ?, shown = ?, corrected = ?, start_availability = ?, end_availability = ? WHERE id_survey = ?", s.Title, s.Promo, s.Group, s.Shown, s.Corrected, s.StartAvailability, s.EndAvailability, s.Id); err != nil { + if _, err := DBExec("UPDATE surveys SET title = ?, promo = ?, grp = ?, shown = ?, direct = ?, corrected = ?, start_availability = ?, end_availability = ? WHERE id_survey = ?", s.Title, s.Promo, s.Group, s.Shown, s.Direct, s.Corrected, s.StartAvailability, s.EndAvailability, s.Id); err != nil { return nil, err } else { return s, err From 3ae644fe13b882e9ab992125415563616f455dce Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Tue, 1 Mar 2022 13:03:16 +0100 Subject: [PATCH 14/16] Live: Retrieve previous submission --- atsebayt/src/components/SurveyBadge.svelte | 2 +- atsebayt/src/components/SurveyList.svelte | 6 ++-- atsebayt/src/lib/questions.js | 12 +++++++ .../src/routes/surveys/[sid]/admin.svelte | 4 +-- atsebayt/src/routes/surveys/[sid]/live.svelte | 36 +++++++++++++++++-- responses.go | 13 +++++++ 6 files changed, 64 insertions(+), 9 deletions(-) diff --git a/atsebayt/src/components/SurveyBadge.svelte b/atsebayt/src/components/SurveyBadge.svelte index 2d87b5a..b224a82 100644 --- a/atsebayt/src/components/SurveyBadge.svelte +++ b/atsebayt/src/components/SurveyBadge.svelte @@ -4,7 +4,7 @@ export { className as class }; -{#if survey.direct}Direct +{#if survey.direct != null}Direct {:else if survey.startAvailability() > Date.now()}Prévu {:else if survey.endAvailability() > Date.now()}En cours {:else if !survey.corrected}Terminé diff --git a/atsebayt/src/components/SurveyList.svelte b/atsebayt/src/components/SurveyList.svelte index cd16519..0d8c415 100644 --- a/atsebayt/src/components/SurveyList.svelte +++ b/atsebayt/src/components/SurveyList.svelte @@ -12,7 +12,7 @@ req_surveys.then((surveys) => { for (const survey of surveys) { - if (survey.direct) { + if (survey.direct != null) { direct = survey; } } @@ -39,7 +39,7 @@ {:then surveys}
@@ -47,7 +47,7 @@
{survey.title} diff --git a/atsebayt/src/lib/questions.js b/atsebayt/src/lib/questions.js index 81ba6b5..f6903d4 100644 --- a/atsebayt/src/lib/questions.js +++ b/atsebayt/src/lib/questions.js @@ -74,6 +74,18 @@ export class Question { } } + async getMyResponse() { + const res = await fetch(`api/questions/${this.id}/response`, { + method: 'GET', + headers: {'Accept': 'application/json'}, + }); + if (res.status == 200) { + return new Response(await res.json()); + } else { + throw new Error((await res.json()).errmsg); + } + } + async getResponses() { const res = await fetch(`api/surveys/${this.id_survey}/questions/${this.id}/responses`, { method: 'GET', diff --git a/atsebayt/src/routes/surveys/[sid]/admin.svelte b/atsebayt/src/routes/surveys/[sid]/admin.svelte index 3abb015..8978448 100644 --- a/atsebayt/src/routes/surveys/[sid]/admin.svelte +++ b/atsebayt/src/routes/surveys/[sid]/admin.svelte @@ -181,7 +181,7 @@ disabled={!current_question || !ws_up} on:click={() => { ws.send('{"action":"pause"}')} } > - Pause +