Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
0ba3b44af4
|
|||
|
3ffd439f88
|
|||
|
68284bc963
|
|||
|
f6ffc90ec2
|
|||
|
75fe60b4c9
|
|||
|
01a147d2d3
|
8
.github/workflows/docker-publish.yml
vendored
8
.github/workflows/docker-publish.yml
vendored
@@ -27,6 +27,13 @@ jobs:
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ env.OCI_TOKEN }}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Extract Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
@@ -40,3 +47,4 @@ jobs:
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -1,8 +1,12 @@
|
||||
passport
|
||||
passport-*
|
||||
.env
|
||||
passport.db*
|
||||
public
|
||||
zqdgr
|
||||
|
||||
# compiled via go prepare
|
||||
src/assets/tailwind.css
|
||||
src/assets/styles/*
|
||||
src/assets/tailwind.css
|
||||
|
||||
node_modules
|
||||
19
.prettierrc
Normal file
19
.prettierrc
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"tabWidth": 4,
|
||||
"useTabs": false,
|
||||
"singleQuote": false,
|
||||
"semi": true,
|
||||
"trailingComma": "es5",
|
||||
"bracketSpacing": true,
|
||||
"bracketSameLine": false,
|
||||
"arrowParens": "always",
|
||||
"endOfLine": "lf",
|
||||
"printWidth": 80,
|
||||
"proseWrap": "preserve",
|
||||
"quoteProps": "as-needed",
|
||||
"requirePragma": false,
|
||||
"embeddedLanguageFormatting": "auto",
|
||||
"vueIndentScriptAndStyle": false,
|
||||
"htmlWhitespaceSensitivity": "css",
|
||||
"insertPragma": false
|
||||
}
|
||||
12
Dockerfile
12
Dockerfile
@@ -1,25 +1,25 @@
|
||||
FROM golang:1.25 AS builder
|
||||
|
||||
# build dependencies
|
||||
RUN apt update && apt install -y upx
|
||||
RUN apt update && apt install -y upx unzip
|
||||
|
||||
RUN curl -sLO https://github.com/tailwindlabs/tailwindcss/releases/download/v4.1.13/tailwindcss-linux-x64
|
||||
RUN chmod +x tailwindcss-linux-x64
|
||||
RUN mv tailwindcss-linux-x64 /usr/local/bin/tailwindcss
|
||||
RUN curl -fsSL https://bun.com/install | BUN_INSTALL=/usr bash
|
||||
|
||||
|
||||
RUN go install github.com/juls0730/zqdgr@latest
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
ENV CGO_ENABLED=0 GOOS=linux GOARCH=amd64
|
||||
ARG TARGETARCH
|
||||
ENV CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH}
|
||||
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
COPY . .
|
||||
|
||||
RUN bun install
|
||||
|
||||
RUN zqdgr build
|
||||
RUN upx passport
|
||||
|
||||
# ---- Runtime Stage ----
|
||||
FROM gcr.io/distroless/static-debian12 AS runner
|
||||
|
||||
35
README.md
35
README.md
@@ -19,7 +19,7 @@ Passport is a simple, fast, and lightweight web dashboard/new tab replacement.
|
||||
|
||||
### 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
|
||||
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
|
||||
go install github.com/juls0730/zqdgr@latest
|
||||
|
||||
curl -sLO https://github.com/tailwindlabs/tailwindcss/releases/download/v4.1.13/tailwindcss-linux-x64
|
||||
chmod +x tailwindcss-linux-x64
|
||||
mv tailwindcss-linux-x64 /usr/local/bin/tailwindcss
|
||||
# install bun
|
||||
curl -fsSL https://bun.sh/install | bash
|
||||
|
||||
# install css build deps
|
||||
bun install
|
||||
|
||||
# you may also have to install sqlite3...
|
||||
```
|
||||
@@ -70,23 +72,24 @@ You can then run the binary.
|
||||
|
||||
The weather integration is optional, and will be enabled automatically if you provide an API key. The following only applies if you are using the OpenWeatherMap integration.
|
||||
|
||||
| Environment Variable | Description | Required | Default |
|
||||
| ------------------------- | ------------------------------------------------------------------------- | -------- | -------------- |
|
||||
| `WEATHER_PROVIDER` | The weather provider to use, currently only `openweathermap` is supported | false | openweathermap |
|
||||
| `WEATHER_API_KEY` | The OpenWeather API key | true | |
|
||||
| `WEATHER_TEMP_UNITS` | The temperature units to use, either `metric` or `imperial` | false | metric |
|
||||
| `WEATHER_LAT` | The latitude of your location | true | |
|
||||
| `WEATHER_LON` | The longitude of your location | true | |
|
||||
| `WEATHER_UPDATE_INTERVAL` | The interval in minutes to update the weather data | false | 15 |
|
||||
| Environment Variable | Description | Required | Default |
|
||||
| -------------------------- | ------------------------------------------------------------------------- | -------- | -------------- |
|
||||
| `PASSPORT_WEATHER_API_KEY` | The OpenWeather API key | true | |
|
||||
| `WEATHER_PROVIDER` | The weather provider to use, currently only `openweathermap` is supported | false | openweathermap |
|
||||
| `WEATHER_TEMP_UNITS` | The temperature units to use, either `metric` or `imperial` | false | metric |
|
||||
| `WEATHER_LAT` | The latitude of your location | true | |
|
||||
| `WEATHER_LON` | The longitude of your location | true | |
|
||||
| `WEATHER_UPDATE_INTERVAL` | The interval in minutes to update the weather data | false | 15 |
|
||||
|
||||
#### Uptime configuration
|
||||
|
||||
The uptime integration is optional, and will be enabled automatically if you provide an API key. The following only applies if you are using the UptimeRobot integration.
|
||||
|
||||
| Environment Variable | Description | Required | Default |
|
||||
| ------------------------ | ------------------------------------------------- | -------- | ------- |
|
||||
| `UPTIME_API_KEY` | The UptimeRobot API key | true | |
|
||||
| `UPTIME_UPDATE_INTERVAL` | The interval in seconds to update the uptime data | false | 300 |
|
||||
| Environment Variable | Description | Required | Default |
|
||||
| ------------------------- | ------------------------------------------------------------------ | -------- | ----------- |
|
||||
| `UPTIME_PROVIDER` | The uptime provider to use, either `uptimerobot` or `betteruptime` | false | uptimerobot |
|
||||
| `PASSPORT_UPTIME_API_KEY` | The UptimeRobot API key | true | |
|
||||
| `UPTIME_UPDATE_INTERVAL` | The interval in seconds to update the uptime data | false | 300 |
|
||||
|
||||
### Adding links and categories
|
||||
|
||||
|
||||
530
bun.lock
Normal file
530
bun.lock
Normal file
@@ -0,0 +1,530 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "passport-css-compiler",
|
||||
"devDependencies": {
|
||||
"@fullhuman/postcss-purgecss": "^7.0.2",
|
||||
"baseline-browser-mapping": "^2.9.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.9.2", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-PxSsosKQjI38iXkmb3d0Y32efqyA0uW4s41u4IVBsLlWLhCiYNpH/AfNOVWRqCQBlD8TFJTz6OUWNd4DFJCnmw=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"browserslist/baseline-browser-mapping": ["baseline-browser-mapping@2.8.10", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-uLfgBi+7IBNay8ECBO2mVMGZAc1VgZWEChxm4lv+TobGdG82LnXMjuNGo/BSSZZL4UmkWhxEHP2f5ziLNwGWMA=="],
|
||||
|
||||
"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=="],
|
||||
}
|
||||
}
|
||||
3
go.mod
3
go.mod
@@ -4,6 +4,7 @@ go 1.25.0
|
||||
|
||||
require (
|
||||
github.com/HugoSmits86/nativewebp v1.2.0
|
||||
github.com/NarmadaWeb/gonify/v3 v3.0.0-beta
|
||||
github.com/caarlos0/env/v11 v11.3.1
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd
|
||||
@@ -19,6 +20,7 @@ require (
|
||||
github.com/philhofer/fwd v1.2.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/sirupsen/logrus v1.8.1 // indirect
|
||||
github.com/tdewolff/parse/v2 v2.8.3 // indirect
|
||||
github.com/tinylib/msgp v1.4.0 // indirect
|
||||
golang.org/x/crypto v0.42.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect
|
||||
@@ -44,6 +46,7 @@ require (
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/tdewolff/minify/v2 v2.24.3 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.66.0 // indirect
|
||||
golang.org/x/sys v0.36.0 // indirect
|
||||
|
||||
8
go.sum
8
go.sum
@@ -1,5 +1,7 @@
|
||||
github.com/HugoSmits86/nativewebp v1.2.0 h1:XJtXeTg7FsOi9VB1elQYZy3n6VjYLqofSr3gGRLUOp4=
|
||||
github.com/HugoSmits86/nativewebp v1.2.0/go.mod h1:YNQuWenlVmSUUASVNhTDwf4d7FwYQGbGhklC8p72Vr8=
|
||||
github.com/NarmadaWeb/gonify/v3 v3.0.0-beta h1:tNj6Rq9S3UUnF2800h6Ns7wmx+q7MwoZBVD24fPCSlo=
|
||||
github.com/NarmadaWeb/gonify/v3 v3.0.0-beta/go.mod h1:AoLhZCGC/9XGqOE+0amArp/dFIZSfZSvbyPI/IbQ7Q0=
|
||||
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
|
||||
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||
github.com/caarlos0/env/v11 v11.3.1 h1:cArPWC15hWmEt+gWk7YBi7lEXTXCvpaSdCiZE2X5mCA=
|
||||
@@ -64,6 +66,12 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.11.0 h1:ib4sjIrwZKxE5u/Japgo/7SJV3PvgjGiRNAvTVGqQl8=
|
||||
github.com/stretchr/testify v1.11.0/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/tdewolff/minify/v2 v2.24.3 h1:BaKgWSFLKbKDiUskbeRgbe2n5d1Ci1x3cN/eXna8zOA=
|
||||
github.com/tdewolff/minify/v2 v2.24.3/go.mod h1:1JrCtoZXaDbqioQZfk3Jdmr0GPJKiU7c1Apmb+7tCeE=
|
||||
github.com/tdewolff/parse/v2 v2.8.3 h1:5VbvtJ83cfb289A1HzRA9sf02iT8YyUwN84ezjkdY1I=
|
||||
github.com/tdewolff/parse/v2 v2.8.3/go.mod h1:Hwlni2tiVNKyzR1o6nUs4FOF07URA+JLBLd6dlIXYqo=
|
||||
github.com/tdewolff/test v1.0.11 h1:FdLbwQVHxqG16SlkGveC0JVyrJN62COWTRyUFzfbtBE=
|
||||
github.com/tdewolff/test v1.0.11/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
|
||||
github.com/tinylib/msgp v1.4.0 h1:SYOeDRiydzOw9kSiwdYp9UcBgPFtLU2WDHaJXyHruf8=
|
||||
github.com/tinylib/msgp v1.4.0/go.mod h1:cvjFkb4RiC8qSBOPMGPSzSAx47nAsfhLVTCZZNuHv5o=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
|
||||
19
package.json
Normal file
19
package.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"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",
|
||||
"baseline-browser-mapping": "^2.9.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
22
postcss.config.js
Normal 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,
|
||||
],
|
||||
};
|
||||
Binary file not shown.
158
src/main.go
158
src/main.go
@@ -1,4 +1,4 @@
|
||||
//go:generate tailwindcss -i styles/main.scss -o assets/tailwind.css --minify
|
||||
//go:generate bun run build
|
||||
|
||||
package main
|
||||
|
||||
@@ -26,9 +26,11 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/HugoSmits86/nativewebp"
|
||||
"github.com/NarmadaWeb/gonify/v3"
|
||||
"github.com/caarlos0/env/v11"
|
||||
"github.com/disintegration/imaging"
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"github.com/gofiber/fiber/v3/middleware/compress"
|
||||
"github.com/gofiber/fiber/v3/middleware/helmet"
|
||||
"github.com/gofiber/fiber/v3/middleware/static"
|
||||
"github.com/gofiber/template/handlebars/v2"
|
||||
@@ -42,7 +44,7 @@ import (
|
||||
_ "modernc.org/sqlite"
|
||||
)
|
||||
|
||||
//go:embed assets/** templates/** schema.sql
|
||||
//go:embed assets/** templates/** schema.sql scripts/**.js
|
||||
var embeddedAssets embed.FS
|
||||
|
||||
var devContent = `<script>
|
||||
@@ -78,7 +80,15 @@ socket.addEventListener('message', (event) => {
|
||||
setTimeout(testPage, 150);
|
||||
}
|
||||
});
|
||||
</script>`
|
||||
</script>
|
||||
<style>
|
||||
html {
|
||||
outline-color: yellow;
|
||||
outline-width: 5px;
|
||||
outline-style: dashed;
|
||||
outline-offset: -5px;
|
||||
}
|
||||
</style>`
|
||||
|
||||
var (
|
||||
insertCategoryStmt *sql.Stmt
|
||||
@@ -277,7 +287,19 @@ func CropToCenter(img image.Image, outputSize int) (image.Image, error) {
|
||||
return outputImg, nil
|
||||
}
|
||||
|
||||
func UploadFile(file *multipart.FileHeader, fileName, contentType string, c fiber.Ctx) (string, error) {
|
||||
func UploadFile(file *multipart.FileHeader, contentType string, c fiber.Ctx) (string, error) {
|
||||
fileId, err := uuid.NewV7()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
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()
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -298,6 +320,10 @@ func UploadFile(file *multipart.FileHeader, fileName, contentType string, c fibe
|
||||
return "", errors.New("unsupported file type")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if contentType != "image/svg+xml" {
|
||||
off, err := srcFile.Seek(0, io.SeekStart)
|
||||
if err != nil {
|
||||
@@ -312,25 +338,23 @@ func UploadFile(file *multipart.FileHeader, fileName, contentType string, c fibe
|
||||
// if there *is* exif, parse it
|
||||
if err == nil {
|
||||
tag, err := x.Get(exif.Orientation)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get orientation: %v", err)
|
||||
}
|
||||
if err == nil {
|
||||
if tag.Count == 1 && tag.Format() == tiff.IntVal {
|
||||
orientation, err := tag.Int(0)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get orientation: %v", err)
|
||||
}
|
||||
|
||||
if tag.Count == 1 && tag.Format() == tiff.IntVal {
|
||||
orientation, err := tag.Int(0)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get orientation: %v", err)
|
||||
}
|
||||
slog.Debug("Orientation tag found", "orientation", orientation)
|
||||
|
||||
slog.Debug("Orientation tag found", "orientation", orientation)
|
||||
|
||||
switch orientation {
|
||||
case 3:
|
||||
img = imaging.Rotate180(img)
|
||||
case 6:
|
||||
img = imaging.Rotate270(img)
|
||||
case 8:
|
||||
img = imaging.Rotate90(img)
|
||||
switch orientation {
|
||||
case 3:
|
||||
img = imaging.Rotate180(img)
|
||||
case 6:
|
||||
img = imaging.Rotate270(img)
|
||||
case 8:
|
||||
img = imaging.Rotate90(img)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -699,16 +723,24 @@ func main() {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
css, err := fs.ReadFile(embeddedAssets, "assets/tailwind.css")
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
engine := handlebars.NewFileSystem(http.FS(templatesDir), ".hbs")
|
||||
|
||||
engine.AddFunc("inlineCSS", func() string {
|
||||
return string(css)
|
||||
engine.AddFunc("embedFile", func(fileToEmbed string) string {
|
||||
content, err := fs.ReadFile(embeddedAssets, fileToEmbed)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
fileExtension := filepath.Ext(fileToEmbed)
|
||||
|
||||
switch fileExtension {
|
||||
case ".js":
|
||||
return fmt.Sprintf("<script>%s</script>", content)
|
||||
case ".css":
|
||||
return fmt.Sprintf("<style>%s</style>", content)
|
||||
default:
|
||||
return string(content)
|
||||
}
|
||||
})
|
||||
|
||||
engine.AddFunc("devContent", func() string {
|
||||
@@ -718,10 +750,6 @@ func main() {
|
||||
return ""
|
||||
})
|
||||
|
||||
engine.AddFunc("eq", func(a, b any) bool {
|
||||
return a == b
|
||||
})
|
||||
|
||||
router := fiber.New(fiber.Config{
|
||||
Views: engine,
|
||||
})
|
||||
@@ -733,6 +761,17 @@ func main() {
|
||||
return c.Redirect().To("/assets/favicon.ico")
|
||||
})
|
||||
|
||||
router.Use(compress.New(compress.Config{
|
||||
Level: compress.LevelBestSpeed,
|
||||
}))
|
||||
|
||||
router.Use(gonify.New(gonify.Config{
|
||||
MinifySVG: !app.DevMode,
|
||||
MinifyCSS: !app.DevMode,
|
||||
MinifyJS: !app.DevMode,
|
||||
MinifyHTML: !app.DevMode,
|
||||
}))
|
||||
|
||||
router.Use("/", static.New("./public", static.Config{
|
||||
Browse: false,
|
||||
MaxAge: 31536000,
|
||||
@@ -740,10 +779,13 @@ func main() {
|
||||
|
||||
router.Use("/assets", static.New("", static.Config{
|
||||
FS: assetsDir,
|
||||
Browse: false,
|
||||
MaxAge: 31536000,
|
||||
}))
|
||||
|
||||
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{
|
||||
"SearchProviderURL": app.Config.SearchProvider.URL,
|
||||
"SearchParam": app.Config.SearchProvider.Query,
|
||||
@@ -764,7 +806,7 @@ func main() {
|
||||
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))
|
||||
@@ -774,7 +816,7 @@ func main() {
|
||||
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 {
|
||||
@@ -823,7 +865,8 @@ func main() {
|
||||
|
||||
return c.Render("views/admin/index", fiber.Map{
|
||||
"Categories": app.CategoryManager.GetCategories(),
|
||||
}, "layouts/main")
|
||||
"IsAdmin": true,
|
||||
})
|
||||
})
|
||||
|
||||
api := router.Group("/api")
|
||||
@@ -880,9 +923,7 @@ func main() {
|
||||
})
|
||||
}
|
||||
|
||||
filename := fmt.Sprintf("%d_%s.svg", time.Now().Unix(), strings.ReplaceAll(req.Name[:min(10, len(req.Name))], " ", "_"))
|
||||
|
||||
iconPath, err := UploadFile(file, filename, contentType, c)
|
||||
iconPath, err := UploadFile(file, contentType, c)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
"message": "Failed to upload file, please try again!",
|
||||
@@ -975,13 +1016,16 @@ func main() {
|
||||
})
|
||||
}
|
||||
|
||||
filename := fmt.Sprintf("%d_%s.webp", time.Now().Unix(), strings.ReplaceAll(req.Name[:min(10, len(req.Name))], " ", "_"))
|
||||
|
||||
iconPath, err := UploadFile(file, filename, contentType, c)
|
||||
iconPath, err := UploadFile(file, contentType, c)
|
||||
if err != nil {
|
||||
slog.Error("Failed to upload file", "error", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
"message": "Failed to upload file, please try again!",
|
||||
status := fiber.StatusInternalServerError
|
||||
if strings.Contains(err.Error(), "unsupported file type") {
|
||||
status = fiber.StatusBadRequest
|
||||
}
|
||||
|
||||
return c.Status(status).JSON(fiber.Map{
|
||||
"message": "Failed to upload file: " + err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1069,13 +1113,16 @@ func main() {
|
||||
|
||||
oldIconPath := category.Icon
|
||||
|
||||
filename := fmt.Sprintf("%d_%s.svg", time.Now().Unix(), strings.ReplaceAll(req.Name[:min(10, len(req.Name))], " ", "_"))
|
||||
|
||||
iconPath, err := UploadFile(file, filename, contentType, c)
|
||||
iconPath, err := UploadFile(file, contentType, c)
|
||||
if err != nil {
|
||||
slog.Error("Failed to upload file", "error", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
"message": "Failed to upload file, please try again!",
|
||||
status := fiber.StatusInternalServerError
|
||||
if strings.Contains(err.Error(), "unsupported file type") {
|
||||
status = fiber.StatusBadRequest
|
||||
}
|
||||
|
||||
return c.Status(status).JSON(fiber.Map{
|
||||
"message": "Failed to upload file: " + err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1189,13 +1236,16 @@ func main() {
|
||||
|
||||
oldIconPath := link.Icon
|
||||
|
||||
filename := fmt.Sprintf("%d_%s.webp", time.Now().Unix(), strings.ReplaceAll(req.Name[:min(10, len(req.Name))], " ", "_"))
|
||||
|
||||
iconPath, err := UploadFile(file, filename, contentType, c)
|
||||
iconPath, err := UploadFile(file, contentType, c)
|
||||
if err != nil {
|
||||
slog.Error("Failed to upload file", "error", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
"message": "Failed to upload file, please try again!",
|
||||
status := fiber.StatusInternalServerError
|
||||
if strings.Contains(err.Error(), "unsupported file type") {
|
||||
status = fiber.StatusBadRequest
|
||||
}
|
||||
|
||||
return c.Status(status).JSON(fiber.Map{
|
||||
"message": "Failed to upload file: " + err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
1228
src/scripts/admin.js
Normal file
1228
src/scripts/admin.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -6,6 +6,7 @@ import (
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
@@ -16,18 +17,20 @@ type DepricatedUptimeConfig struct {
|
||||
}
|
||||
|
||||
type UptimeConfig struct {
|
||||
Provider string `env:"UPTIME_PROVIDER" envDefault:"uptimerobot"`
|
||||
APIKey string
|
||||
UpdateInterval int `env:"UPTIME_UPDATE_INTERVAL" envDefault:"300"`
|
||||
}
|
||||
|
||||
type UptimeRobotSite struct {
|
||||
FriendlyName string `json:"friendly_name"`
|
||||
Url string `json:"url"`
|
||||
Status int `json:"status"`
|
||||
type UptimeSite struct {
|
||||
FriendlyName string
|
||||
Url string
|
||||
Up bool
|
||||
}
|
||||
|
||||
type UptimeManager struct {
|
||||
sites []UptimeRobotSite
|
||||
provider string
|
||||
sites []UptimeSite
|
||||
lastUpdate time.Time
|
||||
mutex sync.RWMutex
|
||||
updateChan chan struct{}
|
||||
@@ -37,7 +40,7 @@ type UptimeManager struct {
|
||||
|
||||
func NewUptimeManager(config *UptimeConfig) *UptimeManager {
|
||||
if config.APIKey == "" {
|
||||
log.Fatalln("UptimeRobot API Key is required!")
|
||||
log.Fatalln("An API Key is required to use Uptime Monitoring!")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -47,10 +50,11 @@ func NewUptimeManager(config *UptimeConfig) *UptimeManager {
|
||||
}
|
||||
|
||||
uptimeManager := &UptimeManager{
|
||||
provider: config.Provider,
|
||||
updateChan: make(chan struct{}),
|
||||
updateInterval: updateInterval,
|
||||
apiKey: config.APIKey,
|
||||
sites: []UptimeRobotSite{},
|
||||
sites: []UptimeSite{},
|
||||
}
|
||||
|
||||
go uptimeManager.updateWorker()
|
||||
@@ -60,7 +64,7 @@ func NewUptimeManager(config *UptimeConfig) *UptimeManager {
|
||||
return uptimeManager
|
||||
}
|
||||
|
||||
func (u *UptimeManager) GetUptime() []UptimeRobotSite {
|
||||
func (u *UptimeManager) GetUptime() []UptimeSite {
|
||||
u.mutex.RLock()
|
||||
defer u.mutex.RUnlock()
|
||||
return u.sites
|
||||
@@ -80,32 +84,119 @@ func (u *UptimeManager) updateWorker() {
|
||||
}
|
||||
}
|
||||
|
||||
type UptimeRobotSite struct {
|
||||
FriendlyName string `json:"friendly_name"`
|
||||
Url string `json:"url"`
|
||||
Status int `json:"status"`
|
||||
}
|
||||
|
||||
type UptimeRobotResponse struct {
|
||||
Monitors []UptimeRobotSite `json:"monitors"`
|
||||
}
|
||||
|
||||
type BetterUptimeSite struct {
|
||||
MonitorType string `json:"type"`
|
||||
Attributes struct {
|
||||
PronounceableName string `json:"pronounceable_name"`
|
||||
Url string `json:"url"`
|
||||
Status string `json:"status"`
|
||||
} `json:"attributes"`
|
||||
}
|
||||
|
||||
type BetterUptimeResponse struct {
|
||||
Monitors []BetterUptimeSite `json:"data"`
|
||||
}
|
||||
|
||||
func (u *UptimeManager) update() {
|
||||
var monitors []UptimeSite
|
||||
switch u.provider {
|
||||
case "uptimerobot":
|
||||
monitors = u.updateUptimeRobot()
|
||||
case "betteruptime":
|
||||
monitors = u.updateBetterUptime()
|
||||
default:
|
||||
log.Fatalln("Invalid Uptime Provider!")
|
||||
}
|
||||
|
||||
u.mutex.Lock()
|
||||
u.sites = monitors
|
||||
u.lastUpdate = time.Now()
|
||||
u.mutex.Unlock()
|
||||
}
|
||||
|
||||
func (u *UptimeManager) updateUptimeRobot() []UptimeSite {
|
||||
resp, err := http.Post("https://api.uptimerobot.com/v2/getMonitors?api_key="+u.apiKey, "application/json", nil)
|
||||
if err != nil {
|
||||
fmt.Printf("Error fetching uptime data: %v\n", err)
|
||||
return
|
||||
return []UptimeSite{}
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
fmt.Printf("Error reading response: %v\n", err)
|
||||
return
|
||||
return []UptimeSite{}
|
||||
}
|
||||
|
||||
var monitors UptimeRobotResponse
|
||||
if err := json.Unmarshal(body, &monitors); err != nil {
|
||||
var rawMonitors UptimeRobotResponse
|
||||
if err := json.Unmarshal(body, &rawMonitors); err != nil {
|
||||
fmt.Printf("Error parsing uptime data: %v\n", err)
|
||||
return
|
||||
return []UptimeSite{}
|
||||
}
|
||||
|
||||
u.mutex.Lock()
|
||||
u.sites = monitors.Monitors
|
||||
u.lastUpdate = time.Now()
|
||||
u.mutex.Unlock()
|
||||
var monitors []UptimeSite
|
||||
for _, rawMonitor := range rawMonitors.Monitors {
|
||||
monitors = append(monitors, UptimeSite{
|
||||
FriendlyName: rawMonitor.FriendlyName,
|
||||
Url: rawMonitor.Url,
|
||||
Up: rawMonitor.Status == 2,
|
||||
})
|
||||
}
|
||||
|
||||
return monitors
|
||||
}
|
||||
|
||||
func (u *UptimeManager) updateBetterUptime() []UptimeSite {
|
||||
client := &http.Client{}
|
||||
req, err := http.NewRequest("GET", "https://uptime.betterstack.com/api/v2/monitors", nil)
|
||||
if err != nil {
|
||||
fmt.Printf("Error fetching uptime data: %v\n", err)
|
||||
return []UptimeSite{}
|
||||
}
|
||||
req.Header.Add("Authorization", "Bearer "+u.apiKey)
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
fmt.Printf("Error fetching uptime data: %v\n", err)
|
||||
return []UptimeSite{}
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
fmt.Printf("Error reading response: %v\n", err)
|
||||
return []UptimeSite{}
|
||||
}
|
||||
|
||||
var rawMonitors BetterUptimeResponse
|
||||
if err := json.Unmarshal(body, &rawMonitors); err != nil {
|
||||
fmt.Printf("Error parsing uptime data: %v\n", err)
|
||||
return []UptimeSite{}
|
||||
}
|
||||
|
||||
// alphabetically sort the monitors because UptimeRobot does, but BetterUptime doesnt (or sorts by something else?), and I want them to be consistent
|
||||
sort.Slice(rawMonitors.Monitors, func(i, j int) bool {
|
||||
return rawMonitors.Monitors[i].Attributes.PronounceableName < rawMonitors.Monitors[j].Attributes.PronounceableName
|
||||
})
|
||||
|
||||
var monitors []UptimeSite
|
||||
for _, rawMonitor := range rawMonitors.Monitors {
|
||||
monitors = append(monitors, UptimeSite{
|
||||
FriendlyName: rawMonitor.Attributes.PronounceableName,
|
||||
Url: rawMonitor.Attributes.Url,
|
||||
Up: rawMonitor.Attributes.Status == "up",
|
||||
})
|
||||
}
|
||||
|
||||
return monitors
|
||||
}
|
||||
|
||||
253
src/styles/adminUi.css
Normal file
253
src/styles/adminUi.css
Normal file
@@ -0,0 +1,253 @@
|
||||
@import "./base.css";
|
||||
@import "./card.css";
|
||||
|
||||
@layer components {
|
||||
.modal-bg {
|
||||
position: fixed;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
inset: 0;
|
||||
background-color: color-mix(in srgb, #000 45%, #0000);
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modal-bg.is-visible {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.modal {
|
||||
opacity: 0;
|
||||
background-color: var(--color-overlay);
|
||||
border-radius: calc(var(--spacing) * 3);
|
||||
overflow: hidden;
|
||||
padding: calc(var(--spacing) * 4);
|
||||
width: 100%;
|
||||
max-width: 24rem;
|
||||
}
|
||||
|
||||
.modal.is-visible {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#blur-target {
|
||||
transition: filter 300ms cubic-bezier(0.45, 0, 0.55, 1);
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.modal-bg {
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
|
||||
transition: opacity 0.3s ease, visibility 0s 0.3s;
|
||||
transition-timing-function: cubic-bezier(0.45, 0, 0.55, 1);
|
||||
}
|
||||
|
||||
.modal-bg.is-visible {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
transition-delay: 0s;
|
||||
}
|
||||
|
||||
.modal {
|
||||
opacity: 0;
|
||||
transform: translateY(20px) scale(0.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 {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
column-gap: calc(var(--spacing) * 2);
|
||||
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 {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
column-gap: calc(var(--spacing) * 2);
|
||||
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;
|
||||
}
|
||||
|
||||
&:not([disabled]) {
|
||||
&: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;
|
||||
|
||||
&:not([disabled]) {
|
||||
&: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;
|
||||
}
|
||||
|
||||
button[disabled] {
|
||||
cursor: not-allowed;
|
||||
filter: brightness(75%);
|
||||
}
|
||||
|
||||
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
199
src/styles/base.css
Normal 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);
|
||||
}
|
||||
}
|
||||
157
src/styles/card.css
Normal file
157
src/styles/card.css
Normal file
@@ -0,0 +1,157 @@
|
||||
/* 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;
|
||||
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%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.flex-shrink-0 {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.pl-2 {
|
||||
padding-left: calc(var(--spacing) * 2);
|
||||
}
|
||||
}
|
||||
58
src/styles/login.css
Normal file
58
src/styles/login.css
Normal 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
133
src/styles/main.css
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,135 +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% .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.010 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);
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
@apply font-semibold;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
&: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:has(img):first-child {
|
||||
flex-shrink: 0;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.link-card div:first-child img {
|
||||
user-select: none;
|
||||
border-radius: 0.375rem;
|
||||
aspect-ratio: 1/1;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
/* Div that holds the text */
|
||||
.link-card div:nth-child(2) {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.link-card div:nth-child(2) p {
|
||||
color: var(--color-subtle);
|
||||
}
|
||||
@@ -1,16 +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">
|
||||
<style>{{{inlineCSS}}}</style>
|
||||
</head>
|
||||
|
||||
<body class="bg-surface text-text">
|
||||
{{embed}}
|
||||
</body>
|
||||
{{{devContent}}}
|
||||
</html>
|
||||
111
src/templates/partials/category-grid.hbs
Normal file
111
src/templates/partials/category-grid.hbs
Normal 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>{{this.Name}}</h2>
|
||||
{{#if IsAdmin}}
|
||||
<div class="flex-shrink-0 pl-2">
|
||||
<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}}
|
||||
@@ -1,7 +1,6 @@
|
||||
<div id="category-contents" class="hidden">
|
||||
<h3>Create A category</h3>
|
||||
<form id="category-form" action="/api/categories" method="post"
|
||||
class="flex flex-col gap-y-3 my-2 [&>div]:flex [&>div]:flex-col [&>div]:gap-1">
|
||||
<form id="category-form" action="/api/categories" method="post" class="modal-form">
|
||||
<div>
|
||||
<label for="categoryName">Name</label>
|
||||
<input required type="text" name="name" id="categoryName" maxlength="50" />
|
||||
@@ -10,7 +9,7 @@
|
||||
<label for="linkIcon">Icon</label>
|
||||
<input type="file" name="icon" id="linkIcon" accept=".svg" required />
|
||||
</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>
|
||||
</form>
|
||||
<span id="category-message"></span>
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
<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>
|
||||
<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.
|
||||
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">
|
||||
<button class="px-4 py-2 rounded-md w-full bg-error text-white border-0"
|
||||
onclick="confirmDeleteCategory()">Delete
|
||||
<div>
|
||||
<button onclick="confirmDeleteCategory(event)">Delete
|
||||
category</button>
|
||||
<button class="px-4 py-2 rounded-md w-full bg-overlay border border-highlight text-white"
|
||||
onclick="closeModal()">Cancel</button>
|
||||
<button onclick="closeModal()">Cancel</button>
|
||||
</div>
|
||||
<span id="delete-category-message"></span>
|
||||
</div>
|
||||
@@ -1,12 +1,12 @@
|
||||
<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>
|
||||
<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
|
||||
want to continue?</p>
|
||||
<div class="flex justify-end flex-col gap-y-2">
|
||||
<button class="px-4 py-2 rounded-md w-full bg-error text-white border-0" onclick="confirmDeleteLink()">Delete
|
||||
<div>
|
||||
<button onclick="confirmDeleteLink(event)">Delete
|
||||
link</button>
|
||||
<button class="px-4 py-2 rounded-md w-full bg-overlay border border-highlight text-white"
|
||||
onclick="closeModal()">Cancel</button>
|
||||
<button onclick="closeModal()">Cancel</button>
|
||||
</div>
|
||||
<span id="delete-link-message"></span>
|
||||
</div>
|
||||
@@ -1,7 +1,6 @@
|
||||
<div id="link-contents" class="hidden">
|
||||
<h3>Add A link</h3>
|
||||
<form id="link-form" action="/api/links" method="post"
|
||||
class="flex flex-col gap-y-3 my-2 [&>div]:flex [&>div]:flex-col [&>div]:gap-1">
|
||||
<form id="link-form" action="/api/links" method="post" class="modal-form">
|
||||
<div>
|
||||
<label for="linkName">Name</label>
|
||||
<input required type="text" name="name" id="linkName" maxlength="50" />
|
||||
@@ -16,9 +15,10 @@
|
||||
</div>
|
||||
<div>
|
||||
<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/jpeg,image/png,image/webp,image/svg+xml" />
|
||||
</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>
|
||||
</form>
|
||||
<span id="link-message"></span>
|
||||
|
||||
@@ -1,150 +1,83 @@
|
||||
<div id="blur-target"
|
||||
class="transition-[filter] motion-reduce:transition-none ease-[cubic-bezier(0.45,0,0.55,1)] duration-300">
|
||||
<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>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<section class="flex justify-center w-full">
|
||||
<div class="w-full sm:w-4/5 p-2.5">
|
||||
{{#each Categories}}
|
||||
<div class="flex items-center" key="category-{{this.ID}}">
|
||||
<div class="shrink-0 relative mr-2 h-full flex items-center justify-center">
|
||||
<img class="object-contain select-none" width="32" height="32" draggable="false" alt="{{this.Name}}"
|
||||
src="{{this.Icon}}" />
|
||||
<button onclick="selectIcon()"
|
||||
class="absolute inset-0 bg-highlight/80 hidden 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>
|
||||
<h2 class="capitalize break-all border border-transparent">{{this.Name}}</h2>
|
||||
<div class="ml-2" data-edit-actions>
|
||||
<div class="flex flex-row gap-2">
|
||||
<button aria-label="Edit category" onclick="editCategory({{this.ID}})"
|
||||
class="w-fit h-fit flex p-1 bg-highlight-sm shadow-sm border border-highlight/70 rounded-full hover:filter hover:brightness-125 active:brightness-95 cursor-pointer transition-[filter] ease-[cubic-bezier(0.45,0,0.55,1)] duration-150">
|
||||
<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 category" onclick="deleteCategory({{this.ID}})"
|
||||
class="text-error w-fit h-fit flex p-1 bg-highlight-sm shadow-sm border border-highlight/70 rounded-full hover:filter hover:brightness-125 active:brightness-95 cursor-pointer transition-[filter] ease-[cubic-bezier(0.45,0,0.55,1)] duration-150">
|
||||
<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 class="hidden flex-row gap-2">
|
||||
<button aria-label="Confirm category edit" onclick="confirmCategoryEdit({{this.ID}})"
|
||||
class="w-fit h-fit flex p-1 bg-highlight-sm shadow-sm border border-highlight/70 rounded-full hover:filter hover:brightness-125 active:brightness-95 cursor-pointer transition-[filter] ease-[cubic-bezier(0.45,0,0.55,1)] duration-150 text-success">
|
||||
<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 aria-label="Cancel category edit" onclick="cancelCategoryEdit({{this.ID}})"
|
||||
class="w-fit h-fit flex p-1 bg-highlight-sm shadow-sm border border-highlight/70 rounded-full hover:filter hover:brightness-125 active:brightness-95 cursor-pointer transition-[filter] ease-[cubic-bezier(0.45,0,0.55,1)] duration-150 text-error">
|
||||
<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>
|
||||
</div>
|
||||
<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/styles/adminUi.css"}}}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="blur-target">
|
||||
<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>
|
||||
|
||||
{{> 'partials/category-grid' }}
|
||||
</div>
|
||||
|
||||
<input type="file" id="icon-upload" accept="image/*" style="display: none;" />
|
||||
<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>
|
||||
|
||||
<template id="template-loading-spinner">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<!-- Icon from SVG Spinners by Utkarsh Verma - https://github.com/n3r4zzurr0/svg-spinners/blob/main/LICENSE
|
||||
-->
|
||||
<path fill="#EAEAEA" d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
|
||||
opacity=".25" />
|
||||
<path fill="#EAEAEA"
|
||||
d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z">
|
||||
<animateTransform attributeName="transform" dur="0.75s" repeatCount="indefinite" type="rotate" values="0
|
||||
12 12;360 12 12" />
|
||||
</path>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<!-- store a blank link card so that if we add a new link we can clone it to make the editing experience easier -->
|
||||
<template id="template-link-card">
|
||||
<div data-card>
|
||||
<div>
|
||||
<img width="64" height="64" draggable="false" />
|
||||
</div>
|
||||
<div class="p-2.5 grid grid-cols-[repeat(auto-fill,_minmax(min(330px,_100%),_1fr))] gap-2">
|
||||
{{#each this.Links}}
|
||||
<div key="link-{{this.ID}}" class="link-card relative admin">
|
||||
<div class="relative">
|
||||
<img width="64" height="64" draggable="false" src="{{this.Icon}}" alt="{{this.Name}}" />
|
||||
<button onclick="selectIcon()"
|
||||
class="absolute inset-0 bg-highlight/80 hidden 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>
|
||||
<div class="flex-grow">
|
||||
<h3 class="border border-transparent">{{this.Name}}</h3>
|
||||
<p class="min-h-5">{{this.Description}}</p>
|
||||
</div>
|
||||
<div class="absolute right-1 top-1" data-edit-actions>
|
||||
<div class="flex flex-row gap-2">
|
||||
<button aria-label="Edit link" onclick="editLink({{this.ID}}, {{this.CategoryID}})"
|
||||
class="w-fit h-fit flex p-1 bg-highlight-sm shadow-sm border border-highlight/70 rounded-full hover:filter hover:brightness-125 active:brightness-95 cursor-pointer transition-[filter] ease-[cubic-bezier(0.45,0,0.55,1)] duration-150">
|
||||
<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.ID}}, {{this.CategoryID}})"
|
||||
class="text-error w-fit h-fit flex p-1 bg-highlight-sm shadow-sm border border-highlight/70 rounded-full hover:filter hover:brightness-125 active:brightness-95 cursor-pointer transition-[filter] ease-[cubic-bezier(0.45,0,0.55,1)] duration-150">
|
||||
<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 class="hidden flex-row gap-2">
|
||||
<button aria-label="Confirm link edit"
|
||||
onclick="confirmLinkEdit({{this.ID}}, {{this.CategoryID}})"
|
||||
class="w-fit h-fit flex p-1 bg-highlight-sm shadow-sm border border-highlight/70 rounded-full hover:filter hover:brightness-125 active:brightness-95 cursor-pointer transition-[filter] ease-[cubic-bezier(0.45,0,0.55,1)] duration-150 text-success">
|
||||
<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 aria-label="Cancel link edit"
|
||||
onclick="cancelLinkEdit({{this.ID}}, {{this.CategoryID}})"
|
||||
class="w-fit h-fit flex p-1 bg-highlight-sm shadow-sm border border-highlight/70 rounded-full hover:filter hover:brightness-125 active:brightness-95 cursor-pointer transition-[filter] ease-[cubic-bezier(0.45,0,0.55,1)] duration-150 text-error">
|
||||
<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>
|
||||
</div>
|
||||
<div>
|
||||
<h3></h3>
|
||||
<!-- add 2 to the height to account for the border -->
|
||||
<p></p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="template-category">
|
||||
<div>
|
||||
<div class="category-header">
|
||||
<div>
|
||||
<img width="32" height="32" draggable="false" />
|
||||
</div>
|
||||
{{/each}}
|
||||
<div onclick="openModal('link', {{this.ID}})"
|
||||
class="rounded-2xl border border-dashed border-subtle p-2.5 flex flex-row items-center hover:underline transition-[box-shadow,transform] ease-[cubic-bezier(0.45,0,0.55,1)] duration-150 pointer-cursor select-none cursor-pointer">
|
||||
<h2></h2>
|
||||
</div>
|
||||
<div class="link-grid">
|
||||
<div class="new-link-card link-card admin">
|
||||
<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" />
|
||||
@@ -154,706 +87,66 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
<div class="flex items-center">
|
||||
<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"
|
||||
stroke-width="2" d="M12 5v14m-7-7h14" />
|
||||
</svg>
|
||||
<h2 onclick="openModal('category')" class="text-subtle underline decoration-dashed cursor-pointer">
|
||||
Add a new category
|
||||
</h2>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="template-edit-actions">
|
||||
<div>
|
||||
<div class="action-container">
|
||||
<button 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 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>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<input type="file" id="icon-upload" accept="image/*" style="display: none;" />
|
||||
<div id="modal-container" role="dialog" aria-modal="true"
|
||||
class="flex modal-bg fixed top-0 left-0 bottom-0 right-0 bg-black/45 justify-center items-center">
|
||||
<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 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 -->
|
||||
<div class="action-container" id="confirm-actions">
|
||||
<button class="action-button text-success" onclick="confirmEdit(event)">
|
||||
<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 -->
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// idfk what this variable capitalization is, it's a mess
|
||||
let modalContainer = document.getElementById("modal-container");
|
||||
let modal = modalContainer.querySelector("div");
|
||||
let pageElement = document.getElementById("blur-target");
|
||||
let iconUploader = document.getElementById("icon-upload");
|
||||
let targetCategoryID = null;
|
||||
let activeModal = null;
|
||||
{{{embedFile "scripts/admin.js"}}}
|
||||
</body>
|
||||
|
||||
// errpr check the form and add the invalid class if it's invalid
|
||||
{{{devContent}}}
|
||||
|
||||
/**
|
||||
* Submits a form to the given URL
|
||||
* @param {Event} event - The event that triggered the function
|
||||
* @param {string} url - The URL to submit the form to
|
||||
* @param {"category" | "link"} target - The target to close the modal for
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function submitRequest(event, url, target) {
|
||||
event.preventDefault();
|
||||
let data = new FormData(event.target);
|
||||
|
||||
let res = await fetch(url, {
|
||||
method: "POST",
|
||||
body: data
|
||||
});
|
||||
|
||||
if (res.status === 201) {
|
||||
closeModal(target);
|
||||
document.getElementById(`${target}-form`).reset();
|
||||
location.reload();
|
||||
} else {
|
||||
let json = await res.json();
|
||||
document.getElementById(`${target}-message`).innerText = json.message;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the form for the given form
|
||||
* @param {"category" | "link"} form - The form to initialize
|
||||
* @returns {void}
|
||||
*/
|
||||
function addErrorListener(form) {
|
||||
document.getElementById(`${form}-form`).querySelector("button").addEventListener("click", (event) => {
|
||||
document.getElementById(`${form}-form`).querySelectorAll("[required]").forEach((el) => {
|
||||
el.classList.add("invalid:border-[#861024]!");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
addErrorListener("link");
|
||||
document.getElementById("link-form").addEventListener("submit", async (event) => {
|
||||
await submitRequest(event, `/api/category/${targetCategoryID}/link`, "link");
|
||||
});
|
||||
|
||||
addErrorListener("category");
|
||||
document.getElementById("category-form").addEventListener("submit", async (event) => {
|
||||
await submitRequest(event, `/api/category`, "category");
|
||||
});
|
||||
|
||||
// when the background is clicked, close the modal
|
||||
modalContainer.addEventListener("click", (event) => {
|
||||
if (event.target === modalContainer) {
|
||||
closeModal();
|
||||
}
|
||||
});
|
||||
|
||||
function selectIcon() {
|
||||
iconUploader.click();
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a file and returns a data URL.
|
||||
* @param {File} file The file to process.
|
||||
*/
|
||||
async function processFile(file) {
|
||||
let reader = new FileReader();
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
if (file.type === "image/svg+xml") {
|
||||
reader.addEventListener("load", async (event) => {
|
||||
let svgString = event.target.result;
|
||||
|
||||
console.log(svgString);
|
||||
|
||||
svgString = svgString.replaceAll("currentColor", "oklch(87% 0.015 286)");
|
||||
|
||||
console.log(svgString);
|
||||
|
||||
// turn svgString into a data URL
|
||||
resolve("data:image/svg+xml;base64," + btoa(unescape(encodeURIComponent(svgString))));
|
||||
})
|
||||
|
||||
reader.readAsText(file);
|
||||
} else {
|
||||
|
||||
// these should be jpg, png, or webp
|
||||
// make a DataURL out of it
|
||||
reader.addEventListener("load", async (event) => {
|
||||
resolve(event.target.result);
|
||||
});
|
||||
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let targetedImageElement = null;
|
||||
iconUploader.addEventListener("change", async (event) => {
|
||||
let file = event.target.files[0];
|
||||
if (file === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (targetedImageElement === null) {
|
||||
throw new Error("icon upload element was clicked, but no target image element was set");
|
||||
}
|
||||
|
||||
console.log(file);
|
||||
|
||||
let dataURL = await processFile(file);
|
||||
targetedImageElement.src = dataURL;
|
||||
});
|
||||
|
||||
function openModal(modalKind, categoryID) {
|
||||
activeModal = modalKind;
|
||||
targetCategoryID = categoryID;
|
||||
|
||||
pageElement.style.filter = "blur(20px)";
|
||||
document.getElementById(modalKind + "-contents").classList.remove("hidden");
|
||||
|
||||
modalContainer.classList.add("is-visible");
|
||||
modal.classList.add("is-visible");
|
||||
|
||||
if (document.getElementById(modalKind + "-form") !== null) {
|
||||
document.getElementById(modalKind + "-form").reset();
|
||||
}
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
pageElement.style.filter = "";
|
||||
|
||||
modalContainer.classList.remove("is-visible");
|
||||
modal.classList.remove("is-visible");
|
||||
|
||||
setTimeout(() => {
|
||||
document.getElementById(activeModal + "-contents").classList.add("hidden");
|
||||
activeModal = null;
|
||||
}, 300)
|
||||
|
||||
if (document.getElementById(activeModal + "-form") !== null) {
|
||||
document.getElementById(activeModal + "-form").querySelectorAll("[required]").forEach((el) => {
|
||||
el.classList.remove("invalid:border-[#861024]!");
|
||||
});
|
||||
}
|
||||
|
||||
targetCategoryID = null;
|
||||
}
|
||||
|
||||
let currentlyEditingLink = {
|
||||
ID: null,
|
||||
categoryID: null,
|
||||
originalText: null,
|
||||
originalDescription: null,
|
||||
icon: null,
|
||||
};
|
||||
|
||||
async function editLink(linkID, categoryID) {
|
||||
if (currentlyEditingLink.ID !== null) {
|
||||
// cancel the edit if it's already in progress
|
||||
cancelLinkEdit(currentlyEditingLink.ID, currentlyEditingCategory.categoryID);
|
||||
}
|
||||
|
||||
let linkEl = document.querySelector(`[key=link-${linkID}]`);
|
||||
let linkImg = linkEl.querySelector("div:first-child img");
|
||||
let fileUploaderOverlay = linkImg.nextElementSibling;
|
||||
let linkName = linkEl.querySelector("div:nth-child(2) h3");
|
||||
let linkDesc = linkEl.querySelector("div:nth-child(2) p");
|
||||
let editActions = linkEl.querySelector("[data-edit-actions]");
|
||||
|
||||
currentlyEditingLink.ID = linkID;
|
||||
currentlyEditingCategory.categoryID = categoryID;
|
||||
currentlyEditingLink.originalText = linkName.textContent;
|
||||
currentlyEditingLink.originalDescription = linkDesc.textContent;
|
||||
currentlyEditingLink.icon = linkImg.src;
|
||||
|
||||
console.log(currentlyEditingLink)
|
||||
|
||||
iconUploader.accept = "image/*";
|
||||
targetedImageElement = linkImg;
|
||||
|
||||
editActions.querySelector("div").classList.add("hidden");
|
||||
editActions.querySelector("div").classList.remove("flex");
|
||||
|
||||
editActions.querySelector("div:nth-child(2)").classList.remove("hidden");
|
||||
editActions.querySelector("div:nth-child(2)").classList.add("flex");
|
||||
|
||||
fileUploaderOverlay.classList.remove("hidden");
|
||||
fileUploaderOverlay.classList.add("flex");
|
||||
|
||||
replaceWithResizableInput(linkName);
|
||||
replaceWithResizableTextarea(linkDesc);
|
||||
}
|
||||
|
||||
async function confirmLinkEdit(linkID, categoryID) {
|
||||
let linkEl = document.querySelector(`[key=link-${linkID}]`);
|
||||
let linkImg = linkEl.querySelector("div:first-child img");
|
||||
let fileUploaderOverlay = linkImg.nextElementSibling;
|
||||
let linkNameInput = linkEl.querySelector("input");
|
||||
let linkDescInput = linkEl.querySelector("textarea");
|
||||
let editActions = linkEl.querySelector("[data-edit-actions]");
|
||||
|
||||
linkNameInput.value = linkNameInput.value.trim();
|
||||
linkDescInput.value = linkDescInput.value.trim();
|
||||
console.log(linkNameInput.value);
|
||||
if (linkNameInput.value === "") {
|
||||
return;
|
||||
}
|
||||
|
||||
let formData = new FormData();
|
||||
if (linkNameInput.value !== currentlyEditingLink.originalText) {
|
||||
formData.append("name", linkNameInput.value)
|
||||
}
|
||||
|
||||
if (linkDescInput.value !== currentlyEditingLink.originalDescription) {
|
||||
formData.append("description", linkDescInput.value)
|
||||
}
|
||||
|
||||
if (iconUploader.files.length > 0) {
|
||||
formData.append("icon", iconUploader.files[0]);
|
||||
}
|
||||
|
||||
// nothing to update
|
||||
if (formData.get("name") === null && formData.get("description") === null && formData.get("icon") === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
let res = await fetch(`/api/category/${categoryID}/link/${linkID}`, {
|
||||
method: "PATCH",
|
||||
body: formData
|
||||
});
|
||||
|
||||
if (res.status === 200) {
|
||||
|
||||
iconUploader.value = "";
|
||||
|
||||
currentlyEditingLink.icon = null;
|
||||
|
||||
cancelLinkEdit(currentlyEditingLink.ID, currentlyEditingCategory.categoryID, linkNameInput.value || currentlyEditingLink.originalText, linkDescInput.value || currentlyEditingLink.originalDescription);
|
||||
currentlyEditingLink.originalText = null;
|
||||
currentlyEditingLink.originalDescription = null;
|
||||
|
||||
currentlyEditingLink.ID = null;
|
||||
} else {
|
||||
console.error("Failed to edit category");
|
||||
}
|
||||
}
|
||||
|
||||
async function cancelLinkEdit(linkID, categoryID, text = undefined, description = undefined) {
|
||||
let linkEl = document.querySelector(`[key=link-${linkID}]`);
|
||||
let linkInput = linkEl.querySelector("input");
|
||||
let linkTextarea = linkEl.querySelector("textarea");
|
||||
let linkImg = linkEl.querySelector("div:first-child img");
|
||||
let fileUploaderOverlay = linkImg.nextElementSibling;
|
||||
let editActions = linkEl.querySelector("[data-edit-actions]");
|
||||
|
||||
console.log(linkInput);
|
||||
console.log(editActions);
|
||||
|
||||
if (currentlyEditingLink.icon !== null) {
|
||||
linkImg.src = currentlyEditingLink.icon;
|
||||
}
|
||||
|
||||
editActions.querySelector("div").classList.remove("hidden");
|
||||
editActions.querySelector("div").classList.add("flex");
|
||||
|
||||
editActions.querySelector("div:nth-child(2)").classList.add("hidden");
|
||||
editActions.querySelector("div:nth-child(2)").classList.remove("flex");
|
||||
|
||||
fileUploaderOverlay.classList.add("hidden");
|
||||
fileUploaderOverlay.classList.remove("flex");
|
||||
|
||||
if (text === undefined) {
|
||||
text = currentlyEditingLink.originalText;
|
||||
}
|
||||
|
||||
if (description === undefined) {
|
||||
description = currentlyEditingLink.originalDescription;
|
||||
}
|
||||
|
||||
restoreElementFromInput(linkInput, text);
|
||||
restoreElementFromInput(linkTextarea, description);
|
||||
|
||||
currentlyEditingLink.ID = null;
|
||||
targetedImageElement = null;
|
||||
}
|
||||
|
||||
let currentlyDeletingLink = {
|
||||
ID: null,
|
||||
categoryID: null,
|
||||
};
|
||||
|
||||
async function deleteLink(linkID, categoryID) {
|
||||
currentlyDeletingLink.ID = linkID;
|
||||
currentlyDeletingLink.categoryID = categoryID;
|
||||
|
||||
let linkNameSpan = document.getElementById("link-name");
|
||||
linkNameSpan.textContent = document.querySelector(`[key=link-${linkID}] h3`).textContent;
|
||||
|
||||
openModal("link-delete");
|
||||
}
|
||||
|
||||
async function confirmDeleteLink() {
|
||||
let res = await fetch(`/api/category/${currentlyDeletingLink.categoryID}/link/${currentlyDeletingLink.ID}`, {
|
||||
method: "DELETE"
|
||||
});
|
||||
|
||||
if (res.status === 200) {
|
||||
let linkEl = document.querySelector(`[key="link-${currentlyDeletingLink.ID}"]`);
|
||||
linkEl.remove();
|
||||
|
||||
closeModal();
|
||||
}
|
||||
}
|
||||
|
||||
let currentlyEditingCategory = {
|
||||
ID: null,
|
||||
originalText: null,
|
||||
icon: null,
|
||||
};
|
||||
|
||||
async function editCategory(categoryID) {
|
||||
if (currentlyEditingCategory.ID !== null) {
|
||||
// cancel the edit if it's already in progress
|
||||
cancelCategoryEdit(currentlyEditingCategory.ID);
|
||||
}
|
||||
|
||||
currentlyEditingCategory.ID = categoryID;
|
||||
|
||||
let categoryEl = document.querySelector(`[key=category-${categoryID}]`);
|
||||
let categoryName = categoryEl.querySelector("h2");
|
||||
let categoryIcon = categoryEl.querySelector("div img");
|
||||
let fileUploaderOverlay = categoryIcon.nextElementSibling;
|
||||
let editActions = categoryEl.querySelector("[data-edit-actions]");
|
||||
|
||||
currentlyEditingCategory.originalText = categoryName.textContent;
|
||||
currentlyEditingCategory.icon = categoryIcon.src;
|
||||
|
||||
iconUploader.accept = "image/svg+xml";
|
||||
targetedImageElement = categoryIcon;
|
||||
|
||||
editActions.querySelector("div").classList.add("hidden");
|
||||
editActions.querySelector("div").classList.remove("flex");
|
||||
|
||||
editActions.querySelector("div:nth-child(2)").classList.remove("hidden");
|
||||
editActions.querySelector("div:nth-child(2)").classList.add("flex");
|
||||
|
||||
fileUploaderOverlay.classList.remove("hidden");
|
||||
fileUploaderOverlay.classList.add("flex");
|
||||
|
||||
replaceWithResizableInput(categoryName);
|
||||
}
|
||||
|
||||
async function confirmCategoryEdit(categoryID) {
|
||||
let categoryEl = document.querySelector(`[key=category-${categoryID}]`);
|
||||
let categoryInput = categoryEl.querySelector("input");
|
||||
let categoryIcon = categoryEl.querySelector("div img");
|
||||
let fileUploaderOverlay = categoryIcon.nextElementSibling;
|
||||
let editActions = categoryEl.querySelector("[data-edit-actions]");
|
||||
|
||||
if (categoryInput.value === "") {
|
||||
return;
|
||||
}
|
||||
|
||||
categoryInput.value = categoryInput.value.trim();
|
||||
|
||||
let formData = new FormData();
|
||||
if (categoryInput.value !== currentlyEditingCategory.originalText) {
|
||||
formData.append("name", categoryInput.value)
|
||||
}
|
||||
|
||||
if (iconUploader.files.length > 0) {
|
||||
formData.append("icon", iconUploader.files[0]);
|
||||
}
|
||||
|
||||
// nothing to update
|
||||
if (formData.get("name") === null && formData.get("icon") === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
let res = await fetch(`/api/category/${categoryID}`, {
|
||||
method: "PATCH",
|
||||
body: formData
|
||||
});
|
||||
|
||||
if (res.status === 200) {
|
||||
|
||||
iconUploader.value = "";
|
||||
|
||||
cancelCategoryEdit(categoryID, categoryInput.value || currentlyEditingCategory.originalText);
|
||||
|
||||
currentlyEditingCategory.icon = null;
|
||||
currentlyEditingCategory.originalText = null;
|
||||
|
||||
currentlyEditingCategory.ID = null;
|
||||
} else {
|
||||
console.error("Failed to edit category");
|
||||
}
|
||||
}
|
||||
|
||||
async function cancelCategoryEdit(categoryID, text = undefined) {
|
||||
let categoryEl = document.querySelector(`[key=category-${categoryID}]`);
|
||||
let categoryInput = categoryEl.querySelector("input");
|
||||
let categoryIcon = categoryEl.querySelector("div img");
|
||||
let fileUploaderOverlay = categoryIcon.nextElementSibling;
|
||||
let editActions = categoryEl.querySelector("[data-edit-actions]");
|
||||
|
||||
console.log(categoryInput);
|
||||
console.log(editActions);
|
||||
|
||||
if (currentlyEditingCategory.icon !== null) {
|
||||
categoryIcon.src = currentlyEditingCategory.icon;
|
||||
}
|
||||
|
||||
editActions.querySelector("div").classList.remove("hidden");
|
||||
editActions.querySelector("div").classList.add("flex");
|
||||
|
||||
editActions.querySelector("div:nth-child(2)").classList.add("hidden");
|
||||
editActions.querySelector("div:nth-child(2)").classList.remove("flex");
|
||||
|
||||
fileUploaderOverlay.classList.remove("flex");
|
||||
fileUploaderOverlay.classList.add("hidden");
|
||||
|
||||
restoreElementFromInput(categoryInput, text || currentlyEditingCategory.originalText);
|
||||
|
||||
currentlyEditingCategory.ID = null;
|
||||
targetedImageElement = null;
|
||||
}
|
||||
|
||||
let currentlyDeletingCategory = {
|
||||
ID: null,
|
||||
};
|
||||
|
||||
async function deleteCategory(categoryID) {
|
||||
currentlyDeletingCategory.ID = categoryID;
|
||||
|
||||
let categoryNameSpan = document.getElementById("category-name");
|
||||
categoryNameSpan.textContent = document.querySelector(`[key=category-${categoryID}] h2`).textContent;
|
||||
|
||||
openModal("category-delete");
|
||||
}
|
||||
|
||||
async function confirmDeleteCategory() {
|
||||
let res = await fetch(`/api/category/${currentlyDeletingCategory.ID}`, {
|
||||
method: "DELETE"
|
||||
});
|
||||
|
||||
if (res.status === 200) {
|
||||
let categoryEl = document.querySelector(`[key="category-${currentlyDeletingCategory.ID}"]`);
|
||||
// get the next element and remove it (its the link grid)
|
||||
let nextEl = categoryEl.nextElementSibling;
|
||||
nextEl.remove();
|
||||
categoryEl.remove();
|
||||
|
||||
closeModal();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces an H2 element with a resizable input field that matches its initial text and styling.
|
||||
* @param {HTMLElement} targetEl The element to replace.
|
||||
*/
|
||||
function replaceWithResizableInput(targetEl) {
|
||||
const originalText = targetEl.textContent;
|
||||
const computedStyle = window.getComputedStyle(targetEl);
|
||||
|
||||
const inputElement = document.createElement('input');
|
||||
inputElement.type = 'text';
|
||||
inputElement.value = originalText;
|
||||
inputElement.className = 'resizable-input';
|
||||
inputElement.placeholder = 'Enter title...';
|
||||
inputElement.dataset.originalElementType = targetEl.tagName;
|
||||
inputElement.dataset.originalClassName = targetEl.className;
|
||||
|
||||
const stylesToCopy = [
|
||||
'font-family', 'font-size', 'font-weight', 'font-style', 'color',
|
||||
'line-height', 'letter-spacing', 'text-transform', 'text-align',
|
||||
'padding-top', 'padding-right', 'padding-bottom', 'padding-left',
|
||||
'border-top-width', 'border-right-width', 'border-bottom-width', 'border-left-width',
|
||||
'border-top-style', 'border-right-style', 'border-bottom-style', 'border-left-style',
|
||||
'border-top-color', 'border-right-color', 'border-bottom-color', 'border-left-color',
|
||||
'border-radius', 'box-sizing',
|
||||
'margin-top', 'margin-right', 'margin-bottom', 'margin-left',
|
||||
'height'
|
||||
];
|
||||
|
||||
stylesToCopy.forEach(prop => {
|
||||
inputElement.style[prop] = computedStyle[prop];
|
||||
});
|
||||
|
||||
inputElement.style.display = 'inline-block';
|
||||
inputElement.style.backgroundColor = 'var(--color-base)';
|
||||
inputElement.style.border = '1px solid var(--color-highlight-sm)';
|
||||
inputElement.style.borderRadius = '0.375rem';
|
||||
inputElement.maxLength = 50;
|
||||
|
||||
/**
|
||||
* Function to measure the text width accurately and apply it to the input.
|
||||
* @param {HTMLInputElement} inputEl The input element to resize.
|
||||
*/
|
||||
const resizeInput = (inputEl) => {
|
||||
const tempSpan = document.createElement('span');
|
||||
const currentInputComputedStyle = window.getComputedStyle(inputEl);
|
||||
|
||||
const textStylesToCopy = [
|
||||
'font-family', 'font-size', 'font-weight', 'font-style', 'letter-spacing',
|
||||
'text-transform', 'line-height'
|
||||
];
|
||||
textStylesToCopy.forEach(prop => {
|
||||
tempSpan.style[prop] = currentInputComputedStyle[prop];
|
||||
});
|
||||
|
||||
tempSpan.style.position = 'absolute';
|
||||
tempSpan.style.visibility = 'hidden';
|
||||
tempSpan.style.whiteSpace = 'nowrap';
|
||||
tempSpan.textContent = inputEl.value === '' ? inputEl.placeholder || 'W' : inputEl.value;
|
||||
|
||||
document.body.appendChild(tempSpan);
|
||||
let measuredTextWidth = tempSpan.offsetWidth;
|
||||
document.body.removeChild(tempSpan);
|
||||
|
||||
// Add a small buffer for the caret and a bit of extra space
|
||||
const caretBuffer = 10;
|
||||
let finalWidth = measuredTextWidth + caretBuffer;
|
||||
|
||||
const minWidth = 100;
|
||||
finalWidth = Math.max(finalWidth, minWidth);
|
||||
|
||||
if (currentInputComputedStyle.boxSizing === 'border-box') {
|
||||
const hPadding = parseFloat(currentInputComputedStyle.paddingLeft) + parseFloat(currentInputComputedStyle.paddingRight);
|
||||
const hBorder = parseFloat(currentInputComputedStyle.borderLeftWidth) + parseFloat(currentInputComputedStyle.borderRightWidth);
|
||||
inputEl.style.width = (finalWidth + hPadding + hBorder) + 'px';
|
||||
} else {
|
||||
inputEl.style.width = finalWidth + 'px';
|
||||
}
|
||||
};
|
||||
|
||||
setTimeout(() => resizeInput(inputElement), 0);
|
||||
inputElement.addEventListener('input', () => resizeInput(inputElement));
|
||||
targetEl.parentNode.replaceChild(inputElement, targetEl);
|
||||
|
||||
inputElement.focus();
|
||||
}
|
||||
|
||||
function replaceWithResizableTextarea(targetEl) {
|
||||
const originalText = targetEl.textContent;
|
||||
const computedStyle = window.getComputedStyle(targetEl);
|
||||
|
||||
const inputElement = document.createElement('textarea');
|
||||
inputElement.value = originalText;
|
||||
inputElement.className = 'resizable-input';
|
||||
inputElement.placeholder = 'Enter title...';
|
||||
inputElement.dataset.originalElementType = targetEl.tagName;
|
||||
inputElement.dataset.originalClassName = targetEl.className;
|
||||
|
||||
const stylesToCopy = [
|
||||
'font-family', 'font-size', 'font-weight', 'font-style', 'color',
|
||||
'line-height', 'letter-spacing', 'text-transform', 'text-align',
|
||||
'padding-top', 'padding-right', 'padding-bottom', 'padding-left',
|
||||
'border-top-width', 'border-right-width', 'border-bottom-width', 'border-left-width',
|
||||
'border-top-style', 'border-right-style', 'border-bottom-style', 'border-left-style',
|
||||
'border-top-color', 'border-right-color', 'border-bottom-color', 'border-left-color',
|
||||
'border-radius', 'box-sizing',
|
||||
'margin-top', 'margin-right', 'margin-bottom', 'margin-left',
|
||||
'height'
|
||||
];
|
||||
|
||||
stylesToCopy.forEach(prop => {
|
||||
inputElement.style[prop] = computedStyle[prop];
|
||||
});
|
||||
|
||||
inputElement.style.backgroundColor = 'var(--color-base)';
|
||||
inputElement.style.border = '1px solid var(--color-highlight-sm)';
|
||||
inputElement.style.borderRadius = '0.375rem';
|
||||
inputElement.style.resize = 'none';
|
||||
inputElement.style.overflow = 'hidden';
|
||||
inputElement.style.width = '100%';
|
||||
inputElement.style.outline = 'none';
|
||||
inputElement.maxLength = 150;
|
||||
|
||||
function resize() {
|
||||
inputElement.style.height = "0px";
|
||||
inputElement.style.height = inputElement.scrollHeight + "px";
|
||||
}
|
||||
|
||||
setTimeout(() => resize(), 0);
|
||||
inputElement.addEventListener('input', () => resize());
|
||||
targetEl.parentNode.replaceChild(inputElement, targetEl);
|
||||
|
||||
inputElement.focus();
|
||||
}
|
||||
|
||||
function restoreElementFromInput(inputEl, originalText) {
|
||||
const computedStyle = window.getComputedStyle(inputEl);
|
||||
|
||||
let elementType = inputEl.dataset.originalElementType;
|
||||
const newElement = document.createElement(elementType);
|
||||
newElement.textContent = originalText;
|
||||
newElement.className = inputEl.dataset.originalClassName;
|
||||
|
||||
newElement.style.border = '1px solid #0000';
|
||||
|
||||
const stylesToCopy = [
|
||||
'font-family', 'font-size', 'font-weight', 'font-style', 'color',
|
||||
'line-height', 'letter-spacing', 'text-transform', 'text-align',
|
||||
'padding-top', 'padding-right', 'padding-bottom', 'padding-left', 'box-sizing',
|
||||
'margin-top', 'margin-right', 'margin-bottom', 'margin-left',
|
||||
'height'
|
||||
];
|
||||
|
||||
stylesToCopy.forEach(prop => {
|
||||
newElement.style[prop] = computedStyle[prop];
|
||||
});
|
||||
|
||||
inputEl.parentNode.replaceChild(newElement, inputEl);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.modal-bg {
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.modal-bg.is-visible {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.modal {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.modal.is-visible {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.modal-bg {
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
|
||||
transition: opacity 0.3s ease, visibility 0s 0.3s;
|
||||
transition-timing-function: cubic-bezier(0.45, 0, 0.55, 1);
|
||||
}
|
||||
|
||||
.modal-bg.is-visible {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
transition-delay: 0s;
|
||||
}
|
||||
|
||||
.modal {
|
||||
opacity: 0;
|
||||
transform: translateY(20px) scale(0.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;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</html>
|
||||
@@ -1,20 +1,31 @@
|
||||
<main class="flex justify-center items-center h-screen relative bg-base">
|
||||
<div class="flex bg-surface rounded-xl overflow-hidden">
|
||||
<img src="/assets/leaves.webp" class="h-96 w-64 object-cover" />
|
||||
<div class="flex flex-col p-4 text-center">
|
||||
<h2 class="text-2xl">
|
||||
<!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/styles/login.css"}}}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main class="login-container">
|
||||
<img src="/assets/leaves.webp" />
|
||||
<div>
|
||||
<h2>
|
||||
Login
|
||||
</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="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>
|
||||
</form>
|
||||
<span id="message"></span>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
</main>
|
||||
</body>
|
||||
<script>
|
||||
let message = document.getElementById("message");
|
||||
let form = document.querySelector("form");
|
||||
@@ -42,4 +53,7 @@
|
||||
|
||||
message.innerText = (await res.json()).message;
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
{{{devContent}}}
|
||||
|
||||
</html>
|
||||
@@ -1,100 +1,86 @@
|
||||
<main class="grid grid-rows-3 grid-cols-[1fr] justify-center items-center h-screen bg-base">
|
||||
<div class="flex h-full p-2.5 justify-between">
|
||||
<div>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<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}}
|
||||
<div class="text-subtle flex items-center">
|
||||
<span class="mr-2 flex items-center">
|
||||
<div class="weather-data">
|
||||
<span>
|
||||
{{{WeatherData.Icon}}}
|
||||
</span>
|
||||
<div class="font-semibold">
|
||||
<p>{{WeatherData.Temp}}°C</p>
|
||||
<p>{{WeatherData.Desc}}</p>
|
||||
<p class="leading-condensed">{{WeatherData.Temp}}°C</p>
|
||||
<p class="leading-condensed">{{WeatherData.Desc}}</p>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div>
|
||||
{{#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}}
|
||||
<div class="flex items-center">
|
||||
<span class="mr-2 flex items-center">
|
||||
{{{this.FriendlyName}}}
|
||||
<div>
|
||||
<span>
|
||||
{{this.FriendlyName}}
|
||||
</span>
|
||||
<div class="relative my-auto size-2">
|
||||
<div class="relative my-auto size-2 flex-shrink-0 flex-grow-0">
|
||||
<svg class="absolute w-full h-full animate-ping" viewBox="0 0 10 10">
|
||||
<circle cx="5" cy="5" r="5"
|
||||
class="fill-current {{#if (eq this.Status 2)}} text-success {{else}} text-error {{/if}}">
|
||||
</circle>
|
||||
</svg>
|
||||
<svg class="relative w-full h-full" viewBox="0 0 10 10">
|
||||
<circle cx="5" cy="5" r="5"
|
||||
class="fill-current {{#if (eq this.Status 2)}} text-success {{else}} text-error {{/if}}">
|
||||
</circle>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="uptime-status">
|
||||
<svg viewBox="0 0 10 10">
|
||||
<use href="#status-dot" class="{{#if this.Up}}text-success{{else}}text-error{{/if}}">
|
||||
</use>
|
||||
</svg>
|
||||
<svg viewBox="0 0 10 10">
|
||||
<use href="#status-dot" class="{{#if this.Up}}text-success{{else}}text-error{{/if}}">
|
||||
</use>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row-start-2 flex flex-col items-center w-full px-6">
|
||||
<div class="flex items-center pb-2.5">
|
||||
<svg class="mr-3 aspect-square w-[clamp(42px,10vw,60px)]" viewBox="0 0 100 100" fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="12.1483" y="24.7693" width="70" height="47" rx="12" transform="rotate(14.63 12.1483 24.7693)"
|
||||
fill="url(#paint0_linear_20_10)" />
|
||||
<path fill-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"
|
||||
fill="url(#paint1_linear_20_10)" />
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_20_10" x1="12.359" y1="44.8681" x2="82.491" y2="48.2607"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#F0389B" />
|
||||
<stop offset="1" stop-color="#EEE740" />
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_20_10" x1="33.8935" y1="25.6926" x2="94.2236" y2="61.6131"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#AA38F0" />
|
||||
<stop offset="1" stop-color="#EE406A" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
<h1>Passport</h1>
|
||||
<div class="primary-hero-container">
|
||||
<div>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 100 100">
|
||||
<rect width="70" height="47" x="12.1" y="24.8" fill="url(#a)" rx="12"
|
||||
transform="rotate(14.6 12.1 24.8)" />
|
||||
<path fill="url(#b)" fill-rule="evenodd"
|
||||
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"
|
||||
clip-rule="evenodd" />
|
||||
<defs>
|
||||
<linearGradient id="a" x1="12.4" x2="82.5" y1="44.9" y2="48.3" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#f0389b" />
|
||||
<stop offset="1" stop-color="#eee740" />
|
||||
</linearGradient>
|
||||
<linearGradient id="b" x1="33.9" x2="94.2" y1="25.7" y2="61.6" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#aa38f0" />
|
||||
<stop offset="1" stop-color="#ee406a" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
<h1>Passport</h1>
|
||||
</div>
|
||||
<form action="{{ SearchProviderURL }}" method="GET">
|
||||
<input name="{{ SearchParam }}" aria-label="Search bar" placeholder="Search..." />
|
||||
</form>
|
||||
</div>
|
||||
<form class="w-full max-w-3xl" action="{{ SearchProviderURL }}" method="GET">
|
||||
<input name="{{ SearchParam }}" aria-label="Search bar"
|
||||
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"
|
||||
placeholder="Search..." />
|
||||
</form>
|
||||
</div>
|
||||
</main>
|
||||
<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" width="32" height="32" draggable="false" alt="{{this.Name}}"
|
||||
src="{{this.Icon}}" />
|
||||
<h2 class="capitalize break-all">{{this.Name}}</h2>
|
||||
</div>
|
||||
<div class="p-2.5 grid grid-cols-[repeat(auto-fill,_minmax(min(330px,_100%),_1fr))] gap-2">
|
||||
{{#each this.Links}} <a href="{{this.URL}}" class="link-card" draggable="false" target="_blank"
|
||||
rel="noopener noreferrer">
|
||||
<div>
|
||||
<img width="64" height="64" draggable="false" src="{{this.Icon}}" alt="{{this.Name}}" />
|
||||
</div>
|
||||
<div>
|
||||
<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>
|
||||
</main>
|
||||
{{> 'partials/category-grid' }}
|
||||
</body>
|
||||
|
||||
{{{devContent}}}
|
||||
|
||||
</html>
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "passport",
|
||||
"version": "0.3.0",
|
||||
"version": "0.3.5",
|
||||
"description": "Passport is a simple, lightweight, and fast dashboard/new tab page for your browser.",
|
||||
"author": "juls0730",
|
||||
"license": "BSL-1.0",
|
||||
@@ -10,9 +10,16 @@
|
||||
"url": "https://github.com/juls0730/passport.git"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "go generate ./src/; PASSPORT_DEV_MODE=true go run src/main.go",
|
||||
"build": "go generate ./src/ && go build -tags netgo,prod -ldflags=\"-w -s\" -o passport src/main.go"
|
||||
"generate": "go generate ./src/",
|
||||
"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 && upx passport",
|
||||
"build:all": "zqdgr build && zqdgr build:arm64 && zqdgr build:amd64",
|
||||
"build:amd64": "zqdgr generate && GOOS=linux GOARCH=amd64 go build -tags netgo,prod -ldflags=\"-w -s\" -o passport-linux-amd64 src/main.go && upx passport-linux-amd64",
|
||||
"build:arm64": "zqdgr generate && GOOS=linux GOARCH=arm64 go build -tags netgo,prod -ldflags=\"-w -s\" -o passport-linux-arm64 src/main.go && upx passport-linux-arm64"
|
||||
},
|
||||
"pattern": "src/**/*.{go,hbs,scss,svg,png,jpg,jpeg,webp,woff2,ico,webp}",
|
||||
"pattern": "src/**/*.{go,hbs,css,js,svg,png,jpg,jpeg,webp,woff2,ico,webp}",
|
||||
"excluded_globs": [
|
||||
"src/assets/styles"
|
||||
],
|
||||
"shutdown_signal": "SIGINT"
|
||||
}
|
||||
Reference in New Issue
Block a user