1 Commits
main ... v0.3.3

Author SHA1 Message Date
Zoe
7620577fa0 V0.3.3: Even more optimization
Some checks failed
Build and Push Docker Image to GHCR / build-and-push (push) Has been cancelled
In this realease, I have further optimized Passport. The css that
Passport now uses is entirely handrolled and build via postcss (sadly).
Several bugs have also been fixed in this release, as well as a few
performance improvements relating to the admin UI.
2025-10-04 22:43:21 -05:00
27 changed files with 1771 additions and 641 deletions

View File

@@ -10,6 +10,8 @@ on:
jobs: jobs:
build-and-push: build-and-push:
env:
RUNNER_TOOL_CACHE: /toolcache
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:

3
.gitignore vendored
View File

@@ -5,4 +5,7 @@ public
zqdgr zqdgr
# compiled via go prepare # compiled via go prepare
src/assets/styles/*
src/assets/tailwind.css src/assets/tailwind.css
node_modules

View File

@@ -1,7 +1,9 @@
FROM golang:1.25 AS builder FROM golang:1.25 AS builder
# build dependencies # build dependencies
RUN apt update && apt install -y upx RUN apt update && apt install -y upx unzip
RUN curl -fsSL https://bun.com/install | BUN_INSTALL=/usr bash
ARG TARGETARCH ARG TARGETARCH
RUN set -eux; \ RUN set -eux; \
@@ -30,6 +32,8 @@ COPY go.mod go.sum ./
RUN go mod download RUN go mod download
COPY . . COPY . .
RUN bun install
RUN zqdgr build RUN zqdgr build
RUN upx passport RUN upx passport

View File

@@ -10,16 +10,16 @@ Passport is a simple, fast, and lightweight web dashboard/new tab replacement.
### Prerequisites ### Prerequisites
- [ZQDGR](https://github.com/juls0730/zqdgr) - [ZQDGR](https://github.com/juls0730/zqdgr)
- [Go](https://go.dev/doc/install) - [Go](https://go.dev/doc/install)
- [sqlite3](https://www.sqlite.org/download.html) - [sqlite3](https://www.sqlite.org/download.html)
- [TailwdinCSS CLI](https://github.com/tailwindlabs/tailwindcss/releases/latest) - [TailwdinCSS CLI](https://github.com/tailwindlabs/tailwindcss/releases/latest)
## Usage ## Usage
### Docker ### Docker
Passport is available as a Docker image via this repository. This is the recommended way to run Passport. Passport is available as a Docker image via this repository for both amd64 and arm64. This is the recommended way to run Passport.
```bash ```bash
docker run -d --name passport -p 3000:3000 -v passport_data:/data -e PASSPORT_ADMIN_USERNAME=admin -e PASSPORT_ADMIN_PASSWORD=password ghcr.io/juls0730/passport:latest docker run -d --name passport -p 3000:3000 -v passport_data:/data -e PASSPORT_ADMIN_USERNAME=admin -e PASSPORT_ADMIN_PASSWORD=password ghcr.io/juls0730/passport:latest
@@ -35,9 +35,11 @@ If you want to build from source, you will need to install the dependencies firs
# note entirely necessary, but strongly recommended # note entirely necessary, but strongly recommended
go install github.com/juls0730/zqdgr@latest go install github.com/juls0730/zqdgr@latest
curl -sLO https://github.com/tailwindlabs/tailwindcss/releases/download/v4.1.13/tailwindcss-linux-x64 # install bun
chmod +x tailwindcss-linux-x64 curl -fsSL https://bun.sh/install | bash
mv tailwindcss-linux-x64 /usr/local/bin/tailwindcss
# install css build deps
bun install
# you may also have to install sqlite3... # you may also have to install sqlite3...
``` ```

527
bun.lock Normal file
View File

@@ -0,0 +1,527 @@
{
"lockfileVersion": 1,
"workspaces": {
"": {
"name": "passport-css-compiler",
"devDependencies": {
"@fullhuman/postcss-purgecss": "^7.0.2",
"cssnano": "^7.1.1",
"postcss": "^8.4.35",
"postcss-cli": "^11.0.0",
"postcss-import": "^16.1.1",
"postcss-preset-env": "^10.4.0",
},
},
},
"packages": {
"@csstools/cascade-layer-name-parser": ["@csstools/cascade-layer-name-parser@2.0.5", "", { "peerDependencies": { "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4" } }, "sha512-p1ko5eHgV+MgXFVa4STPKpvPxr6ReS8oS2jzTukjR74i5zJNyWO1ZM1m8YKBXnzDKWfBN1ztLYlHxbVemDD88A=="],
"@csstools/color-helpers": ["@csstools/color-helpers@5.1.0", "", {}, "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA=="],
"@csstools/css-calc": ["@csstools/css-calc@2.1.4", "", { "peerDependencies": { "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4" } }, "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ=="],
"@csstools/css-color-parser": ["@csstools/css-color-parser@3.1.0", "", { "dependencies": { "@csstools/color-helpers": "^5.1.0", "@csstools/css-calc": "^2.1.4" }, "peerDependencies": { "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4" } }, "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA=="],
"@csstools/css-parser-algorithms": ["@csstools/css-parser-algorithms@3.0.5", "", { "peerDependencies": { "@csstools/css-tokenizer": "^3.0.4" } }, "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ=="],
"@csstools/css-tokenizer": ["@csstools/css-tokenizer@3.0.4", "", {}, "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw=="],
"@csstools/media-query-list-parser": ["@csstools/media-query-list-parser@4.0.3", "", { "peerDependencies": { "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4" } }, "sha512-HAYH7d3TLRHDOUQK4mZKf9k9Ph/m8Akstg66ywKR4SFAigjs3yBiUeZtFxywiTm5moZMAp/5W/ZuFnNXXYLuuQ=="],
"@csstools/postcss-alpha-function": ["@csstools/postcss-alpha-function@1.0.1", "", { "dependencies": { "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", "@csstools/postcss-progressive-custom-properties": "^4.2.1", "@csstools/utilities": "^2.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-isfLLwksH3yHkFXfCI2Gcaqg7wGGHZZwunoJzEZk0yKYIokgre6hYVFibKL3SYAoR1kBXova8LB+JoO5vZzi9w=="],
"@csstools/postcss-cascade-layers": ["@csstools/postcss-cascade-layers@5.0.2", "", { "dependencies": { "@csstools/selector-specificity": "^5.0.0", "postcss-selector-parser": "^7.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-nWBE08nhO8uWl6kSAeCx4im7QfVko3zLrtgWZY4/bP87zrSPpSyN/3W3TDqz1jJuH+kbKOHXg5rJnK+ZVYcFFg=="],
"@csstools/postcss-color-function": ["@csstools/postcss-color-function@4.0.12", "", { "dependencies": { "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", "@csstools/postcss-progressive-custom-properties": "^4.2.1", "@csstools/utilities": "^2.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-yx3cljQKRaSBc2hfh8rMZFZzChaFgwmO2JfFgFr1vMcF3C/uyy5I4RFIBOIWGq1D+XbKCG789CGkG6zzkLpagA=="],
"@csstools/postcss-color-function-display-p3-linear": ["@csstools/postcss-color-function-display-p3-linear@1.0.1", "", { "dependencies": { "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", "@csstools/postcss-progressive-custom-properties": "^4.2.1", "@csstools/utilities": "^2.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-E5qusdzhlmO1TztYzDIi8XPdPoYOjoTY6HBYBCYSj+Gn4gQRBlvjgPQXzfzuPQqt8EhkC/SzPKObg4Mbn8/xMg=="],
"@csstools/postcss-color-mix-function": ["@csstools/postcss-color-mix-function@3.0.12", "", { "dependencies": { "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", "@csstools/postcss-progressive-custom-properties": "^4.2.1", "@csstools/utilities": "^2.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-4STERZfCP5Jcs13P1U5pTvI9SkgLgfMUMhdXW8IlJWkzOOOqhZIjcNhWtNJZes2nkBDsIKJ0CJtFtuaZ00moag=="],
"@csstools/postcss-color-mix-variadic-function-arguments": ["@csstools/postcss-color-mix-variadic-function-arguments@1.0.2", "", { "dependencies": { "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", "@csstools/postcss-progressive-custom-properties": "^4.2.1", "@csstools/utilities": "^2.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-rM67Gp9lRAkTo+X31DUqMEq+iK+EFqsidfecmhrteErxJZb6tUoJBVQca1Vn1GpDql1s1rD1pKcuYzMsg7Z1KQ=="],
"@csstools/postcss-content-alt-text": ["@csstools/postcss-content-alt-text@2.0.8", "", { "dependencies": { "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", "@csstools/postcss-progressive-custom-properties": "^4.2.1", "@csstools/utilities": "^2.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-9SfEW9QCxEpTlNMnpSqFaHyzsiRpZ5J5+KqCu1u5/eEJAWsMhzT40qf0FIbeeglEvrGRMdDzAxMIz3wqoGSb+Q=="],
"@csstools/postcss-contrast-color-function": ["@csstools/postcss-contrast-color-function@2.0.12", "", { "dependencies": { "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", "@csstools/postcss-progressive-custom-properties": "^4.2.1", "@csstools/utilities": "^2.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-YbwWckjK3qwKjeYz/CijgcS7WDUCtKTd8ShLztm3/i5dhh4NaqzsbYnhm4bjrpFpnLZ31jVcbK8YL77z3GBPzA=="],
"@csstools/postcss-exponential-functions": ["@csstools/postcss-exponential-functions@2.0.9", "", { "dependencies": { "@csstools/css-calc": "^2.1.4", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-abg2W/PI3HXwS/CZshSa79kNWNZHdJPMBXeZNyPQFbbj8sKO3jXxOt/wF7juJVjyDTc6JrvaUZYFcSBZBhaxjw=="],
"@csstools/postcss-font-format-keywords": ["@csstools/postcss-font-format-keywords@4.0.0", "", { "dependencies": { "@csstools/utilities": "^2.0.0", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-usBzw9aCRDvchpok6C+4TXC57btc4bJtmKQWOHQxOVKen1ZfVqBUuCZ/wuqdX5GHsD0NRSr9XTP+5ID1ZZQBXw=="],
"@csstools/postcss-gamut-mapping": ["@csstools/postcss-gamut-mapping@2.0.11", "", { "dependencies": { "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-fCpCUgZNE2piVJKC76zFsgVW1apF6dpYsqGyH8SIeCcM4pTEsRTWTLCaJIMKFEundsCKwY1rwfhtrio04RJ4Dw=="],
"@csstools/postcss-gradients-interpolation-method": ["@csstools/postcss-gradients-interpolation-method@5.0.12", "", { "dependencies": { "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", "@csstools/postcss-progressive-custom-properties": "^4.2.1", "@csstools/utilities": "^2.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-jugzjwkUY0wtNrZlFeyXzimUL3hN4xMvoPnIXxoZqxDvjZRiSh+itgHcVUWzJ2VwD/VAMEgCLvtaJHX+4Vj3Ow=="],
"@csstools/postcss-hwb-function": ["@csstools/postcss-hwb-function@4.0.12", "", { "dependencies": { "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", "@csstools/postcss-progressive-custom-properties": "^4.2.1", "@csstools/utilities": "^2.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-mL/+88Z53KrE4JdePYFJAQWFrcADEqsLprExCM04GDNgHIztwFzj0Mbhd/yxMBngq0NIlz58VVxjt5abNs1VhA=="],
"@csstools/postcss-ic-unit": ["@csstools/postcss-ic-unit@4.0.4", "", { "dependencies": { "@csstools/postcss-progressive-custom-properties": "^4.2.1", "@csstools/utilities": "^2.0.0", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-yQ4VmossuOAql65sCPppVO1yfb7hDscf4GseF0VCA/DTDaBc0Wtf8MTqVPfjGYlT5+2buokG0Gp7y0atYZpwjg=="],
"@csstools/postcss-initial": ["@csstools/postcss-initial@2.0.1", "", { "peerDependencies": { "postcss": "^8.4" } }, "sha512-L1wLVMSAZ4wovznquK0xmC7QSctzO4D0Is590bxpGqhqjboLXYA16dWZpfwImkdOgACdQ9PqXsuRroW6qPlEsg=="],
"@csstools/postcss-is-pseudo-class": ["@csstools/postcss-is-pseudo-class@5.0.3", "", { "dependencies": { "@csstools/selector-specificity": "^5.0.0", "postcss-selector-parser": "^7.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-jS/TY4SpG4gszAtIg7Qnf3AS2pjcUM5SzxpApOrlndMeGhIbaTzWBzzP/IApXoNWEW7OhcjkRT48jnAUIFXhAQ=="],
"@csstools/postcss-light-dark-function": ["@csstools/postcss-light-dark-function@2.0.11", "", { "dependencies": { "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", "@csstools/postcss-progressive-custom-properties": "^4.2.1", "@csstools/utilities": "^2.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-fNJcKXJdPM3Lyrbmgw2OBbaioU7yuKZtiXClf4sGdQttitijYlZMD5K7HrC/eF83VRWRrYq6OZ0Lx92leV2LFA=="],
"@csstools/postcss-logical-float-and-clear": ["@csstools/postcss-logical-float-and-clear@3.0.0", "", { "peerDependencies": { "postcss": "^8.4" } }, "sha512-SEmaHMszwakI2rqKRJgE+8rpotFfne1ZS6bZqBoQIicFyV+xT1UF42eORPxJkVJVrH9C0ctUgwMSn3BLOIZldQ=="],
"@csstools/postcss-logical-overflow": ["@csstools/postcss-logical-overflow@2.0.0", "", { "peerDependencies": { "postcss": "^8.4" } }, "sha512-spzR1MInxPuXKEX2csMamshR4LRaSZ3UXVaRGjeQxl70ySxOhMpP2252RAFsg8QyyBXBzuVOOdx1+bVO5bPIzA=="],
"@csstools/postcss-logical-overscroll-behavior": ["@csstools/postcss-logical-overscroll-behavior@2.0.0", "", { "peerDependencies": { "postcss": "^8.4" } }, "sha512-e/webMjoGOSYfqLunyzByZj5KKe5oyVg/YSbie99VEaSDE2kimFm0q1f6t/6Jo+VVCQ/jbe2Xy+uX+C4xzWs4w=="],
"@csstools/postcss-logical-resize": ["@csstools/postcss-logical-resize@3.0.0", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-DFbHQOFW/+I+MY4Ycd/QN6Dg4Hcbb50elIJCfnwkRTCX05G11SwViI5BbBlg9iHRl4ytB7pmY5ieAFk3ws7yyg=="],
"@csstools/postcss-logical-viewport-units": ["@csstools/postcss-logical-viewport-units@3.0.4", "", { "dependencies": { "@csstools/css-tokenizer": "^3.0.4", "@csstools/utilities": "^2.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-q+eHV1haXA4w9xBwZLKjVKAWn3W2CMqmpNpZUk5kRprvSiBEGMgrNH3/sJZ8UA3JgyHaOt3jwT9uFa4wLX4EqQ=="],
"@csstools/postcss-media-minmax": ["@csstools/postcss-media-minmax@2.0.9", "", { "dependencies": { "@csstools/css-calc": "^2.1.4", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", "@csstools/media-query-list-parser": "^4.0.3" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-af9Qw3uS3JhYLnCbqtZ9crTvvkR+0Se+bBqSr7ykAnl9yKhk6895z9rf+2F4dClIDJWxgn0iZZ1PSdkhrbs2ig=="],
"@csstools/postcss-media-queries-aspect-ratio-number-values": ["@csstools/postcss-media-queries-aspect-ratio-number-values@3.0.5", "", { "dependencies": { "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", "@csstools/media-query-list-parser": "^4.0.3" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-zhAe31xaaXOY2Px8IYfoVTB3wglbJUVigGphFLj6exb7cjZRH9A6adyE22XfFK3P2PzwRk0VDeTJmaxpluyrDg=="],
"@csstools/postcss-nested-calc": ["@csstools/postcss-nested-calc@4.0.0", "", { "dependencies": { "@csstools/utilities": "^2.0.0", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-jMYDdqrQQxE7k9+KjstC3NbsmC063n1FTPLCgCRS2/qHUbHM0mNy9pIn4QIiQGs9I/Bg98vMqw7mJXBxa0N88A=="],
"@csstools/postcss-normalize-display-values": ["@csstools/postcss-normalize-display-values@4.0.0", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-HlEoG0IDRoHXzXnkV4in47dzsxdsjdz6+j7MLjaACABX2NfvjFS6XVAnpaDyGesz9gK2SC7MbNwdCHusObKJ9Q=="],
"@csstools/postcss-oklab-function": ["@csstools/postcss-oklab-function@4.0.12", "", { "dependencies": { "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", "@csstools/postcss-progressive-custom-properties": "^4.2.1", "@csstools/utilities": "^2.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-HhlSmnE1NKBhXsTnNGjxvhryKtO7tJd1w42DKOGFD6jSHtYOrsJTQDKPMwvOfrzUAk8t7GcpIfRyM7ssqHpFjg=="],
"@csstools/postcss-progressive-custom-properties": ["@csstools/postcss-progressive-custom-properties@4.2.1", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-uPiiXf7IEKtUQXsxu6uWtOlRMXd2QWWy5fhxHDnPdXKCQckPP3E34ZgDoZ62r2iT+UOgWsSbM4NvHE5m3mAEdw=="],
"@csstools/postcss-random-function": ["@csstools/postcss-random-function@2.0.1", "", { "dependencies": { "@csstools/css-calc": "^2.1.4", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-q+FQaNiRBhnoSNo+GzqGOIBKoHQ43lYz0ICrV+UudfWnEF6ksS6DsBIJSISKQT2Bvu3g4k6r7t0zYrk5pDlo8w=="],
"@csstools/postcss-relative-color-syntax": ["@csstools/postcss-relative-color-syntax@3.0.12", "", { "dependencies": { "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", "@csstools/postcss-progressive-custom-properties": "^4.2.1", "@csstools/utilities": "^2.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-0RLIeONxu/mtxRtf3o41Lq2ghLimw0w9ByLWnnEVuy89exmEEq8bynveBxNW3nyHqLAFEeNtVEmC1QK9MZ8Huw=="],
"@csstools/postcss-scope-pseudo-class": ["@csstools/postcss-scope-pseudo-class@4.0.1", "", { "dependencies": { "postcss-selector-parser": "^7.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-IMi9FwtH6LMNuLea1bjVMQAsUhFxJnyLSgOp/cpv5hrzWmrUYU5fm0EguNDIIOHUqzXode8F/1qkC/tEo/qN8Q=="],
"@csstools/postcss-sign-functions": ["@csstools/postcss-sign-functions@1.1.4", "", { "dependencies": { "@csstools/css-calc": "^2.1.4", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-P97h1XqRPcfcJndFdG95Gv/6ZzxUBBISem0IDqPZ7WMvc/wlO+yU0c5D/OCpZ5TJoTt63Ok3knGk64N+o6L2Pg=="],
"@csstools/postcss-stepped-value-functions": ["@csstools/postcss-stepped-value-functions@4.0.9", "", { "dependencies": { "@csstools/css-calc": "^2.1.4", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-h9btycWrsex4dNLeQfyU3y3w40LMQooJWFMm/SK9lrKguHDcFl4VMkncKKoXi2z5rM9YGWbUQABI8BT2UydIcA=="],
"@csstools/postcss-text-decoration-shorthand": ["@csstools/postcss-text-decoration-shorthand@4.0.3", "", { "dependencies": { "@csstools/color-helpers": "^5.1.0", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-KSkGgZfx0kQjRIYnpsD7X2Om9BUXX/Kii77VBifQW9Ih929hK0KNjVngHDH0bFB9GmfWcR9vJYJJRvw/NQjkrA=="],
"@csstools/postcss-trigonometric-functions": ["@csstools/postcss-trigonometric-functions@4.0.9", "", { "dependencies": { "@csstools/css-calc": "^2.1.4", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-Hnh5zJUdpNrJqK9v1/E3BbrQhaDTj5YiX7P61TOvUhoDHnUmsNNxcDAgkQ32RrcWx9GVUvfUNPcUkn8R3vIX6A=="],
"@csstools/postcss-unset-value": ["@csstools/postcss-unset-value@4.0.0", "", { "peerDependencies": { "postcss": "^8.4" } }, "sha512-cBz3tOCI5Fw6NIFEwU3RiwK6mn3nKegjpJuzCndoGq3BZPkUjnsq7uQmIeMNeMbMk7YD2MfKcgCpZwX5jyXqCA=="],
"@csstools/selector-resolve-nested": ["@csstools/selector-resolve-nested@3.1.0", "", { "peerDependencies": { "postcss-selector-parser": "^7.0.0" } }, "sha512-mf1LEW0tJLKfWyvn5KdDrhpxHyuxpbNwTIwOYLIvsTffeyOf85j5oIzfG0yosxDgx/sswlqBnESYUcQH0vgZ0g=="],
"@csstools/selector-specificity": ["@csstools/selector-specificity@5.0.0", "", { "peerDependencies": { "postcss-selector-parser": "^7.0.0" } }, "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw=="],
"@csstools/utilities": ["@csstools/utilities@2.0.0", "", { "peerDependencies": { "postcss": "^8.4" } }, "sha512-5VdOr0Z71u+Yp3ozOx8T11N703wIFGVRgOWbOZMKgglPJsWA54MRIoMNVMa7shUToIhx5J8vX4sOZgD2XiihiQ=="],
"@fullhuman/postcss-purgecss": ["@fullhuman/postcss-purgecss@7.0.2", "", { "dependencies": { "purgecss": "^7.0.2" }, "peerDependencies": { "postcss": "^8.0.0" } }, "sha512-U4zAXNaVztbDxO9EdcLp51F3UxxYsb/7DN89rFxFJhfk2Wua2pvw2Kf3HdspbPhW/wpHjSjsxWYoIlbTgRSjbQ=="],
"@isaacs/balanced-match": ["@isaacs/balanced-match@4.0.1", "", {}, "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ=="],
"@isaacs/brace-expansion": ["@isaacs/brace-expansion@5.0.0", "", { "dependencies": { "@isaacs/balanced-match": "^4.0.1" } }, "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA=="],
"@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="],
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
"anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
"autoprefixer": ["autoprefixer@10.4.21", "", { "dependencies": { "browserslist": "^4.24.4", "caniuse-lite": "^1.0.30001702", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ=="],
"baseline-browser-mapping": ["baseline-browser-mapping@2.8.10", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-uLfgBi+7IBNay8ECBO2mVMGZAc1VgZWEChxm4lv+TobGdG82LnXMjuNGo/BSSZZL4UmkWhxEHP2f5ziLNwGWMA=="],
"binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="],
"boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="],
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
"browserslist": ["browserslist@4.26.3", "", { "dependencies": { "baseline-browser-mapping": "^2.8.9", "caniuse-lite": "^1.0.30001746", "electron-to-chromium": "^1.5.227", "node-releases": "^2.0.21", "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w=="],
"caniuse-api": ["caniuse-api@3.0.0", "", { "dependencies": { "browserslist": "^4.0.0", "caniuse-lite": "^1.0.0", "lodash.memoize": "^4.1.2", "lodash.uniq": "^4.5.0" } }, "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw=="],
"caniuse-lite": ["caniuse-lite@1.0.30001746", "", {}, "sha512-eA7Ys/DGw+pnkWWSE/id29f2IcPHVoE8wxtvE5JdvD2V28VTDPy1yEeo11Guz0sJ4ZeGRcm3uaTcAqK1LXaphA=="],
"chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
"cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
"colord": ["colord@2.9.3", "", {}, "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw=="],
"commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="],
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
"css-blank-pseudo": ["css-blank-pseudo@7.0.1", "", { "dependencies": { "postcss-selector-parser": "^7.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-jf+twWGDf6LDoXDUode+nc7ZlrqfaNphrBIBrcmeP3D8yw1uPaix1gCC8LUQUGQ6CycuK2opkbFFWFuq/a94ag=="],
"css-declaration-sorter": ["css-declaration-sorter@7.3.0", "", { "peerDependencies": { "postcss": "^8.0.9" } }, "sha512-LQF6N/3vkAMYF4xoHLJfG718HRJh34Z8BnNhd6bosOMIVjMlhuZK5++oZa3uYAgrI5+7x2o27gUqTR2U/KjUOQ=="],
"css-has-pseudo": ["css-has-pseudo@7.0.3", "", { "dependencies": { "@csstools/selector-specificity": "^5.0.0", "postcss-selector-parser": "^7.0.0", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-oG+vKuGyqe/xvEMoxAQrhi7uY16deJR3i7wwhBerVrGQKSqUC5GiOVxTpM9F9B9hw0J+eKeOWLH7E9gZ1Dr5rA=="],
"css-prefers-color-scheme": ["css-prefers-color-scheme@10.0.0", "", { "peerDependencies": { "postcss": "^8.4" } }, "sha512-VCtXZAWivRglTZditUfB4StnsWr6YVZ2PRtuxQLKTNRdtAf8tpzaVPE9zXIF3VaSc7O70iK/j1+NXxyQCqdPjQ=="],
"css-select": ["css-select@5.2.2", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" } }, "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw=="],
"css-tree": ["css-tree@3.1.0", "", { "dependencies": { "mdn-data": "2.12.2", "source-map-js": "^1.0.1" } }, "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w=="],
"css-what": ["css-what@6.2.2", "", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="],
"cssdb": ["cssdb@8.4.2", "", {}, "sha512-PzjkRkRUS+IHDJohtxkIczlxPPZqRo0nXplsYXOMBRPjcVRjj1W4DfvRgshUYTVuUigU7ptVYkFJQ7abUB0nyg=="],
"cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="],
"cssnano": ["cssnano@7.1.1", "", { "dependencies": { "cssnano-preset-default": "^7.0.9", "lilconfig": "^3.1.3" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-fm4D8ti0dQmFPeF8DXSAA//btEmqCOgAc/9Oa3C1LW94h5usNrJEfrON7b4FkPZgnDEn6OUs5NdxiJZmAtGOpQ=="],
"cssnano-preset-default": ["cssnano-preset-default@7.0.9", "", { "dependencies": { "browserslist": "^4.25.1", "css-declaration-sorter": "^7.2.0", "cssnano-utils": "^5.0.1", "postcss-calc": "^10.1.1", "postcss-colormin": "^7.0.4", "postcss-convert-values": "^7.0.7", "postcss-discard-comments": "^7.0.4", "postcss-discard-duplicates": "^7.0.2", "postcss-discard-empty": "^7.0.1", "postcss-discard-overridden": "^7.0.1", "postcss-merge-longhand": "^7.0.5", "postcss-merge-rules": "^7.0.6", "postcss-minify-font-values": "^7.0.1", "postcss-minify-gradients": "^7.0.1", "postcss-minify-params": "^7.0.4", "postcss-minify-selectors": "^7.0.5", "postcss-normalize-charset": "^7.0.1", "postcss-normalize-display-values": "^7.0.1", "postcss-normalize-positions": "^7.0.1", "postcss-normalize-repeat-style": "^7.0.1", "postcss-normalize-string": "^7.0.1", "postcss-normalize-timing-functions": "^7.0.1", "postcss-normalize-unicode": "^7.0.4", "postcss-normalize-url": "^7.0.1", "postcss-normalize-whitespace": "^7.0.1", "postcss-ordered-values": "^7.0.2", "postcss-reduce-initial": "^7.0.4", "postcss-reduce-transforms": "^7.0.1", "postcss-svgo": "^7.1.0", "postcss-unique-selectors": "^7.0.4" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-tCD6AAFgYBOVpMBX41KjbvRh9c2uUjLXRyV7KHSIrwHiq5Z9o0TFfUCoM3TwVrRsRteN3sVXGNvjVNxYzkpTsA=="],
"cssnano-utils": ["cssnano-utils@5.0.1", "", { "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-ZIP71eQgG9JwjVZsTPSqhc6GHgEr53uJ7tK5///VfyWj6Xp2DBmixWHqJgPno+PqATzn48pL42ww9x5SSGmhZg=="],
"csso": ["csso@5.0.5", "", { "dependencies": { "css-tree": "~2.2.0" } }, "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ=="],
"dependency-graph": ["dependency-graph@1.0.0", "", {}, "sha512-cW3gggJ28HZ/LExwxP2B++aiKxhJXMSIt9K48FOXQkm+vuG5gyatXnLsONRJdzO/7VfjDIiaOOa/bs4l464Lwg=="],
"dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="],
"domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="],
"domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="],
"domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="],
"eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="],
"electron-to-chromium": ["electron-to-chromium@1.5.229", "", {}, "sha512-cwhDcZKGcT/rEthLRJ9eBlMDkh1sorgsuk+6dpsehV0g9CABsIqBxU4rLRjG+d/U6pYU1s37A4lSKrVc5lSQYg=="],
"emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
"entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
"foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="],
"fraction.js": ["fraction.js@4.3.7", "", {}, "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew=="],
"fs-extra": ["fs-extra@11.3.2", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A=="],
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
"get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
"glob": ["glob@11.0.3", "", { "dependencies": { "foreground-child": "^3.3.1", "jackspeak": "^4.1.1", "minimatch": "^10.0.3", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA=="],
"glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
"is-binary-path": ["is-binary-path@2.1.0", "", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="],
"is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="],
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
"is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
"jackspeak": ["jackspeak@4.1.1", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" } }, "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ=="],
"jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="],
"lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="],
"lodash.memoize": ["lodash.memoize@4.1.2", "", {}, "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag=="],
"lodash.uniq": ["lodash.uniq@4.5.0", "", {}, "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ=="],
"lru-cache": ["lru-cache@11.2.2", "", {}, "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg=="],
"mdn-data": ["mdn-data@2.12.2", "", {}, "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA=="],
"minimatch": ["minimatch@10.0.3", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw=="],
"minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
"node-releases": ["node-releases@2.0.21", "", {}, "sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw=="],
"normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
"normalize-range": ["normalize-range@0.1.2", "", {}, "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA=="],
"nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="],
"package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="],
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
"path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
"path-scurry": ["path-scurry@2.0.0", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg=="],
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
"pify": ["pify@2.3.0", "", {}, "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog=="],
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
"postcss-attribute-case-insensitive": ["postcss-attribute-case-insensitive@7.0.1", "", { "dependencies": { "postcss-selector-parser": "^7.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-Uai+SupNSqzlschRyNx3kbCTWgY/2hcwtHEI/ej2LJWc9JJ77qKgGptd8DHwY1mXtZ7Aoh4z4yxfwMBue9eNgw=="],
"postcss-calc": ["postcss-calc@10.1.1", "", { "dependencies": { "postcss-selector-parser": "^7.0.0", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4.38" } }, "sha512-NYEsLHh8DgG/PRH2+G9BTuUdtf9ViS+vdoQ0YA5OQdGsfN4ztiwtDWNtBl9EKeqNMFnIu8IKZ0cLxEQ5r5KVMw=="],
"postcss-clamp": ["postcss-clamp@4.1.0", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4.6" } }, "sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow=="],
"postcss-cli": ["postcss-cli@11.0.1", "", { "dependencies": { "chokidar": "^3.3.0", "dependency-graph": "^1.0.0", "fs-extra": "^11.0.0", "picocolors": "^1.0.0", "postcss-load-config": "^5.0.0", "postcss-reporter": "^7.0.0", "pretty-hrtime": "^1.0.3", "read-cache": "^1.0.0", "slash": "^5.0.0", "tinyglobby": "^0.2.12", "yargs": "^17.0.0" }, "peerDependencies": { "postcss": "^8.0.0" }, "bin": { "postcss": "index.js" } }, "sha512-0UnkNPSayHKRe/tc2YGW6XnSqqOA9eqpiRMgRlV1S6HdGi16vwJBx7lviARzbV1HpQHqLLRH3o8vTcB0cLc+5g=="],
"postcss-color-functional-notation": ["postcss-color-functional-notation@7.0.12", "", { "dependencies": { "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", "@csstools/postcss-progressive-custom-properties": "^4.2.1", "@csstools/utilities": "^2.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-TLCW9fN5kvO/u38/uesdpbx3e8AkTYhMvDZYa9JpmImWuTE99bDQ7GU7hdOADIZsiI9/zuxfAJxny/khknp1Zw=="],
"postcss-color-hex-alpha": ["postcss-color-hex-alpha@10.0.0", "", { "dependencies": { "@csstools/utilities": "^2.0.0", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-1kervM2cnlgPs2a8Vt/Qbe5cQ++N7rkYo/2rz2BkqJZIHQwaVuJgQH38REHrAi4uM0b1fqxMkWYmese94iMp3w=="],
"postcss-color-rebeccapurple": ["postcss-color-rebeccapurple@10.0.0", "", { "dependencies": { "@csstools/utilities": "^2.0.0", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-JFta737jSP+hdAIEhk1Vs0q0YF5P8fFcj+09pweS8ktuGuZ8pPlykHsk6mPxZ8awDl4TrcxUqJo9l1IhVr/OjQ=="],
"postcss-colormin": ["postcss-colormin@7.0.4", "", { "dependencies": { "browserslist": "^4.25.1", "caniuse-api": "^3.0.0", "colord": "^2.9.3", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-ziQuVzQZBROpKpfeDwmrG+Vvlr0YWmY/ZAk99XD+mGEBuEojoFekL41NCsdhyNUtZI7DPOoIWIR7vQQK9xwluw=="],
"postcss-convert-values": ["postcss-convert-values@7.0.7", "", { "dependencies": { "browserslist": "^4.25.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-HR9DZLN04Xbe6xugRH6lS4ZQH2zm/bFh/ZyRkpedZozhvh+awAfbA0P36InO4fZfDhvYfNJeNvlTf1sjwGbw/A=="],
"postcss-custom-media": ["postcss-custom-media@11.0.6", "", { "dependencies": { "@csstools/cascade-layer-name-parser": "^2.0.5", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", "@csstools/media-query-list-parser": "^4.0.3" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-C4lD4b7mUIw+RZhtY7qUbf4eADmb7Ey8BFA2px9jUbwg7pjTZDl4KY4bvlUV+/vXQvzQRfiGEVJyAbtOsCMInw=="],
"postcss-custom-properties": ["postcss-custom-properties@14.0.6", "", { "dependencies": { "@csstools/cascade-layer-name-parser": "^2.0.5", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", "@csstools/utilities": "^2.0.0", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-fTYSp3xuk4BUeVhxCSJdIPhDLpJfNakZKoiTDx7yRGCdlZrSJR7mWKVOBS4sBF+5poPQFMj2YdXx1VHItBGihQ=="],
"postcss-custom-selectors": ["postcss-custom-selectors@8.0.5", "", { "dependencies": { "@csstools/cascade-layer-name-parser": "^2.0.5", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", "postcss-selector-parser": "^7.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-9PGmckHQswiB2usSO6XMSswO2yFWVoCAuih1yl9FVcwkscLjRKjwsjM3t+NIWpSU2Jx3eOiK2+t4vVTQaoCHHg=="],
"postcss-dir-pseudo-class": ["postcss-dir-pseudo-class@9.0.1", "", { "dependencies": { "postcss-selector-parser": "^7.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-tRBEK0MHYvcMUrAuYMEOa0zg9APqirBcgzi6P21OhxtJyJADo/SWBwY1CAwEohQ/6HDaa9jCjLRG7K3PVQYHEA=="],
"postcss-discard-comments": ["postcss-discard-comments@7.0.4", "", { "dependencies": { "postcss-selector-parser": "^7.1.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-6tCUoql/ipWwKtVP/xYiFf1U9QgJ0PUvxN7pTcsQ8Ns3Fnwq1pU5D5s1MhT/XySeLq6GXNvn37U46Ded0TckWg=="],
"postcss-discard-duplicates": ["postcss-discard-duplicates@7.0.2", "", { "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-eTonaQvPZ/3i1ASDHOKkYwAybiM45zFIc7KXils4mQmHLqIswXD9XNOKEVxtTFnsmwYzF66u4LMgSr0abDlh5w=="],
"postcss-discard-empty": ["postcss-discard-empty@7.0.1", "", { "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-cFrJKZvcg/uxB6Ijr4l6qmn3pXQBna9zyrPC+sK0zjbkDUZew+6xDltSF7OeB7rAtzaaMVYSdbod+sZOCWnMOg=="],
"postcss-discard-overridden": ["postcss-discard-overridden@7.0.1", "", { "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-7c3MMjjSZ/qYrx3uc1940GSOzN1Iqjtlqe8uoSg+qdVPYyRb0TILSqqmtlSFuE4mTDECwsm397Ya7iXGzfF7lg=="],
"postcss-double-position-gradients": ["postcss-double-position-gradients@6.0.4", "", { "dependencies": { "@csstools/postcss-progressive-custom-properties": "^4.2.1", "@csstools/utilities": "^2.0.0", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-m6IKmxo7FxSP5nF2l63QbCC3r+bWpFUWmZXZf096WxG0m7Vl1Q1+ruFOhpdDRmKrRS+S3Jtk+TVk/7z0+BVK6g=="],
"postcss-focus-visible": ["postcss-focus-visible@10.0.1", "", { "dependencies": { "postcss-selector-parser": "^7.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-U58wyjS/I1GZgjRok33aE8juW9qQgQUNwTSdxQGuShHzwuYdcklnvK/+qOWX1Q9kr7ysbraQ6ht6r+udansalA=="],
"postcss-focus-within": ["postcss-focus-within@9.0.1", "", { "dependencies": { "postcss-selector-parser": "^7.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-fzNUyS1yOYa7mOjpci/bR+u+ESvdar6hk8XNK/TRR0fiGTp2QT5N+ducP0n3rfH/m9I7H/EQU6lsa2BrgxkEjw=="],
"postcss-font-variant": ["postcss-font-variant@5.0.0", "", { "peerDependencies": { "postcss": "^8.1.0" } }, "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA=="],
"postcss-gap-properties": ["postcss-gap-properties@6.0.0", "", { "peerDependencies": { "postcss": "^8.4" } }, "sha512-Om0WPjEwiM9Ru+VhfEDPZJAKWUd0mV1HmNXqp2C29z80aQ2uP9UVhLc7e3aYMIor/S5cVhoPgYQ7RtfeZpYTRw=="],
"postcss-image-set-function": ["postcss-image-set-function@7.0.0", "", { "dependencies": { "@csstools/utilities": "^2.0.0", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-QL7W7QNlZuzOwBTeXEmbVckNt1FSmhQtbMRvGGqqU4Nf4xk6KUEQhAoWuMzwbSv5jxiRiSZ5Tv7eiDB9U87znA=="],
"postcss-import": ["postcss-import@16.1.1", "", { "dependencies": { "postcss-value-parser": "^4.0.0", "read-cache": "^1.0.0", "resolve": "^1.1.7" }, "peerDependencies": { "postcss": "^8.0.0" } }, "sha512-2xVS1NCZAfjtVdvXiyegxzJ447GyqCeEI5V7ApgQVOWnros1p5lGNovJNapwPpMombyFBfqDwt7AD3n2l0KOfQ=="],
"postcss-lab-function": ["postcss-lab-function@7.0.12", "", { "dependencies": { "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", "@csstools/postcss-progressive-custom-properties": "^4.2.1", "@csstools/utilities": "^2.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-tUcyRk1ZTPec3OuKFsqtRzW2Go5lehW29XA21lZ65XmzQkz43VY2tyWEC202F7W3mILOjw0voOiuxRGTsN+J9w=="],
"postcss-load-config": ["postcss-load-config@5.1.0", "", { "dependencies": { "lilconfig": "^3.1.1", "yaml": "^2.4.2" }, "peerDependencies": { "jiti": ">=1.21.0", "postcss": ">=8.0.9", "tsx": "^4.8.1" }, "optionalPeers": ["jiti", "postcss", "tsx"] }, "sha512-G5AJ+IX0aD0dygOE0yFZQ/huFFMSNneyfp0e3/bT05a8OfPC5FUoZRPfGijUdGOJNMewJiwzcHJXFafFzeKFVA=="],
"postcss-logical": ["postcss-logical@8.1.0", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-pL1hXFQ2fEXNKiNiAgtfA005T9FBxky5zkX6s4GZM2D8RkVgRqz3f4g1JUoq925zXv495qk8UNldDwh8uGEDoA=="],
"postcss-merge-longhand": ["postcss-merge-longhand@7.0.5", "", { "dependencies": { "postcss-value-parser": "^4.2.0", "stylehacks": "^7.0.5" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-Kpu5v4Ys6QI59FxmxtNB/iHUVDn9Y9sYw66D6+SZoIk4QTz1prC4aYkhIESu+ieG1iylod1f8MILMs1Em3mmIw=="],
"postcss-merge-rules": ["postcss-merge-rules@7.0.6", "", { "dependencies": { "browserslist": "^4.25.1", "caniuse-api": "^3.0.0", "cssnano-utils": "^5.0.1", "postcss-selector-parser": "^7.1.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-2jIPT4Tzs8K87tvgCpSukRQ2jjd+hH6Bb8rEEOUDmmhOeTcqDg5fEFK8uKIu+Pvc3//sm3Uu6FRqfyv7YF7+BQ=="],
"postcss-minify-font-values": ["postcss-minify-font-values@7.0.1", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-2m1uiuJeTplll+tq4ENOQSzB8LRnSUChBv7oSyFLsJRtUgAAJGP6LLz0/8lkinTgxrmJSPOEhgY1bMXOQ4ZXhQ=="],
"postcss-minify-gradients": ["postcss-minify-gradients@7.0.1", "", { "dependencies": { "colord": "^2.9.3", "cssnano-utils": "^5.0.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-X9JjaysZJwlqNkJbUDgOclyG3jZEpAMOfof6PUZjPnPrePnPG62pS17CjdM32uT1Uq1jFvNSff9l7kNbmMSL2A=="],
"postcss-minify-params": ["postcss-minify-params@7.0.4", "", { "dependencies": { "browserslist": "^4.25.1", "cssnano-utils": "^5.0.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-3OqqUddfH8c2e7M35W6zIwv7jssM/3miF9cbCSb1iJiWvtguQjlxZGIHK9JRmc8XAKmE2PFGtHSM7g/VcW97sw=="],
"postcss-minify-selectors": ["postcss-minify-selectors@7.0.5", "", { "dependencies": { "cssesc": "^3.0.0", "postcss-selector-parser": "^7.1.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-x2/IvofHcdIrAm9Q+p06ZD1h6FPcQ32WtCRVodJLDR+WMn8EVHI1kvLxZuGKz/9EY5nAmI6lIQIrpo4tBy5+ug=="],
"postcss-nesting": ["postcss-nesting@13.0.2", "", { "dependencies": { "@csstools/selector-resolve-nested": "^3.1.0", "@csstools/selector-specificity": "^5.0.0", "postcss-selector-parser": "^7.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-1YCI290TX+VP0U/K/aFxzHzQWHWURL+CtHMSbex1lCdpXD1SoR2sYuxDu5aNI9lPoXpKTCggFZiDJbwylU0LEQ=="],
"postcss-normalize-charset": ["postcss-normalize-charset@7.0.1", "", { "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-sn413ofhSQHlZFae//m9FTOfkmiZ+YQXsbosqOWRiVQncU2BA3daX3n0VF3cG6rGLSFVc5Di/yns0dFfh8NFgQ=="],
"postcss-normalize-display-values": ["postcss-normalize-display-values@7.0.1", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-E5nnB26XjSYz/mGITm6JgiDpAbVuAkzXwLzRZtts19jHDUBFxZ0BkXAehy0uimrOjYJbocby4FVswA/5noOxrQ=="],
"postcss-normalize-positions": ["postcss-normalize-positions@7.0.1", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-pB/SzrIP2l50ZIYu+yQZyMNmnAcwyYb9R1fVWPRxm4zcUFCY2ign7rcntGFuMXDdd9L2pPNUgoODDk91PzRZuQ=="],
"postcss-normalize-repeat-style": ["postcss-normalize-repeat-style@7.0.1", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-NsSQJ8zj8TIDiF0ig44Byo3Jk9e4gNt9x2VIlJudnQQ5DhWAHJPF4Tr1ITwyHio2BUi/I6Iv0HRO7beHYOloYQ=="],
"postcss-normalize-string": ["postcss-normalize-string@7.0.1", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-QByrI7hAhsoze992kpbMlJSbZ8FuCEc1OT9EFbZ6HldXNpsdpZr+YXC5di3UEv0+jeZlHbZcoCADgb7a+lPmmQ=="],
"postcss-normalize-timing-functions": ["postcss-normalize-timing-functions@7.0.1", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-bHifyuuSNdKKsnNJ0s8fmfLMlvsQwYVxIoUBnowIVl2ZAdrkYQNGVB4RxjfpvkMjipqvbz0u7feBZybkl/6NJg=="],
"postcss-normalize-unicode": ["postcss-normalize-unicode@7.0.4", "", { "dependencies": { "browserslist": "^4.25.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-LvIURTi1sQoZqj8mEIE8R15yvM+OhbR1avynMtI9bUzj5gGKR/gfZFd8O7VMj0QgJaIFzxDwxGl/ASMYAkqO8g=="],
"postcss-normalize-url": ["postcss-normalize-url@7.0.1", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-sUcD2cWtyK1AOL/82Fwy1aIVm/wwj5SdZkgZ3QiUzSzQQofrbq15jWJ3BA7Z+yVRwamCjJgZJN0I9IS7c6tgeQ=="],
"postcss-normalize-whitespace": ["postcss-normalize-whitespace@7.0.1", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-vsbgFHMFQrJBJKrUFJNZ2pgBeBkC2IvvoHjz1to0/0Xk7sII24T0qFOiJzG6Fu3zJoq/0yI4rKWi7WhApW+EFA=="],
"postcss-opacity-percentage": ["postcss-opacity-percentage@3.0.0", "", { "peerDependencies": { "postcss": "^8.4" } }, "sha512-K6HGVzyxUxd/VgZdX04DCtdwWJ4NGLG212US4/LA1TLAbHgmAsTWVR86o+gGIbFtnTkfOpb9sCRBx8K7HO66qQ=="],
"postcss-ordered-values": ["postcss-ordered-values@7.0.2", "", { "dependencies": { "cssnano-utils": "^5.0.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-AMJjt1ECBffF7CEON/Y0rekRLS6KsePU6PRP08UqYW4UGFRnTXNrByUzYK1h8AC7UWTZdQ9O3Oq9kFIhm0SFEw=="],
"postcss-overflow-shorthand": ["postcss-overflow-shorthand@6.0.0", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-BdDl/AbVkDjoTofzDQnwDdm/Ym6oS9KgmO7Gr+LHYjNWJ6ExORe4+3pcLQsLA9gIROMkiGVjjwZNoL/mpXHd5Q=="],
"postcss-page-break": ["postcss-page-break@3.0.4", "", { "peerDependencies": { "postcss": "^8" } }, "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ=="],
"postcss-place": ["postcss-place@10.0.0", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-5EBrMzat2pPAxQNWYavwAfoKfYcTADJ8AXGVPcUZ2UkNloUTWzJQExgrzrDkh3EKzmAx1evfTAzF9I8NGcc+qw=="],
"postcss-preset-env": ["postcss-preset-env@10.4.0", "", { "dependencies": { "@csstools/postcss-alpha-function": "^1.0.1", "@csstools/postcss-cascade-layers": "^5.0.2", "@csstools/postcss-color-function": "^4.0.12", "@csstools/postcss-color-function-display-p3-linear": "^1.0.1", "@csstools/postcss-color-mix-function": "^3.0.12", "@csstools/postcss-color-mix-variadic-function-arguments": "^1.0.2", "@csstools/postcss-content-alt-text": "^2.0.8", "@csstools/postcss-contrast-color-function": "^2.0.12", "@csstools/postcss-exponential-functions": "^2.0.9", "@csstools/postcss-font-format-keywords": "^4.0.0", "@csstools/postcss-gamut-mapping": "^2.0.11", "@csstools/postcss-gradients-interpolation-method": "^5.0.12", "@csstools/postcss-hwb-function": "^4.0.12", "@csstools/postcss-ic-unit": "^4.0.4", "@csstools/postcss-initial": "^2.0.1", "@csstools/postcss-is-pseudo-class": "^5.0.3", "@csstools/postcss-light-dark-function": "^2.0.11", "@csstools/postcss-logical-float-and-clear": "^3.0.0", "@csstools/postcss-logical-overflow": "^2.0.0", "@csstools/postcss-logical-overscroll-behavior": "^2.0.0", "@csstools/postcss-logical-resize": "^3.0.0", "@csstools/postcss-logical-viewport-units": "^3.0.4", "@csstools/postcss-media-minmax": "^2.0.9", "@csstools/postcss-media-queries-aspect-ratio-number-values": "^3.0.5", "@csstools/postcss-nested-calc": "^4.0.0", "@csstools/postcss-normalize-display-values": "^4.0.0", "@csstools/postcss-oklab-function": "^4.0.12", "@csstools/postcss-progressive-custom-properties": "^4.2.1", "@csstools/postcss-random-function": "^2.0.1", "@csstools/postcss-relative-color-syntax": "^3.0.12", "@csstools/postcss-scope-pseudo-class": "^4.0.1", "@csstools/postcss-sign-functions": "^1.1.4", "@csstools/postcss-stepped-value-functions": "^4.0.9", "@csstools/postcss-text-decoration-shorthand": "^4.0.3", "@csstools/postcss-trigonometric-functions": "^4.0.9", "@csstools/postcss-unset-value": "^4.0.0", "autoprefixer": "^10.4.21", "browserslist": "^4.26.0", "css-blank-pseudo": "^7.0.1", "css-has-pseudo": "^7.0.3", "css-prefers-color-scheme": "^10.0.0", "cssdb": "^8.4.2", "postcss-attribute-case-insensitive": "^7.0.1", "postcss-clamp": "^4.1.0", "postcss-color-functional-notation": "^7.0.12", "postcss-color-hex-alpha": "^10.0.0", "postcss-color-rebeccapurple": "^10.0.0", "postcss-custom-media": "^11.0.6", "postcss-custom-properties": "^14.0.6", "postcss-custom-selectors": "^8.0.5", "postcss-dir-pseudo-class": "^9.0.1", "postcss-double-position-gradients": "^6.0.4", "postcss-focus-visible": "^10.0.1", "postcss-focus-within": "^9.0.1", "postcss-font-variant": "^5.0.0", "postcss-gap-properties": "^6.0.0", "postcss-image-set-function": "^7.0.0", "postcss-lab-function": "^7.0.12", "postcss-logical": "^8.1.0", "postcss-nesting": "^13.0.2", "postcss-opacity-percentage": "^3.0.0", "postcss-overflow-shorthand": "^6.0.0", "postcss-page-break": "^3.0.4", "postcss-place": "^10.0.0", "postcss-pseudo-class-any-link": "^10.0.1", "postcss-replace-overflow-wrap": "^4.0.0", "postcss-selector-not": "^8.0.1" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-2kqpOthQ6JhxqQq1FSAAZGe9COQv75Aw8WbsOvQVNJ2nSevc9Yx/IKZGuZ7XJ+iOTtVon7LfO7ELRzg8AZ+sdw=="],
"postcss-pseudo-class-any-link": ["postcss-pseudo-class-any-link@10.0.1", "", { "dependencies": { "postcss-selector-parser": "^7.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-3el9rXlBOqTFaMFkWDOkHUTQekFIYnaQY55Rsp8As8QQkpiSgIYEcF/6Ond93oHiDsGb4kad8zjt+NPlOC1H0Q=="],
"postcss-reduce-initial": ["postcss-reduce-initial@7.0.4", "", { "dependencies": { "browserslist": "^4.25.1", "caniuse-api": "^3.0.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-rdIC9IlMBn7zJo6puim58Xd++0HdbvHeHaPgXsimMfG1ijC5A9ULvNLSE0rUKVJOvNMcwewW4Ga21ngyJjY/+Q=="],
"postcss-reduce-transforms": ["postcss-reduce-transforms@7.0.1", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-MhyEbfrm+Mlp/36hvZ9mT9DaO7dbncU0CvWI8V93LRkY6IYlu38OPg3FObnuKTUxJ4qA8HpurdQOo5CyqqO76g=="],
"postcss-replace-overflow-wrap": ["postcss-replace-overflow-wrap@4.0.0", "", { "peerDependencies": { "postcss": "^8.0.3" } }, "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw=="],
"postcss-reporter": ["postcss-reporter@7.1.0", "", { "dependencies": { "picocolors": "^1.0.0", "thenby": "^1.3.4" }, "peerDependencies": { "postcss": "^8.1.0" } }, "sha512-/eoEylGWyy6/DOiMP5lmFRdmDKThqgn7D6hP2dXKJI/0rJSO1ADFNngZfDzxL0YAxFvws+Rtpuji1YIHj4mySA=="],
"postcss-selector-not": ["postcss-selector-not@8.0.1", "", { "dependencies": { "postcss-selector-parser": "^7.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-kmVy/5PYVb2UOhy0+LqUYAhKj7DUGDpSWa5LZqlkWJaaAV+dxxsOG3+St0yNLu6vsKD7Dmqx+nWQt0iil89+WA=="],
"postcss-selector-parser": ["postcss-selector-parser@6.1.2", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg=="],
"postcss-svgo": ["postcss-svgo@7.1.0", "", { "dependencies": { "postcss-value-parser": "^4.2.0", "svgo": "^4.0.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-KnAlfmhtoLz6IuU3Sij2ycusNs4jPW+QoFE5kuuUOK8awR6tMxZQrs5Ey3BUz7nFCzT3eqyFgqkyrHiaU2xx3w=="],
"postcss-unique-selectors": ["postcss-unique-selectors@7.0.4", "", { "dependencies": { "postcss-selector-parser": "^7.1.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-pmlZjsmEAG7cHd7uK3ZiNSW6otSZ13RHuZ/4cDN/bVglS5EpF2r2oxY99SuOHa8m7AWoBCelTS3JPpzsIs8skQ=="],
"postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="],
"pretty-hrtime": ["pretty-hrtime@1.0.3", "", {}, "sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A=="],
"purgecss": ["purgecss@7.0.2", "", { "dependencies": { "commander": "^12.1.0", "glob": "^11.0.0", "postcss": "^8.4.47", "postcss-selector-parser": "^6.1.2" }, "bin": { "purgecss": "bin/purgecss.js" } }, "sha512-4Ku8KoxNhOWi9X1XJ73XY5fv+I+hhTRedKpGs/2gaBKU8ijUiIKF/uyyIyh7Wo713bELSICF5/NswjcuOqYouQ=="],
"read-cache": ["read-cache@1.0.0", "", { "dependencies": { "pify": "^2.3.0" } }, "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA=="],
"readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
"require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
"resolve": ["resolve@1.22.10", "", { "dependencies": { "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w=="],
"sax": ["sax@1.4.1", "", {}, "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg=="],
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
"signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
"slash": ["slash@5.1.0", "", {}, "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg=="],
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
"string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
"string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
"strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"stylehacks": ["stylehacks@7.0.6", "", { "dependencies": { "browserslist": "^4.25.1", "postcss-selector-parser": "^7.1.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-iitguKivmsueOmTO0wmxURXBP8uqOO+zikLGZ7Mm9e/94R4w5T999Js2taS/KBOnQ/wdC3jN3vNSrkGDrlnqQg=="],
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
"svgo": ["svgo@4.0.0", "", { "dependencies": { "commander": "^11.1.0", "css-select": "^5.1.0", "css-tree": "^3.0.1", "css-what": "^6.1.0", "csso": "^5.0.5", "picocolors": "^1.1.1", "sax": "^1.4.1" }, "bin": "./bin/svgo.js" }, "sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw=="],
"thenby": ["thenby@1.3.4", "", {}, "sha512-89Gi5raiWA3QZ4b2ePcEwswC3me9JIg+ToSgtE0JWeCynLnLxNr/f9G+xfo9K+Oj4AFdom8YNJjibIARTJmapQ=="],
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
"universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="],
"update-browserslist-db": ["update-browserslist-db@1.1.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw=="],
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
"wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
"wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
"y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
"yaml": ["yaml@2.8.1", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw=="],
"yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
"yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
"@csstools/postcss-cascade-layers/postcss-selector-parser": ["postcss-selector-parser@7.1.0", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA=="],
"@csstools/postcss-is-pseudo-class/postcss-selector-parser": ["postcss-selector-parser@7.1.0", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA=="],
"@csstools/postcss-scope-pseudo-class/postcss-selector-parser": ["postcss-selector-parser@7.1.0", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA=="],
"@csstools/selector-resolve-nested/postcss-selector-parser": ["postcss-selector-parser@7.1.0", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA=="],
"@csstools/selector-specificity/postcss-selector-parser": ["postcss-selector-parser@7.1.0", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA=="],
"@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="],
"@isaacs/cliui/strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="],
"@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
"anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"css-blank-pseudo/postcss-selector-parser": ["postcss-selector-parser@7.1.0", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA=="],
"css-has-pseudo/postcss-selector-parser": ["postcss-selector-parser@7.1.0", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA=="],
"csso/css-tree": ["css-tree@2.2.1", "", { "dependencies": { "mdn-data": "2.0.28", "source-map-js": "^1.0.1" } }, "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA=="],
"postcss-attribute-case-insensitive/postcss-selector-parser": ["postcss-selector-parser@7.1.0", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA=="],
"postcss-calc/postcss-selector-parser": ["postcss-selector-parser@7.1.0", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA=="],
"postcss-custom-selectors/postcss-selector-parser": ["postcss-selector-parser@7.1.0", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA=="],
"postcss-dir-pseudo-class/postcss-selector-parser": ["postcss-selector-parser@7.1.0", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA=="],
"postcss-discard-comments/postcss-selector-parser": ["postcss-selector-parser@7.1.0", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA=="],
"postcss-focus-visible/postcss-selector-parser": ["postcss-selector-parser@7.1.0", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA=="],
"postcss-focus-within/postcss-selector-parser": ["postcss-selector-parser@7.1.0", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA=="],
"postcss-merge-rules/postcss-selector-parser": ["postcss-selector-parser@7.1.0", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA=="],
"postcss-minify-selectors/postcss-selector-parser": ["postcss-selector-parser@7.1.0", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA=="],
"postcss-nesting/postcss-selector-parser": ["postcss-selector-parser@7.1.0", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA=="],
"postcss-pseudo-class-any-link/postcss-selector-parser": ["postcss-selector-parser@7.1.0", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA=="],
"postcss-selector-not/postcss-selector-parser": ["postcss-selector-parser@7.1.0", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA=="],
"postcss-unique-selectors/postcss-selector-parser": ["postcss-selector-parser@7.1.0", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA=="],
"readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"stylehacks/postcss-selector-parser": ["postcss-selector-parser@7.1.0", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA=="],
"svgo/commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="],
"@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
"@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
"@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
"csso/css-tree/mdn-data": ["mdn-data@2.0.28", "", {}, "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g=="],
}
}

18
package.json Normal file
View File

@@ -0,0 +1,18 @@
{
"name": "passport-css-compiler",
"private": true,
"description": "A manifest to acquire CLI tools for passport. Not a Node app, only required for compiling CSS.",
"license": "MIT",
"type": "module",
"scripts": {
"build": "postcss src/styles/*.css !src/styles/base.css --dir src/assets/styles"
},
"devDependencies": {
"@fullhuman/postcss-purgecss": "^7.0.2",
"cssnano": "^7.1.1",
"postcss": "^8.4.35",
"postcss-cli": "^11.0.0",
"postcss-import": "^16.1.1",
"postcss-preset-env": "^10.4.0"
}
}

22
postcss.config.js Normal file
View File

@@ -0,0 +1,22 @@
import { purgeCSSPlugin } from "@fullhuman/postcss-purgecss";
import postcssPresetEnv from "postcss-preset-env";
import postcssImport from "postcss-import";
import cssnano from "cssnano";
export default {
plugins: [
postcssImport,
purgeCSSPlugin({
content: ["./src/**/*.hbs", "./src/**/*.js"],
}),
postcssPresetEnv({
browsers: "last 4 versions",
// false *dsables* polyfills
features: {
"cascade-layers": false,
},
autoprefixer: false,
}),
cssnano,
],
};

View File

@@ -1,4 +1,4 @@
//go:generate tailwindcss -i styles/main.scss -o assets/tailwind.css --minify //go:generate bun run build
package main package main
@@ -44,7 +44,7 @@ import (
_ "modernc.org/sqlite" _ "modernc.org/sqlite"
) )
//go:embed assets/** templates/** schema.sql scripts/**.js styles/**.css //go:embed assets/** templates/** schema.sql scripts/**.js
var embeddedAssets embed.FS var embeddedAssets embed.FS
var devContent = `<script> var devContent = `<script>
@@ -285,7 +285,12 @@ func UploadFile(file *multipart.FileHeader, contentType string, c fiber.Ctx) (st
return "", err return "", err
} }
fileName := fmt.Sprintf("%s.%s", fileId.String(), filepath.Ext(file.Filename)) var fileName string
if filepath.Ext(file.Filename) != ".svg" {
fileName = fmt.Sprintf("%s.webp", fileId.String())
} else {
fileName = fmt.Sprintf("%s.svg", fileId.String())
}
srcFile, err := file.Open() srcFile, err := file.Open()
if err != nil { if err != nil {
@@ -307,6 +312,10 @@ func UploadFile(file *multipart.FileHeader, contentType string, c fiber.Ctx) (st
return "", errors.New("unsupported file type") return "", errors.New("unsupported file type")
} }
if err != nil {
return "", err
}
if contentType != "image/svg+xml" { if contentType != "image/svg+xml" {
off, err := srcFile.Seek(0, io.SeekStart) off, err := srcFile.Seek(0, io.SeekStart)
if err != nil { if err != nil {
@@ -708,6 +717,10 @@ func main() {
engine := handlebars.NewFileSystem(http.FS(templatesDir), ".hbs") engine := handlebars.NewFileSystem(http.FS(templatesDir), ".hbs")
engine.AddFunc("eq", func(a, b any) bool {
return a == b
})
engine.AddFunc("embedFile", func(fileToEmbed string) string { engine.AddFunc("embedFile", func(fileToEmbed string) string {
content, err := fs.ReadFile(embeddedAssets, fileToEmbed) content, err := fs.ReadFile(embeddedAssets, fileToEmbed)
if err != nil { if err != nil {
@@ -715,6 +728,7 @@ func main() {
} }
fileExtension := filepath.Ext(fileToEmbed) fileExtension := filepath.Ext(fileToEmbed)
switch fileExtension { switch fileExtension {
case ".js": case ".js":
return fmt.Sprintf("<script>%s</script>", content) return fmt.Sprintf("<script>%s</script>", content)
@@ -761,10 +775,13 @@ func main() {
router.Use("/assets", static.New("", static.Config{ router.Use("/assets", static.New("", static.Config{
FS: assetsDir, FS: assetsDir,
Browse: false,
MaxAge: 31536000, MaxAge: 31536000,
})) }))
router.Get("/", func(c fiber.Ctx) error { router.Get("/", func(c fiber.Ctx) error {
c.Response().Header.Set("Link", "</assets/fonts/InstrumentSans-VariableFont_wdth,wght.woff2>; rel=preload; as=font; type=font/woff2; crossorigin")
renderData := fiber.Map{ renderData := fiber.Map{
"SearchProviderURL": app.Config.SearchProvider.URL, "SearchProviderURL": app.Config.SearchProvider.URL,
"SearchParam": app.Config.SearchProvider.Query, "SearchParam": app.Config.SearchProvider.Query,
@@ -785,7 +802,7 @@ func main() {
renderData["UptimeData"] = app.UptimeManager.GetUptime() renderData["UptimeData"] = app.UptimeManager.GetUptime()
} }
return c.Render("views/index", renderData, "layouts/main") return c.Render("views/index", renderData)
}) })
router.Use(middleware.AdminMiddleware(app.db)) router.Use(middleware.AdminMiddleware(app.db))
@@ -795,7 +812,7 @@ func main() {
return c.Redirect().To("/admin") return c.Redirect().To("/admin")
} }
return c.Render("views/admin/login", fiber.Map{}, "layouts/main") return c.Render("views/admin/login", fiber.Map{})
}) })
router.Post("/admin/login", func(c fiber.Ctx) error { router.Post("/admin/login", func(c fiber.Ctx) error {
@@ -844,7 +861,8 @@ func main() {
return c.Render("views/admin/index", fiber.Map{ return c.Render("views/admin/index", fiber.Map{
"Categories": app.CategoryManager.GetCategories(), "Categories": app.CategoryManager.GetCategories(),
}, "layouts/admin") "IsAdmin": true,
})
}) })
api := router.Group("/api") api := router.Group("/api")

View File

@@ -1,3 +1,5 @@
"use strict";
// idfk what this variable capitalization is, it's a mess // idfk what this variable capitalization is, it's a mess
let modalContainer = document.getElementById("modal-container"); let modalContainer = document.getElementById("modal-container");
let modal = modalContainer.querySelector("div"); let modal = modalContainer.querySelector("div");
@@ -54,7 +56,7 @@ function addErrorListener(form) {
event.target.parentElement event.target.parentElement
.querySelectorAll("[required]") .querySelectorAll("[required]")
.forEach((el) => { .forEach((el) => {
el.classList.add("invalid:border-[#861024]!"); el.classList.add("invalid");
}); });
}); });
} }
@@ -83,7 +85,7 @@ function cloneEditActions(primaryActions) {
let actionButtonObj = primaryActions[i]; let actionButtonObj = primaryActions[i];
let actionButton = editActions.querySelector( let actionButton = editActions.querySelector(
`div[data-primary-actions] button:nth-child(${i + 1})` `div:first-child button:nth-child(${i + 1})`
); );
actionButton.setAttribute("onclick", actionButtonObj.clickAction); actionButton.setAttribute("onclick", actionButtonObj.clickAction);
actionButton.setAttribute("aria-label", actionButtonObj.label); actionButton.setAttribute("aria-label", actionButtonObj.label);
@@ -120,7 +122,7 @@ document
newLinkCard.classList.add("link-card", "admin", "relative"); newLinkCard.classList.add("link-card", "admin", "relative");
let newLinkImgElement = newLinkCard.querySelector( let newLinkImgElement = newLinkCard.querySelector(
"div[data-img-container] img" "div:first-child img"
); );
newLinkImgElement.src = await processFile(data.get("icon")); newLinkImgElement.src = await processFile(data.get("icon"));
@@ -200,7 +202,7 @@ document
categoryHeader.appendChild(editActions); categoryHeader.appendChild(editActions);
let categoryImg = categoryHeader.querySelector(".category-img"); let categoryImg = categoryHeader.querySelector("div:first-child");
categoryImg.querySelector("img").src = await processFile( categoryImg.querySelector("img").src = await processFile(
data.get("icon") data.get("icon")
@@ -336,11 +338,11 @@ function closeModal() {
.getElementById(activeModal + "-form") .getElementById(activeModal + "-form")
.querySelectorAll("[required]") .querySelectorAll("[required]")
.forEach((el) => { .forEach((el) => {
el.classList.remove("invalid:border-[#861024]!"); el.classList.remove("invalid");
}); });
} }
targetCategoryID = null; currentlyEditing = {};
} }
/** /**
@@ -417,22 +419,23 @@ function cancelEdit() {
* @param {HTMLElement} target The target element that was clicked * @param {HTMLElement} target The target element that was clicked
*/ */
function editLink(target) { function editLink(target) {
let startTime = performance.now();
// we do it in this dynamic way so that if we add a new link without refreshing the page, it still works // we do it in this dynamic way so that if we add a new link without refreshing the page, it still works
let linkEl = target.closest(".link-card"); let linkEl = target.closest("[data-card]");
let linkID = parseInt(linkEl.id); let linkID = parseInt(linkEl.id);
let categoryID = parseInt(linkEl.parentElement.previousElementSibling.id); let categoryID = parseInt(linkEl.parentElement.previousElementSibling.id);
if (currentlyEditing.linkID !== undefined) { if (
currentlyEditing.linkID !== undefined ||
currentlyEditing.categoryID !== undefined
) {
// cancel the edit if it's already in progress // cancel the edit if it's already in progress
cancelEdit(); cancelEdit();
} }
let linkImg = linkEl.querySelector("div[data-img-container] img"); let linkImg = linkEl.querySelector("div:first-child img");
let linkName = linkEl.querySelector("div[data-text-container] h3"); let linkName = linkEl.querySelector("div:nth-child(2) h3");
let linkDesc = linkEl.querySelector("div[data-text-container] p"); let linkDesc = linkEl.querySelector("div:nth-child(2) p");
let editActions = linkEl.querySelector("[data-edit-actions]"); let editActions = linkEl.querySelector("div:nth-child(3)");
currentlyEditing = { currentlyEditing = {
type: "link", type: "link",
@@ -453,8 +456,7 @@ function editLink(target) {
teleportElement(selectIconButton, linkImg.parentElement); teleportElement(selectIconButton, linkImg.parentElement);
teleportElement(confirmActions, editActions); teleportElement(confirmActions, editActions);
editActions.querySelector("div[data-primary-actions]").style.display = editActions.querySelector("div:first-child").style.display = "none";
"none";
requestAnimationFrame(() => { requestAnimationFrame(() => {
currentlyEditing.cleanup = replaceWithResizableTextarea([ currentlyEditing.cleanup = replaceWithResizableTextarea([
@@ -498,6 +500,7 @@ async function confirmLinkEdit() {
formData.get("description") === null && formData.get("description") === null &&
formData.get("icon") === null formData.get("icon") === null
) { ) {
cancelEdit();
return; return;
} }
@@ -527,14 +530,14 @@ function cancelLinkEdit(
let linkEl = document.getElementById(`${currentlyEditing.linkID}_link`); let linkEl = document.getElementById(`${currentlyEditing.linkID}_link`);
let linkInput = linkEl.querySelector("textarea"); let linkInput = linkEl.querySelector("textarea");
let linkTextarea = linkInput.nextElementSibling; let linkTextarea = linkInput.nextElementSibling;
let linkImg = linkEl.querySelector("div[data-img-container] img"); let linkImg = linkEl.querySelector("div:first-child img");
let editActions = linkEl.querySelector("[data-edit-actions]"); let editActions = linkEl.querySelector("div:nth-child(3)");
if (currentlyEditing.icon !== undefined) { if (currentlyEditing.icon !== undefined) {
linkImg.src = currentlyEditing.icon; linkImg.src = currentlyEditing.icon;
} }
editActions.querySelector("div[data-primary-actions]").style.display = ""; editActions.querySelector("div:first-child").style.display = "";
// teleport the teleported elements back to the body for literally safe keeping // teleport the teleported elements back to the body for literally safe keeping
unteleportElement(selectIconButton); unteleportElement(selectIconButton);
@@ -557,7 +560,10 @@ function deleteLink(target) {
let linkID = parseInt(linkEl.id); let linkID = parseInt(linkEl.id);
let categoryID = parseInt(linkEl.parentElement.previousElementSibling.id); let categoryID = parseInt(linkEl.parentElement.previousElementSibling.id);
if (currentlyEditing.linkID !== undefined) { if (
currentlyEditing.linkID !== undefined ||
currentlyEditing.categoryID !== undefined
) {
// cancel the edit if it's already in progress // cancel the edit if it's already in progress
cancelEdit(); cancelEdit();
} }
@@ -596,14 +602,17 @@ function editCategory(target) {
let categoryEl = target.closest(".category-header"); let categoryEl = target.closest(".category-header");
let categoryID = parseInt(categoryEl.id); let categoryID = parseInt(categoryEl.id);
if (currentlyEditing.linkID !== undefined) { if (
currentlyEditing.linkID !== undefined ||
currentlyEditing.categoryID !== undefined
) {
// cancel the edit if it's already in progress // cancel the edit if it's already in progress
cancelEdit(); cancelEdit();
} }
let categoryName = categoryEl.querySelector("h2"); let categoryName = categoryEl.querySelector("h2");
let categoryIcon = categoryEl.querySelector("div[data-img-container] img"); let categoryIcon = categoryEl.querySelector("div:first-child img");
let editActions = categoryEl.querySelector("[data-edit-actions]"); let editActions = categoryEl.querySelector("div:nth-child(3)");
currentlyEditing = { currentlyEditing = {
type: "category", type: "category",
@@ -622,8 +631,7 @@ function editCategory(target) {
teleportElement(selectIconButton, categoryIcon.parentElement); teleportElement(selectIconButton, categoryIcon.parentElement);
teleportElement(confirmActions, editActions); teleportElement(confirmActions, editActions);
editActions.querySelector("div[data-primary-actions]").style.display = editActions.querySelector("div:first-child").style.display = "none";
"none";
requestAnimationFrame(() => { requestAnimationFrame(() => {
currentlyEditing.cleanup = replaceWithResizableTextarea([ currentlyEditing.cleanup = replaceWithResizableTextarea([
@@ -659,6 +667,7 @@ async function confirmCategoryEdit() {
// nothing to update // nothing to update
if (formData.get("name") === null && formData.get("icon") === null) { if (formData.get("name") === null && formData.get("icon") === null) {
cancelEdit();
return; return;
} }
@@ -686,8 +695,8 @@ function cancelCategoryEdit(text = currentlyEditing.originalText) {
); );
let categoryInput = categoryEl.querySelector("textarea"); let categoryInput = categoryEl.querySelector("textarea");
let categoryIcon = categoryEl.querySelector(".category-img img"); let categoryIcon = categoryEl.querySelector("div:first-child img");
let editActions = categoryEl.querySelector("[data-edit-actions]"); let editActions = categoryEl.querySelector("div:nth-child(3)");
if (currentlyEditing.icon !== undefined) { if (currentlyEditing.icon !== undefined) {
categoryIcon.src = currentlyEditing.icon; categoryIcon.src = currentlyEditing.icon;
@@ -696,7 +705,7 @@ function cancelCategoryEdit(text = currentlyEditing.originalText) {
unteleportElement(selectIconButton); unteleportElement(selectIconButton);
unteleportElement(confirmActions); unteleportElement(confirmActions);
editActions.querySelector("div[data-primary-actions]").style.display = ""; editActions.querySelector("div:first-child").style.display = "";
restoreElementFromInput(categoryInput, text); restoreElementFromInput(categoryInput, text);
@@ -711,7 +720,10 @@ function cancelCategoryEdit(text = currentlyEditing.originalText) {
function deleteCategory(target) { function deleteCategory(target) {
let categoryEl = target.closest(".category-header"); let categoryEl = target.closest(".category-header");
if (currentlyEditing.categoryID !== undefined) { if (
currentlyEditing.categoryID !== undefined ||
currentlyEditing.linkID !== undefined
) {
// cancel the edit if it's already in progress // cancel the edit if it's already in progress
cancelEdit(); cancelEdit();
} }
@@ -815,8 +827,14 @@ function replaceWithResizableTextarea(targetEls) {
let maxWidth = parentBoundingRect.width - borderWidth; let maxWidth = parentBoundingRect.width - borderWidth;
// take care of category headers specifically because the parent bounding box contains two other elements // take care of category headers specifically because the parent bounding box contains two other elements
if (targetEl.tagName === "H2") { if (targetEl.tagName === "H2") {
let imageComputedStyle = window.getComputedStyle(
targetEl.previousElementSibling
);
let imageWidth = let imageWidth =
targetEl.previousElementSibling.getBoundingClientRect().width; targetEl.previousElementSibling.getBoundingClientRect().width +
parseFloat(imageComputedStyle.marginLeft) +
parseFloat(imageComputedStyle.marginRight);
let actionButtonWidth = let actionButtonWidth =
targetEl.nextElementSibling.getBoundingClientRect().width; targetEl.nextElementSibling.getBoundingClientRect().width;
@@ -898,11 +916,12 @@ function replaceWithResizableTextarea(targetEls) {
// step 3: batch writes // step 3: batch writes
let inputElements = []; let inputElements = [];
elsInitialStyles.forEach((elInfo) => { elsInitialStyles.forEach((elInfo, i) => {
const inputElement = document.createElement("textarea"); const inputElement = document.createElement("textarea");
inputElement.value = elInfo.originalText; inputElement.value = elInfo.originalText;
inputElement.className = "resizable-input"; inputElement.className = "resizable-input";
inputElement.placeholder = elInfo.targetEl.dataset.placeholder; inputElement.placeholder =
i == 0 ? "Enter title..." : "Enter description...";
inputElement.dataset.originalElementType = elInfo.targetEl.tagName; inputElement.dataset.originalElementType = elInfo.targetEl.tagName;
inputElement.dataset.originalClassName = elInfo.targetEl.className; inputElement.dataset.originalClassName = elInfo.targetEl.className;
@@ -951,9 +970,15 @@ function replaceWithResizableTextarea(targetEls) {
// is it maybe a bit of some math that doesnt entirely make sense to me? you bet. But does it work? Hell yeah it does // is it maybe a bit of some math that doesnt entirely make sense to me? you bet. But does it work? Hell yeah it does
if (inputElement.dataset.originalElementType === "H2") { if (inputElement.dataset.originalElementType === "H2") {
let imageComputedStyle = window.getComputedStyle(
inputElement.previousElementSibling
);
let imageWidth = let imageWidth =
inputElement.previousElementSibling.getBoundingClientRect() inputElement.previousElementSibling.getBoundingClientRect()
.width; .width +
imageComputedStyle.marginLeft +
imageComputedStyle.marginRight;
let actionButtonWidth = let actionButtonWidth =
inputElement.nextElementSibling.getBoundingClientRect().width; inputElement.nextElementSibling.getBoundingClientRect().width;

View File

@@ -1,70 +1,238 @@
.modal-bg { @import "./base.css";
visibility: hidden; @import "./card.css";
opacity: 0;
}
.modal-bg.is-visible { @layer components {
visibility: visible;
opacity: 1;
}
.modal {
opacity: 0;
}
.modal.is-visible {
opacity: 1;
}
@media (prefers-reduced-motion: no-preference) {
.modal-bg { .modal-bg {
position: fixed;
visibility: hidden; visibility: hidden;
opacity: 0; opacity: 0;
inset: 0;
transition: opacity 0.3s ease, visibility 0s 0.3s; background-color: color-mix(in srgb, #000 45%, #0000);
transition-timing-function: cubic-bezier(0.45, 0, 0.55, 1); justify-content: center;
align-items: center;
} }
.modal-bg.is-visible { .modal-bg.is-visible {
visibility: visible; visibility: visible;
opacity: 1; opacity: 1;
transition-delay: 0s;
} }
.modal { .modal {
opacity: 0; opacity: 0;
transform: translateY(20px) scale(0.95); background-color: var(--color-overlay);
border-radius: calc(var(--spacing) * 3);
transition: opacity 0.3s ease, transform 0.3s ease; overflow: hidden;
transition-timing-function: cubic-bezier(0.45, 0, 0.55, 1); padding: calc(var(--spacing) * 4);
width: 100%;
max-width: 24rem;
} }
.modal.is-visible { .modal.is-visible {
opacity: 1; opacity: 1;
visibility: visible; }
transform: translateY(0) scale(1);
transition-delay: 0s; #blur-target {
} transition: filter 300ms cubic-bezier(0.45, 0, 0.55, 1);
} }
.action-button { @media (prefers-reduced-motion: no-preference) {
display: flex; .modal-bg {
width: fit-content; visibility: hidden;
height: fit-content; opacity: 0;
padding: 0.25rem;
background-color: var(--color-highlight-sm); transition: opacity 0.3s ease, visibility 0s 0.3s;
border: 1px solid color-mix(in srgb, var(--color-highlight) 70%, #0000); transition-timing-function: cubic-bezier(0.45, 0, 0.55, 1);
border-radius: 9999px; }
box-shadow: var(--shadow-sm);
cursor: pointer; .modal-bg.is-visible {
transition: filter 0.15s cubic-bezier(0.45, 0, 0.55, 1); visibility: visible;
contain: layout style paint; opacity: 1;
transition-delay: 0s;
&:hover { }
filter: brightness(125%);
} .modal {
opacity: 0;
&:active { transform: translateY(20px) scale(0.95);
filter: brightness(95%);
transition: opacity 0.3s ease, transform 0.3s ease;
transition-timing-function: cubic-bezier(0.45, 0, 0.55, 1);
}
.modal.is-visible {
opacity: 1;
visibility: visible;
transform: translateY(0) scale(1);
transition-delay: 0s;
}
#blur-target {
transition: none;
}
}
.modal-form {
display: flex;
flex-direction: column;
gap: calc(var(--spacing) * 2);
& > div {
display: flex;
flex-direction: column;
gap: calc(var(--spacing) * 1);
}
& > button {
background-color: var(--color-accent);
color: #fff;
border-radius: calc(var(--spacing) * 1.5);
padding-inline: calc(var(--spacing) * 4);
padding-block: calc(var(--spacing) * 2);
}
}
.delete-modal {
text-align: center;
& > p {
margin-bottom: calc(var(--spacing) * 3);
}
& > div {
display: flex;
justify-content: end;
flex-direction: column;
row-gap: calc(var(--spacing) * 2);
& > button {
padding-inline: calc(var(--spacing) * 4);
padding-block: calc(var(--spacing) * 2);
border-radius: calc(var(--spacing) * 1.5);
width: 100%;
color: #fff;
transition: filter 300ms cubic-bezier(0.45, 0, 0.55, 1);
&:nth-child(1) {
background-color: var(--color-error);
}
&:nth-child(2) {
border: 1px solid var(--color-highlight);
background-color: #0000;
}
&:hover {
filter: brightness(125%);
}
&:active {
filter: brightness(95%);
}
}
}
}
input:invalid.invalid {
border: 1px solid var(--color-error);
}
.new-link-card {
border: calc(var(--spacing) * 0.5) dashed var(--color-subtle);
cursor: pointer;
user-select: none;
&:hover {
text-decoration: underline;
}
}
.action-container {
display: flex;
flex-direction: row;
gap: calc(var(--spacing) * 2);
}
.link-grid > div > div:nth-child(3) {
position: absolute;
right: var(--spacing);
top: var(--spacing);
}
.category-header > div:nth-child(2) {
padding-left: calc(var(--spacing) * 2);
}
.add-category-button {
width: fit-content;
display: flex;
align-items: center;
color: var(--color-subtle);
cursor: pointer;
}
.add-category-button > h2 {
text-decoration: underline;
text-decoration-style: dashed;
text-decoration-thickness: calc(var(--spacing) * 0.5);
&:hover {
text-decoration: none;
}
}
.action-button {
display: flex;
width: fit-content;
height: fit-content;
padding: 0.25rem;
background-color: var(--color-highlight-sm);
border: 1px solid color-mix(in srgb, var(--color-highlight) 70%, #0000);
border-radius: 9999px;
box-shadow: var(--shadow-sm);
cursor: pointer;
transition: filter 0.15s cubic-bezier(0.45, 0, 0.55, 1);
contain: layout style paint;
&:hover {
filter: brightness(125%);
}
&:active {
filter: brightness(95%);
}
}
.select-icon-button {
display: flex;
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: color-mix(in srgb, var(--color-highlight) 70%, #0000);
color: var(--color-base);
justify-content: center;
align-items: center;
}
header {
display: flex;
width: 100%;
padding: calc(var(--spacing) * 3);
& > a {
text-decoration: none;
display: flex;
flex-direction: row;
align-items: center;
gap: calc(var(--spacing) * 2);
color: var(--color-text);
border-bottom: 1px solid var(--color-text);
justify-content: center;
line-height: var(--leading-condensed);
&:hover {
border-bottom-color: transparent;
}
}
} }
} }

199
src/styles/base.css Normal file
View File

@@ -0,0 +1,199 @@
@layer reset, base, components, utilities;
@font-face {
font-family: "Instrument Sans";
src: url("/assets/fonts/InstrumentSans-VariableFont_wdth,wght.woff2")
format("woff2");
font-display: swap;
}
@layer reset {
/*
Josh's Custom CSS Reset slightly Modified
https://www.joshwcomeau.com/css/custom-css-reset/
*/
*,
*::before,
*::after {
box-sizing: border-box;
}
* {
border: 0 solid;
line-height: calc(1em + 0.5rem);
margin: 0;
padding: 0;
}
body {
-webkit-font-smoothing: antialiased;
}
img,
picture,
video,
canvas,
svg {
display: block;
max-width: 100%;
}
input,
button,
textarea,
select {
font: inherit;
}
button {
cursor: pointer;
}
p,
h1,
h2,
h3,
h4,
h5,
h6 {
word-break: break-word;
}
p {
text-wrap: pretty;
hyphens: auto;
}
h1,
h2,
h3,
h4,
h5,
h6 {
text-wrap: balance;
}
a {
color: #fff;
}
}
@layer base {
:root {
--family-sans: "Instrument Sans", ui-sans-serif, system-ui, sans-serif,
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol",
"Noto Color Emoji";
--color-accent: oklch(57.93% 0.258 294.12);
--color-success: oklch(70.19% 0.158 160.44);
--color-error: oklch(53% 0.251 28.48);
--color-base: oklch(11% 0.007 285);
--color-surface: oklch(19% 0.007 285.66);
--color-overlay: oklch(26% 0.008 285.66);
--color-muted: oklch(63% 0.015 286);
--color-subtle: oklch(72% 0.015 286);
--color-text: oklch(87% 0.015 286);
--color-highlight-sm: oklch(30.67% 0.007 286);
--color-highlight: oklch(39.26% 0.01 286);
--color-highlight-lg: oklch(47.72% 0.011 286);
--spacing: 0.25rem;
--leading-condensed: normal;
}
html,
body {
font-family: var(--family-sans);
color-scheme: dark;
color: var(--color-text);
background-color: var(--color-surface);
min-height: 100vh;
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-weight: 600;
}
h1 {
font-size: clamp(42px, 10vw, 64px);
}
h2 {
font-size: clamp(30px, 6vw, 36px);
}
h3 {
font-size: 1.25rem;
}
input:not(.search) {
color: var(--color-text);
padding-inline: calc(var(--spacing) * 4);
padding-block: calc(var(--spacing) * 2);
border-radius: calc(var(--spacing) * 1.5);
width: 100%;
background-color: var(--color-surface);
border: 1px solid color-mix(in srgb, var(--color-highlight) 70%, #0000);
transition-property: color, border, background-color;
transition-duration: 300ms;
transition-timing-function: cubic-bezier(0.45, 0, 0.55, 1);
overflow: hidden;
&::placeholder {
font-style: italic;
color: var(--color-highlight);
}
&:focus-visible {
outline: none;
}
&[type="file"] {
padding: 0;
cursor: pointer;
&::file-selector-button {
border: 0px;
padding: calc(var(--spacing) * 2);
margin-right: var(--spacing);
background-color: var(--color-highlight);
color: var(--color-subtle);
cursor: pointer;
}
}
}
}
@layer utilities {
.flex {
display: flex;
}
.hidden {
display: none;
}
.leading-condensed {
line-height: var(--leading-condensed);
}
.text-error {
color: var(--color-error);
}
.text-success {
color: var(--color-success);
}
}

148
src/styles/card.css Normal file
View File

@@ -0,0 +1,148 @@
/* All css related to the card and category stuff */
@layer components {
.link-grid > :is(a, div) {
display: flex;
flex-direction: row;
border-radius: calc(var(--spacing) * 4);
padding: calc(var(--spacing) * 2.5);
align-items: center;
transition-property: box-shadow, transform, translate;
transition-duration: 150ms;
transition-timing-function: cubic-bezier(0.45, 0, 0.55, 1);
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1),
0 2px 4px -2px rgb(0 0 0 / 0.1);
contain: layout style paint;
&:not(:is(div)) {
&:hover {
box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1),
0 4px 6px -4px rgb(0 0 0 / 0.1);
transform: translateY(-4px);
}
&:active {
box-shadow: 0 2px 4px -2px rgb(0 0 0 / 0.1);
transform: translateY(2px);
}
}
&:not(.new-link-card) {
text-decoration: none;
background: var(--color-overlay);
}
}
@media (prefers-reduced-motion: reduce) {
.link-grid > :is(a, div) {
transition: none;
&:hover {
transform: none;
}
&:active {
transform: none;
}
}
}
/* Div that holds the image */
.link-grid > :is(a, div) > div:first-child {
position: relative;
flex-shrink: 0;
margin-right: calc(var(--spacing) * 2);
border-radius: calc(var(--spacing) * 2.5);
overflow: hidden;
}
.link-grid > :is(a, div) > div:first-child img {
user-select: none;
aspect-ratio: 1/1;
object-fit: cover;
}
/* Div that holds the text */
.link-grid > :is(a, div) > div:nth-child(2) {
display: flex;
flex-grow: 1;
flex-direction: column;
row-gap: 1px;
overflow: hidden;
word-break: break-all;
}
.link-grid > :is(a, div) > div:nth-child(2) h3 {
border: 1px solid transparent;
}
.link-grid > :is(a, div) > div:nth-child(2) p {
color: var(--color-subtle);
white-space: pre-wrap;
border: 1px solid #0000;
min-height: calc(1em + 0.5rem);
border: 1px solid transparent;
min-height: 26px;
}
.category-header {
display: flex;
align-items: center;
}
.category-header > div:first-child {
display: flex;
flex-shrink: 0;
position: relative;
margin-right: calc(var(--spacing) * 2);
align-items: center;
justify-content: center;
width: calc(var(--spacing) * 8);
height: calc(var(--spacing) * 8);
border-radius: calc(var(--spacing) * 1.5);
overflow: hidden;
}
.categoy-header > div:first-child img {
user-select: none;
object-fit: cover;
aspect-ratio: 1/1;
}
.category-header h2 {
text-transform: capitalize;
word-break: break-all;
border-width: 1px;
border-color: #0000;
}
.link-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(min(330px, 100%), 1fr));
gap: calc(var(--spacing) * 2);
padding: calc(var(--spacing) * 2.5);
contain: layout style paint;
}
/* Empty state */
.link-grid > p {
color: var(--color-subtle);
}
.card-section {
display: flex;
justify-content: center;
width: 100%;
}
.card-section > div {
width: 100%;
padding: calc(var(--spacing) * 2.5);
}
@media (min-width: 640px) {
.card-section > div {
width: 80%;
}
}
}

58
src/styles/login.css Normal file
View File

@@ -0,0 +1,58 @@
@import "./base.css";
@layer base {
html,
body {
background-color: var(--color-base);
}
body {
display: flex;
justify-content: center;
align-items: center;
}
}
@layer components {
.login-container {
width: fit-content;
display: flex;
position: relative;
background-color: var(--color-surface);
border-radius: calc(var(--spacing) * 3);
overflow: hidden;
}
.login-container > img {
height: calc(var(--spacing) * 96);
width: calc(var(--spacing) * 64);
object-fit: cover;
}
.login-container > div {
display: flex;
flex-direction: column;
padding: calc(var(--spacing) * 4);
text-align: center;
}
.login-form {
display: flex;
flex-direction: column;
row-gap: calc(var(--spacing) * 3);
margin-top: calc(var(--spacing) * 2);
margin-bottom: calc(var(--spacing) * 2);
}
.login-form button {
padding-left: calc(var(--spacing) * 4);
padding-right: calc(var(--spacing) * 4);
padding-top: calc(var(--spacing) * 2);
padding-bottom: calc(var(--spacing) * 2);
border-radius: calc(var(--spacing) * 2.5);
background-color: var(--color-accent);
color: #fff;
}
}

133
src/styles/main.css Normal file
View File

@@ -0,0 +1,133 @@
@import "./base.css";
@import "./card.css";
@layer components {
.hero {
/* grid grid-rows-3 grid-cols-[1fr] justify-center items-center h-screen bg-base */
display: grid;
grid-template: repeat(3, minmax(0, 1fr)) / 1fr;
justify-content: center;
align-items: center;
height: 100vh;
background-color: var(--color-base);
}
.glance-container {
display: flex;
color: var(--color-subtle);
height: 100%;
padding: calc(var(--spacing) * 2.5);
justify-content: space-between;
}
.primary-hero-container {
display: flex;
flex-direction: column;
align-items: center;
grid-row-start: 2;
padding-left: calc(var(--spacing) * 3);
padding-right: calc(var(--spacing) * 3);
width: 100%;
}
.primary-hero-container > div {
display: flex;
align-items: center;
margin-bottom: calc(var(--spacing) * 2.5);
& > svg {
margin-right: calc(var(--spacing) * 3);
aspect-ratio: 1/1;
width: clamp(42px, 10vw, 60px);
}
}
.primary-hero-container > form {
width: 100%;
max-width: 48rem;
& > input {
color: #fff;
width: 100%;
background-color: var(--color-surface);
border: 1px solid
color-mix(in srgb, var(--color-highlight-sm) 70%, #0000);
padding-left: calc(var(--spacing) * 3);
padding-right: calc(var(--spacing) * 3);
padding-top: var(--spacing);
padding-bottom: var(--spacing);
height: calc(var(--spacing) * 7);
border-radius: 9999px;
&::placeholder {
font-style: italic;
color: var(--color-highlight);
opacity: 1;
}
}
}
.weather-data {
display: flex;
height: fit-content;
align-items: center;
font-weight: 600;
}
.weather-data span {
margin-right: calc(var(--spacing) * 2);
}
.uptime-data {
display: flex;
flex-direction: column;
align-items: end;
& > div {
display: flex;
align-items: center;
& > span {
margin-right: calc(var(--spacing) * 2);
line-height: var(--leading-condensed);
}
}
}
.uptime-status {
position: relative;
display: flex;
margin-top: auto;
margin-bottom: auto;
width: calc(var(--spacing) * 2);
height: calc(var(--spacing) * 2);
}
.uptime-status > svg {
position: relative;
display: inline-flex;
height: 100%;
width: 100%;
fill: currentColor;
}
.uptime-status > svg:nth-child(1) {
position: absolute;
animation: ping 1s linear infinite;
opacity: 0.8;
}
}
@layer utilities {
.animate-ping {
animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;
}
@keyframes ping {
75%,
100% {
transform: scale(2);
opacity: 0;
}
}
}

View File

@@ -1,195 +0,0 @@
@import "tailwindcss";
@theme {
--color-accent: oklch(57.93% 0.258 294.12);
--color-success: oklch(70.19% 0.158 160.44);
--color-error: oklch(53% 0.251 28.48);
--color-base: oklch(11% 0.007 285);
--color-surface: oklch(19% 0.007 285.66);
--color-overlay: oklch(26% 0.008 285.66);
--color-muted: oklch(63% 0.015 286);
--color-subtle: oklch(72% 0.015 286);
--color-text: oklch(87% 0.015 286);
--color-highlight-sm: oklch(30.67% 0.007 286);
--color-highlight: oklch(39.26% 0.01 286);
--color-highlight-lg: oklch(47.72% 0.011 286);
}
@font-face {
font-family: "Instrument Sans";
src: url("/assets/fonts/InstrumentSans-VariableFont_wdth,wght.woff2")
format("woff2");
font-display: swap;
}
:root {
--default-font-family: "Instrument Sans", ui-sans-serif, system-ui,
sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol",
"Noto Color Emoji";
}
html {
line-height: normal;
color-scheme: dark;
color: var(--color-text);
}
@layer base {
h1,
h2,
h3,
h4,
h5,
h6 {
font-weight: 600;
}
h1 {
font-size: clamp(42px, 10vw, 64px);
}
h2 {
font-size: clamp(30px, 6vw, 36px);
}
h3 {
font-size: 1.25rem;
}
}
button {
cursor: pointer;
}
input:not(.search) {
@apply px-4 py-2 rounded-md w-full bg-surface border border-highlight/70 placeholder:text-highlight text-text focus-visible:outline-none transition-colors duration-300 ease-out overflow-hidden;
&[type="file"] {
@apply p-0 cursor-pointer;
&::file-selector-button {
@apply px-2 py-2 mr-1 bg-highlight text-subtle cursor-pointer;
}
}
}
.link-card {
background: var(--color-overlay);
display: flex;
flex-direction: row;
text-decoration: none;
border-radius: 1rem;
padding: 0.625rem;
align-items: center;
transition-property: box-shadow, transform, translate;
transition-duration: 150ms;
transition-timing-function: cubic-bezier(0.45, 0, 0.55, 1);
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
contain: layout style paint;
&:not(.admin) {
&:hover {
box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1),
0 4px 6px -4px rgb(0 0 0 / 0.1);
transform: translateY(-4px);
}
&:active {
box-shadow: 0 2px 4px -2px rgb(0 0 0 / 0.1);
transform: translateY(2px);
}
}
}
@media (prefers-reduced-motion: reduce) {
.link-card {
transition: none;
&:hover {
transform: none;
}
&:active {
transform: none;
}
}
}
/* Div that holds the image */
.link-card div[data-img-container] {
flex-shrink: 0;
padding-right: 0.5rem;
}
.link-card div[data-img-container] img {
user-select: none;
border-radius: 0.375rem;
aspect-ratio: 1/1;
object-fit: cover;
}
/* Div that holds the text */
.link-card div[data-text-container] {
display: flex;
flex-grow: 1;
flex-direction: column;
row-gap: 1px;
overflow: hidden;
word-break: break-all;
}
.link-card div[data-text-container] p {
color: var(--color-subtle);
white-space: pre-wrap;
border: 1px solid #0000;
min-height: 22px;
}
.new-link-card {
display: flex;
flex-direction: row;
align-items: center;
padding: 0.625rem;
border: 0.125rem dashed var(--color-subtle);
border-radius: 1rem;
transition: box-shadow, transofrm 150ms cubic-bezier(0.45, 0, 0.55, 1);
cursor: pointer;
user-select: none;
&:hover {
text-decoration: underline;
}
}
.categoy-header {
display: flex;
align-items: center;
}
.category-header div[data-img-container] {
@apply shrink-0 relative mr-2 h-full flex items-center justify-center size-8;
}
.categoy-header div[data-img-container] img {
user-select: none;
object-fit: cover;
aspect-ratio: 1/1;
}
.category-header h2 {
text-transform: capitalize;
word-break: break-all;
border-width: 1px;
border-color: #0000;
}
.link-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(min(330px, 100%), 1fr));
gap: 0.5rem;
padding: 0.625rem;
contain: layout style paint;
}

View File

@@ -1,20 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Passport</title>
<link rel="favicon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="preload" as="font" type="font/woff2" crossorigin="anonymous"
href="/assets/fonts/InstrumentSans-VariableFont_wdth,wght.woff2" />
{{{embedFile "assets/tailwind.css"}}}
{{{embedFile "styles/adminUi.css"}}}
</head>
<body class="bg-surface text-text">
{{embed}}
</body>
{{{devContent}}}
</html>

View File

@@ -1,19 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Passport</title>
<link rel="favicon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="preload" as="font" type="font/woff2" crossorigin="anonymous"
href="/assets/fonts/InstrumentSans-VariableFont_wdth,wght.woff2" />
{{{embedFile "assets/tailwind.css"}}}
</head>
<body class="bg-surface text-text">
{{embed}}
</body>
{{{devContent}}}
</html>

View File

@@ -0,0 +1,111 @@
<section class="card-section">
<div>
{{#each Categories}}
<div class="category-header" id="{{this.ID}}_category">
<div>
<img width="32" height="32" draggable="false" alt="{{this.Name}}" src="{{this.Icon}}" />
</div>
<h2 class="capitalize break-all">{{this.Name}}</h2>
{{#if IsAdmin}}
<div>
<div class="action-container">
<button aria-label="Edit category" onclick="editCategory(this)" class="action-button">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<use href="#edit-icon" />
</svg>
</button>
<button aria-label="Delete category" onclick="deleteCategory(this)"
class="text-error action-button">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<use href="#trash-icon" />
</svg>
</button>
</div>
</div>
{{/if}}
</div>
<div class="link-grid">
{{#each this.Links}}
{{#if IsAdmin}}<div data-card id="{{this.ID}}_link" {{else}} <a href="{{this.URL}}" draggable="false"
target="_blank" rel="noreferrer" {{/if}}>
<div>
<img width="64" height="64" draggable="false" src="{{this.Icon}}" alt="{{this.Name}}" />
</div>
<div>
<h3>{{this.Name}}</h3>
<p>{{this.Description}}</p>
</div>
{{#if IsAdmin}}
<div>
<div class="action-container">
<button aria-label="Edit link" onclick="editLink(this)" class="action-button">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<use href="#edit-icon" />
</svg>
</button>
<button aria-label="Delete link" onclick="deleteLink(this)" class="text-error action-button">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<use href="#trash-icon" />
</svg>
</button>
</div>
</div>
{{/if}}
{{#if IsAdmin}}
</div {{else}} </a {{/if}}>
{{else}}
{{#unless IsAdmin}}
<p class="text-subtle">No links here, add one!</p>
{{/unless}}
{{/each}}
{{#if IsAdmin}}
<div onclick="openModal('link', {{this.ID}})" class="new-link-card link-card admin">
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 24 24">
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2" d="M12 5v14m-7-7h14" />
</svg>
<div>
<h3>Add a link</h3>
</div>
</div>
{{/if }}
</div>
{{/each}}
{{#if IsAdmin}}
<div class="add-category-button" id="add-category-button">
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24">
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 5v14m-7-7h14" />
</svg>
<h2 onclick="openModal('category')">
Add a new category
</h2>
</div>
{{/if}}
</div>
</section>
<!-- store the svg icons here so that they can be reused -->
{{#if IsAdmin}}
<div class="hidden">
<svg id="edit-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24"
viewBox="0 0 24 24"><!-- Icon from Tabler Icons by Paweł Kuna - https://github.com/tabler/tabler-icons/blob/master/LICENSE -->
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
<path d="M7 7H6a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2-2v-1" />
<path d="M20.385 6.585a2.1 2.1 0 0 0-2.97-2.97L9 12v3h3zM16 5l3 3" />
</g>
</svg>
<svg id="trash-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24"
viewBox="0 0 24 24"><!-- Icon from Tabler Icons by Paweł Kuna - https://github.com/tabler/tabler-icons/blob/master/LICENSE -->
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M4 7h16m-10 4v6m4-6v6M5 7l1 12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2l1-12M9 7V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v3" />
</svg>
</div>
{{/if}}

View File

@@ -1,7 +1,6 @@
<div id="category-contents" class="hidden"> <div id="category-contents" class="hidden">
<h3>Create A category</h3> <h3>Create A category</h3>
<form id="category-form" action="/api/categories" method="post" <form id="category-form" action="/api/categories" method="post" class="modal-form">
class="flex flex-col gap-y-3 my-2 [&>div]:flex [&>div]:flex-col [&>div]:gap-1">
<div> <div>
<label for="categoryName">Name</label> <label for="categoryName">Name</label>
<input required type="text" name="name" id="categoryName" maxlength="50" /> <input required type="text" name="name" id="categoryName" maxlength="50" />
@@ -10,7 +9,7 @@
<label for="linkIcon">Icon</label> <label for="linkIcon">Icon</label>
<input type="file" name="icon" id="linkIcon" accept=".svg" required /> <input type="file" name="icon" id="linkIcon" accept=".svg" required />
</div> </div>
<button class="px-4 py-2 rounded-md w-full bg-accent text-white border-0" type="submit">Create <button type="submit">Create
category</button> category</button>
</form> </form>
<span id="category-message"></span> <span id="category-message"></span>

View File

@@ -1,13 +1,11 @@
<div id="category-delete-contents" class="hidden text-center"> <div id="category-delete-contents" class="hidden delete-modal">
<h3>Are you sure you want to delete this category?</h3> <h3>Are you sure you want to delete this category?</h3>
<p class="mb-3">You are about to delete the category <strong id="category-name"></strong>. This action cannot be <p>You are about to delete the category <strong id="category-name"></strong>. This action cannot be
undone. undone.
All links associated with this category will also be deleted. Are you sure you want to continue?</p> All links associated with this category will also be deleted. Are you sure you want to continue?</p>
<div class="flex justify-end flex-col gap-y-2"> <div>
<button class="px-4 py-2 rounded-md w-full bg-error text-white border-0" <button onclick="confirmDeleteCategory()">Delete
onclick="confirmDeleteCategory()">Delete
category</button> category</button>
<button class="px-4 py-2 rounded-md w-full bg-overlay border border-highlight text-white" <button onclick="closeModal()">Cancel</button>
onclick="closeModal()">Cancel</button>
</div> </div>
</div> </div>

View File

@@ -1,12 +1,11 @@
<div id="link-delete-contents" class="hidden text-center"> <div id="link-delete-contents" class="hidden delete-modal">
<h3>Are you sure you want to delete this link?</h3> <h3>Are you sure you want to delete this link?</h3>
<p class="mb-3">You are about to delete the link <strong id="link-name"></strong>. This action cannot be undone. Are <p>You are about to delete the link <strong id="link-name"></strong>. This action cannot be undone. Are
you sure you you sure you
want to continue?</p> want to continue?</p>
<div class="flex justify-end flex-col gap-y-2"> <div>
<button class="px-4 py-2 rounded-md w-full bg-error text-white border-0" onclick="confirmDeleteLink()">Delete <button onclick="confirmDeleteLink()">Delete
link</button> link</button>
<button class="px-4 py-2 rounded-md w-full bg-overlay border border-highlight text-white" <button onclick="closeModal()">Cancel</button>
onclick="closeModal()">Cancel</button>
</div> </div>
</div> </div>

View File

@@ -1,7 +1,6 @@
<div id="link-contents" class="hidden"> <div id="link-contents" class="hidden">
<h3>Add A link</h3> <h3>Add A link</h3>
<form id="link-form" action="/api/links" method="post" <form id="link-form" action="/api/links" method="post" class="modal-form">
class="flex flex-col gap-y-3 my-2 [&>div]:flex [&>div]:flex-col [&>div]:gap-1">
<div> <div>
<label for="linkName">Name</label> <label for="linkName">Name</label>
<input required type="text" name="name" id="linkName" maxlength="50" /> <input required type="text" name="name" id="linkName" maxlength="50" />
@@ -18,7 +17,7 @@
<label for="linkIcon">Icon</label> <label for="linkIcon">Icon</label>
<input required type="file" name="icon" id="linkIcon" accept="image/*" /> <input required type="file" name="icon" id="linkIcon" accept="image/*" />
</div> </div>
<button class="px-4 py-2 rounded-md w-full bg-accent text-white border-0" type="submit">Add <button type="submit">Add
link</button> link</button>
</form> </form>
<span id="link-message"></span> <span id="link-message"></span>

View File

@@ -1,208 +1,132 @@
<div id="blur-target" <!DOCTYPE html>
class="transition-[filter] motion-reduce:transition-none ease-[cubic-bezier(0.45,0,0.55,1)] duration-300"> <html lang="en">
<header class="flex w-full p-3">
<a href="/"
class="flex items-center flex-row gap-2 text-white border-b hover:border-transparent justify-center">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20"
viewBox="0 0 24 24"><!-- Icon from Tabler Icons by Paweł Kuna - https://github.com/tabler/tabler-icons/blob/master/LICENSE -->
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
<path d="m9 14l-4-4l4-4" />
<path d="M5 10h11a4 4 0 1 1 0 8h-1" />
</g>
</svg>
Return to home
</a>
</header>
<section class="flex justify-center w-full"> <head>
<div class="w-full sm:w-4/5 p-2.5"> <title>Passport</title>
{{#each Categories}} <link rel="favicon" href="/favicon.ico" />
<div class="flex items-center category-header" id="{{this.ID}}_category"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<div class="category-img" data-img-container> <link rel="preload" as="font" type="font/woff2" crossorigin="anonymous"
<img width="32" height="32" draggable="false" alt="{{this.Name}}" src="{{this.Icon}}" /> href="/assets/fonts/InstrumentSans-VariableFont_wdth,wght.woff2" />
</div> {{{embedFile "assets/styles/adminUi.css"}}}
<h2 data-placeholder="Enter title...">{{~ this.Name ~}}</h2> </head>
<div class="pl-2" data-edit-actions>
<div class="flex flex-row gap-2" data-primary-actions> <body>
<button aria-label="Edit category" onclick="editCategory(this)" class="action-button"> <div id="blur-target">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" <header class="flex w-full p-3">
viewBox="0 0 24 24"><!-- Icon from Tabler Icons by Paweł Kuna - https://github.com/tabler/tabler-icons/blob/master/LICENSE --> <a href="/"
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" class="flex items-center flex-row gap-2 text-white border-b hover:border-transparent justify-center">
stroke-width="2"> <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20"
<path d="M7 7H6a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2-2v-1" /> viewBox="0 0 24 24"><!-- Icon from Tabler Icons by Paweł Kuna - https://github.com/tabler/tabler-icons/blob/master/LICENSE -->
<path d="M20.385 6.585a2.1 2.1 0 0 0-2.97-2.97L9 12v3h3zM16 5l3 3" /> <g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
</g> stroke-width="2">
</svg> <path d="m9 14l-4-4l4-4" />
</button> <path d="M5 10h11a4 4 0 1 1 0 8h-1" />
<button aria-label="Delete category" onclick="deleteCategory(this)" </g>
class="text-error action-button"> </svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" Return to home
viewBox="0 0 24 24"><!-- Icon from Tabler Icons by Paweł Kuna - https://github.com/tabler/tabler-icons/blob/master/LICENSE --> </a>
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" </header>
stroke-width="2"
d="M4 7h16m-10 4v6m4-6v6M5 7l1 12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2l1-12M9 7V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v3" /> {{> 'partials/category-grid' }}
</svg> </div>
</button>
</div> <input type="file" id="icon-upload" accept="image/*" style="display: none;" />
</div> <div id="modal-container" role="dialog" aria-modal="true" class="modal-bg">
<div class="modal">
{{> 'partials/modals/category-form' }}
{{> 'partials/modals/link-form' }}
{{> 'partials/modals/delete-link' }}
{{> 'partials/modals/delete-category' }}
</div>
</div>
<!-- store a blank link card so that if we add a new link we can clone it to make the editing experience easier -->
<div id="template-link-card" class="hidden">
<div>
<img width="64" height="64" draggable="false" />
</div>
<div>
<h3></h3>
<!-- add 2 to the height to account for the border -->
<p></p>
</div>
</div>
<div id="template-category" class="hidden">
<div class="category-header">
<div>
<img width="32" height="32" draggable="false" />
</div> </div>
<div class="link-grid"> <h2></h2>
{{#each this.Links}} </div>
<div id="{{this.ID}}_link" class="link-card relative admin"> <div class="link-grid">
<div class="relative" data-img-container> <div class="new-link-card link-card admin">
<img width="64" height="64" draggable="false" src="{{this.Icon}}" alt="{{this.Name}}" /> <svg class="mr-2" xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 24 24">
</div>
<div data-text-container>
<h3 class="border border-transparent" data-placeholder="Enter title...">
{{~ this.Name ~}}
</h3>
<!-- add 2 to the height to account for the border -->
<p data-placeholder="Enter description...">
{{~ this.Description ~}}
</p>
</div>
<div class="absolute right-1 top-1" data-edit-actions>
<div class="flex flex-row gap-2" data-primary-actions>
<button aria-label="Edit link" onclick="editLink(this)" class="action-button">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"
viewBox="0 0 24 24"><!-- Icon from Tabler Icons by Paweł Kuna - https://github.com/tabler/tabler-icons/blob/master/LICENSE -->
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2">
<path d="M7 7H6a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2-2v-1" />
<path d="M20.385 6.585a2.1 2.1 0 0 0-2.97-2.97L9 12v3h3zM16 5l3 3" />
</g>
</svg>
</button>
<button aria-label="Delete link" onclick="deleteLink(this)"
class="text-error action-button">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"
viewBox="0 0 24 24"><!-- Icon from Tabler Icons by Paweł Kuna - https://github.com/tabler/tabler-icons/blob/master/LICENSE -->
<path fill="none" stroke="currentColor" stroke-linecap="round"
stroke-linejoin="round" stroke-width="2"
d="M4 7h16m-10 4v6m4-6v6M5 7l1 12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2l1-12M9 7V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v3" />
</svg>
</button>
</div>
</div>
</div>
{{/each}}
<div onclick="openModal('link', {{this.ID}})" class="new-link-card">
<svg class="mr-2" xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 24 24">
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2" d="M12 5v14m-7-7h14" />
</svg>
<div>
<h3>Add a link</h3>
</div>
</div>
</div>
{{/each}}
<div class="flex items-center" id="add-category-button">
<svg class="mr-2" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24">
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2" d="M12 5v14m-7-7h14" /> stroke-width="2" d="M12 5v14m-7-7h14" />
</svg> </svg>
<h2 onclick="openModal('category')" class="text-subtle underline decoration-dashed cursor-pointer"> <div>
Add a new category <h3>Add a link</h3>
</h2> </div>
</div>
</div>
</section>
</div>
<input type="file" id="icon-upload" accept="image/*" style="display: none;" />
<div id="modal-container" role="dialog" aria-modal="true"
class="modal-bg fixed top-0 left-0 bottom-0 right-0 bg-black/45 justify-center items-center hidden">
<div class="bg-overlay rounded-xl overflow-hidden w-full p-4 modal max-w-sm">
{{> 'partials/modals/category-form' }}
{{> 'partials/modals/link-form' }}
{{> 'partials/modals/delete-link' }}
{{> 'partials/modals/delete-category' }}
</div>
</div>
<!-- store a blank link card so that if we add a new link we can clone it to make the editing experience easier -->
<div id="template-link-card" class="hidden">
<div class="relative" data-img-container>
<img width="64" height="64" draggable="false" />
</div>
<div class="flex-grow flex flex-col gap-y-px overflow-hidden" data-text-container>
<h3 class="border border-transparent"></h3>
<!-- add 2 to the height to account for the border -->
<p class="min-h-[22px] border border-transparent"></p>
</div>
</div>
<div id="template-category" class="hidden">
<div class="flex items-center category-header">
<div class="category-img" data-img-container>
<img width="32" height="32" draggable="false" />
</div>
<h2></h2>
</div>
<div class="link-grid">
<div class="new-link-card">
<svg class="mr-2" xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 24 24">
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 5v14m-7-7h14" />
</svg>
<div>
<h3>Add a link</h3>
</div> </div>
</div> </div>
</div> </div>
</div>
<div id="template-edit-actions" class="hidden" data-edit-actions> <div id="template-edit-actions" class="hidden">
<div class="flex flex-row gap-2" data-primary-actions> <div class="flex flex-row gap-2">
<button class="action-button"> <button class="action-button">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"
viewBox="0 0 24 24"><!-- Icon from Tabler Icons by Paweł Kuna - https://github.com/tabler/tabler-icons/blob/master/LICENSE --> viewBox="0 0 24 24"><!-- Icon from Tabler Icons by Paweł Kuna - https://github.com/tabler/tabler-icons/blob/master/LICENSE -->
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"> <g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
<path d="M7 7H6a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2-2v-1" /> stroke-width="2">
<path d="M20.385 6.585a2.1 2.1 0 0 0-2.97-2.97L9 12v3h3zM16 5l3 3" /> <path d="M7 7H6a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2-2v-1" />
</g> <path d="M20.385 6.585a2.1 2.1 0 0 0-2.97-2.97L9 12v3h3zM16 5l3 3" />
</svg> </g>
</button> </svg>
<button class="text-error action-button"> </button>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" <button class="text-error action-button">
viewBox="0 0 24 24"><!-- Icon from Tabler Icons by Paweł Kuna - https://github.com/tabler/tabler-icons/blob/master/LICENSE --> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24"><!-- Icon from Tabler Icons by Paweł Kuna - https://github.com/tabler/tabler-icons/blob/master/LICENSE -->
d="M4 7h16m-10 4v6m4-6v6M5 7l1 12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2l1-12M9 7V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v3" /> <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
</svg> stroke-width="2"
</button> d="M4 7h16m-10 4v6m4-6v6M5 7l1 12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2l1-12M9 7V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v3" />
</svg>
</button>
</div>
</div> </div>
</div>
<div id="teleport-storage" class="absolute -top-full -left-full hidden"> <div id="teleport-storage" class="absolute -top-full -left-full hidden">
<!-- These are the elements that appear when the user enters edit mode, they allow for the cancelation/confirmation of the edit --> <!-- These are the elements that appear when the user enters edit mode, they allow for the cancelation/confirmation of the edit -->
<div class="flex flex-row gap-2" data-confirm-actions id="confirm-actions"> <div class="action-container" id="confirm-actions">
<button class="action-button text-success" onclick="confirmEdit()"> <button class="action-button text-success" onclick="confirmEdit()">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"
viewBox="0 0 24 24"><!-- Icon from Tabler Icons by Paweł Kuna - https://github.com/tabler/tabler-icons/blob/master/LICENSE -->
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2" d="m5 12l5 5L20 7" />
</svg>
</button>
<button class="action-button text-error" onclick="cancelEdit()">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"
viewBox="0 0 24 24"><!-- Icon from Tabler Icons by Paweł Kuna - https://github.com/tabler/tabler-icons/blob/master/LICENSE -->
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2" d="M3 12a9 9 0 1 0 18 0a9 9 0 1 0-18 0m15.364-6.364L5.636 18.364" />
</svg>
</button>
</div>
<!-- This is the element that appears on top of the icon when the user is editing it that allows for changing the icon -->
<button id="select-icon-button" onclick="selectIcon()" class="select-icon-button" draggable="false">
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28"
viewBox="0 0 24 24"><!-- Icon from Tabler Icons by Paweł Kuna - https://github.com/tabler/tabler-icons/blob/master/LICENSE --> viewBox="0 0 24 24"><!-- Icon from Tabler Icons by Paweł Kuna - https://github.com/tabler/tabler-icons/blob/master/LICENSE -->
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="m5 12l5 5L20 7" /> d="M4 17v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2M7 9l5-5l5 5m-5-5v12" />
</svg>
</button>
<button class="action-button text-error" onclick="cancelEdit()">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"
viewBox="0 0 24 24"><!-- Icon from Tabler Icons by Paweł Kuna - https://github.com/tabler/tabler-icons/blob/master/LICENSE -->
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M3 12a9 9 0 1 0 18 0a9 9 0 1 0-18 0m15.364-6.364L5.636 18.364" />
</svg> </svg>
</button> </button>
</div> </div>
<!-- This is the element that appears on top of the icon when the user is editing it that allows for changing the icon --> {{{embedFile "scripts/admin.js"}}}
<button id="select-icon-button" onclick="selectIcon()" </body>
class="flex absolute inset-0 bg-highlight/80 rounded-md text-base items-center justify-center"
draggable="false">
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28"
viewBox="0 0 24 24"><!-- Icon from Tabler Icons by Paweł Kuna - https://github.com/tabler/tabler-icons/blob/master/LICENSE -->
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M4 17v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2M7 9l5-5l5 5m-5-5v12" />
</svg>
</button>
</div>
{{{embedFile "scripts/admin.js"}}} {{{devContent}}}
</html>

View File

@@ -1,20 +1,31 @@
<main class="flex justify-center items-center h-screen relative bg-base"> <!DOCTYPE html>
<div class="flex bg-surface rounded-xl overflow-hidden"> <html lang="en">
<img src="/assets/leaves.webp" class="h-96 w-64 object-cover" />
<div class="flex flex-col p-4 text-center"> <head>
<h2 class="text-2xl"> <title>Passport</title>
<link rel="favicon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="preload" as="font" type="font/woff2" crossorigin="anonymous"
href="/assets/fonts/InstrumentSans-VariableFont_wdth,wght.woff2" />
{{{embedFile "assets/styles/login.css"}}}
</head>
<body>
<main class="login-container">
<img src="/assets/leaves.webp" />
<div>
<h2>
Login Login
</h2> </h2>
<form action="/admin/login" method="post" class="flex flex-col gap-y-3 my-2"> <form action="/admin/login" method="post" class="login-form">
<input type="text" name="username" placeholder="Username" /> <input type="text" name="username" placeholder="Username" />
<input type="password" name="password" placeholder="Password" /> <input type="password" name="password" placeholder="Password" />
<button class="px-4 py-2 rounded-md w-full bg-accent text-white border-0" type="submit">Login</button> <button class="px-4 py-2 rounded-md w-full bg-accent text-white border-0" type="submit">Login</button>
</form> </form>
<span id="message"></span> <span id="message"></span>
</div> </div>
</div> </main>
</main> </body>
<script> <script>
let message = document.getElementById("message"); let message = document.getElementById("message");
let form = document.querySelector("form"); let form = document.querySelector("form");
@@ -43,3 +54,6 @@
message.innerText = (await res.json()).message; message.innerText = (await res.json()).message;
}); });
</script> </script>
{{{devContent}}}
</html>

View File

@@ -1,100 +1,88 @@
<main class="grid grid-rows-3 grid-cols-[1fr] justify-center items-center h-screen bg-base"> <!DOCTYPE html>
<div class="flex h-full p-2.5 justify-between"> <html lang="en">
<div>
<head>
<title>Passport</title>
<link rel="favicon" href="/favicon.ico" />
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="preload" as="font" type="font/woff2" crossorigin="anonymous"
href="/assets/fonts/InstrumentSans-VariableFont_wdth,wght.woff2" />
{{{embedFile "assets/styles/main.css"}}}
</head>
<body>
<main class="hero">
<div class="glance-container">
{{#if WeatherData}} {{#if WeatherData}}
<div class="text-subtle flex items-center"> <div class="weather-data">
<span class="mr-2 flex items-center"> <span>
{{{WeatherData.Icon}}} {{{WeatherData.Icon}}}
</span> </span>
<div class="font-semibold"> <div class="font-semibold">
<p>{{WeatherData.Temp}}°C</p> <p class="leading-condensed">{{WeatherData.Temp}}°C</p>
<p>{{WeatherData.Desc}}</p> <p class="leading-condensed">{{WeatherData.Desc}}</p>
</div> </div>
</div> </div>
{{/if}} {{/if}}
</div>
<div>
{{#if UptimeData}} {{#if UptimeData}}
<div class="text-subtle flex items-end flex-col"> <div class="uptime-data">
<svg width="0" height="0" style="display:none">
<defs>
<circle id="status-dot" cx="5" cy="5" r="5" />
</defs>
</svg>
{{#each UptimeData}} {{#each UptimeData}}
<div class="flex items-center"> <div>
<span class="mr-2 flex items-center"> <span>
{{{this.FriendlyName}}} {{this.FriendlyName}}
</span> </span>
<div class="relative my-auto size-2"> <div class="uptime-status">
<div class="relative my-auto size-2 flex-shrink-0 flex-grow-0"> <svg viewBox="0 0 10 10">
<svg class="absolute w-full h-full animate-ping" viewBox="0 0 10 10"> <use href="#status-dot"
<circle cx="5" cy="5" r="5" class="{{#if (eq this.Status 2)}}text-success{{else}}text-error{{/if}}">
class="fill-current {{#if (eq this.Status 2)}} text-success {{else}} text-error {{/if}}"> </use>
</circle> </svg>
</svg> <svg viewBox="0 0 10 10">
<svg class="relative w-full h-full" viewBox="0 0 10 10"> <use href="#status-dot"
<circle cx="5" cy="5" r="5" class="{{#if (eq this.Status 2)}}text-success{{else}}text-error{{/if}}">
class="fill-current {{#if (eq this.Status 2)}} text-success {{else}} text-error {{/if}}"> </use>
</circle> </svg>
</svg>
</div>
</div> </div>
</div> </div>
{{/each}} {{/each}}
</div> </div>
{{/if}} {{/if}}
</div> </div>
</div> <div class="primary-hero-container">
<div class="row-start-2 flex flex-col items-center w-full px-6"> <div>
<div class="flex items-center pb-2.5"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 100 100">
<svg class="mr-3 aspect-square w-[clamp(42px,10vw,60px)]" viewBox="0 0 100 100" fill="none" <rect width="70" height="47" x="12.1" y="24.8" fill="url(#a)" rx="12"
xmlns="http://www.w3.org/2000/svg"> transform="rotate(14.6 12.1 24.8)" />
<rect x="12.1483" y="24.7693" width="70" height="47" rx="12" transform="rotate(14.63 12.1483 24.7693)" <path fill="url(#b)" fill-rule="evenodd"
fill="url(#paint0_linear_20_10)" /> d="M52.7 13.5a12 12 0 0 0-16.2 5l-3.7 7 35.6 9.3a17 17 0 0 1 12.2 20.7l-5.8 22.3a12 12 0 0 0 12.7-6.2l10.8-20.3a12 12 0 0 0-5-16.2z"
<path fill-rule="evenodd" clip-rule="evenodd" clip-rule="evenodd" />
d="M52.7386 13.4812C46.8869 10.3698 39.6209 12.5913 36.5096 18.4429L32.7819 25.4537L68.4322 34.7599C77.5166 37.1313 82.9586 46.418 80.5872 55.5025L74.7779 77.7567C74.7752 77.7674 74.7724 77.778 74.7696 77.7886C79.7728 78.7022 85.0029 76.3441 87.518 71.6138L98.3159 51.306C101.427 45.4543 99.2058 38.1883 93.3542 35.0769L52.7386 13.4812Z" <defs>
fill="url(#paint1_linear_20_10)" /> <linearGradient id="a" x1="12.4" x2="82.5" y1="44.9" y2="48.3" gradientUnits="userSpaceOnUse">
<defs> <stop stop-color="#f0389b" />
<linearGradient id="paint0_linear_20_10" x1="12.359" y1="44.8681" x2="82.491" y2="48.2607" <stop offset="1" stop-color="#eee740" />
gradientUnits="userSpaceOnUse"> </linearGradient>
<stop stop-color="#F0389B" /> <linearGradient id="b" x1="33.9" x2="94.2" y1="25.7" y2="61.6" gradientUnits="userSpaceOnUse">
<stop offset="1" stop-color="#EEE740" /> <stop stop-color="#aa38f0" />
</linearGradient> <stop offset="1" stop-color="#ee406a" />
<linearGradient id="paint1_linear_20_10" x1="33.8935" y1="25.6926" x2="94.2236" y2="61.6131" </linearGradient>
gradientUnits="userSpaceOnUse"> </defs>
<stop stop-color="#AA38F0" /> </svg>
<stop offset="1" stop-color="#EE406A" /> <h1>Passport</h1>
</linearGradient> </div>
</defs> <form action="{{ SearchProviderURL }}" method="GET">
</svg> <input name="{{ SearchParam }}" aria-label="Search bar" placeholder="Search..." />
<h1>Passport</h1> </form>
</div> </div>
<form class="w-full max-w-3xl" action="{{ SearchProviderURL }}" method="GET"> </main>
<input name="{{ SearchParam }}" aria-label="Search bar" {{> 'partials/category-grid' }}
class="w-full bg-surface border border-highlight-sm/70 rounded-full px-3 py-1 text-white h-7 focus-visible:outline-none placeholder:italic placeholder:text-highlight search" </body>
placeholder="Search..." />
</form> {{{devContent}}}
</div>
</main> </html>
<section class="flex justify-center w-full bg-surface">
<div class="w-full sm:w-4/5 p-2.5">
{{#each Categories}}
<div class="flex items-center mt-2 first:mt-0">
<img class="object-contain mr-2 select-none size-8" width="32" height="32" draggable="false"
alt="{{this.Name}}" src="{{this.Icon}}" />
<h2 class="capitalize break-all">{{this.Name}}</h2>
</div>
<div class="link-grid">
{{#each this.Links}}
<a href="{{this.URL}}" class="link-card" draggable="false" target="_blank" rel="noopener noreferrer">
<div data-img-container>
<img width="64" height="64" draggable="false" src="{{this.Icon}}" alt="{{this.Name}}" />
</div>
<div data-text-container>
<h3>{{this.Name}}</h3>
<p class="min-h-5">{{this.Description}}</p>
</div>
</a>
{{else}}
<p class="text-subtle">No links here, add one!</p>
{{/each}}
</div>
{{/each}}
</div>
</section>

View File

@@ -1,6 +1,6 @@
{ {
"name": "passport", "name": "passport",
"version": "0.3.2", "version": "0.3.3",
"description": "Passport is a simple, lightweight, and fast dashboard/new tab page for your browser.", "description": "Passport is a simple, lightweight, and fast dashboard/new tab page for your browser.",
"author": "juls0730", "author": "juls0730",
"license": "BSL-1.0", "license": "BSL-1.0",
@@ -10,9 +10,14 @@
"url": "https://github.com/juls0730/passport.git" "url": "https://github.com/juls0730/passport.git"
}, },
"scripts": { "scripts": {
"dev": "go generate ./src/; PASSPORT_DEV_MODE=true go run src/main.go", "generate": "go generate ./src/",
"build": "go generate ./src/ && go build -tags netgo,prod -ldflags=\"-w -s\" -o passport src/main.go"
"dev": "zqdgr generate; PASSPORT_DEV_MODE=true go run src/main.go",
"build": "zqdgr generate && go build -tags netgo,prod -ldflags=\"-w -s\" -o passport src/main.go",
"build:amd64": "zqdgr generate && GOOS=linux GOARCH=amd64 go build -tags netgo,prod -ldflags=\"-w -s\" -o passport src/main.go",
"build:arm64": "zqdgr generate && GOOS=linux GOARCH=arm64 go build -tags netgo,prod -ldflags=\"-w -s\" -o passport src/main.go"
}, },
"pattern": "src/**/*.{go,hbs,js,css,scss,svg,png,jpg,jpeg,webp,woff2,ico,webp}", "pattern": "src/**/*.{go,hbs,css,js,svg,png,jpg,jpeg,webp,woff2,ico,webp}",
"excluded_files": ["src/assets/styles"],
"shutdown_signal": "SIGINT" "shutdown_signal": "SIGINT"
} }