Compare commits
1 Commits
v0.3.3
...
71fcbbd6f4
| Author | SHA1 | Date | |
|---|---|---|---|
|
71fcbbd6f4
|
18
.github/workflows/docker-publish.yml
vendored
18
.github/workflows/docker-publish.yml
vendored
@@ -1,7 +1,7 @@
|
||||
name: Build and Push Docker Image to GHCR
|
||||
|
||||
env:
|
||||
OCI_TOKEN: ${{ secrets.OCI_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
OCI_REGISTRY: ghcr.io
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -10,8 +10,6 @@ on:
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
env:
|
||||
RUNNER_TOOL_CACHE: /toolcache
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
@@ -25,22 +23,15 @@ jobs:
|
||||
- name: Log in to GHCR
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ vars.OCI_REGISTRY || 'ghcr.io' }}
|
||||
registry: ${{ OCI_REGISTRY }}
|
||||
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
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ vars.OCI_REGISTRY || 'ghcr.io' }}/${{ github.repository }}
|
||||
images: ${{ OCI_REGISTRY }}/${{ github.repository }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v6
|
||||
@@ -49,4 +40,3 @@ jobs:
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -5,7 +5,4 @@ public
|
||||
zqdgr
|
||||
|
||||
# compiled via go prepare
|
||||
src/assets/styles/*
|
||||
src/assets/tailwind.css
|
||||
|
||||
node_modules
|
||||
assets/tailwind.css
|
||||
19
.prettierrc
19
.prettierrc
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"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
|
||||
}
|
||||
26
Dockerfile
26
Dockerfile
@@ -1,38 +1,22 @@
|
||||
FROM golang:1.25 AS builder
|
||||
|
||||
# build dependencies
|
||||
RUN apt update && apt install -y upx unzip
|
||||
|
||||
RUN curl -fsSL https://bun.com/install | BUN_INSTALL=/usr bash
|
||||
|
||||
ARG TARGETARCH
|
||||
RUN set -eux; \
|
||||
echo "Building for architecture: ${TARGETARCH}"; \
|
||||
case "${TARGETARCH}" in \
|
||||
"amd64") \
|
||||
arch_suffix='x64' ;; \
|
||||
"arm64") \
|
||||
arch_suffix='arm64' ;; \
|
||||
*) \
|
||||
echo "Unsupported architecture: ${TARGETARCH}" && exit 1 ;; \
|
||||
esac; \
|
||||
curl -sLO "https://github.com/tailwindlabs/tailwindcss/releases/download/v4.1.13/tailwindcss-linux-${arch_suffix}"; \
|
||||
mv "tailwindcss-linux-${arch_suffix}" /usr/local/bin/tailwindcss; \
|
||||
chmod +x /usr/local/bin/tailwindcss;
|
||||
RUN apt update && apt install -y upx
|
||||
|
||||
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 go install github.com/juls0730/zqdgr@latest
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
ARG TARGETARCH
|
||||
ENV CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH}
|
||||
ENV CGO_ENABLED=0 GOOS=linux GOARCH=amd64
|
||||
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
COPY . .
|
||||
|
||||
RUN bun install
|
||||
|
||||
RUN zqdgr build
|
||||
RUN upx passport
|
||||
|
||||
51
README.md
51
README.md
@@ -10,16 +10,16 @@ Passport is a simple, fast, and lightweight web dashboard/new tab replacement.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- [ZQDGR](https://github.com/juls0730/zqdgr)
|
||||
- [Go](https://go.dev/doc/install)
|
||||
- [sqlite3](https://www.sqlite.org/download.html)
|
||||
- [TailwdinCSS CLI](https://github.com/tailwindlabs/tailwindcss/releases/latest)
|
||||
- [ZQDGR](https://github.com/juls0730/zqdgr)
|
||||
- [Go](https://go.dev/doc/install)
|
||||
- [sqlite3](https://www.sqlite.org/download.html)
|
||||
- [TailwdinCSS CLI](https://github.com/tailwindlabs/tailwindcss/releases/latest)
|
||||
|
||||
## Usage
|
||||
|
||||
### Docker
|
||||
|
||||
Passport is available as a Docker image via this repository for both amd64 and arm64. This is the recommended way to run Passport.
|
||||
Passport is available as a Docker image via this repository. 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,11 +35,9 @@ 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
|
||||
|
||||
# install bun
|
||||
curl -fsSL https://bun.sh/install | bash
|
||||
|
||||
# install css build deps
|
||||
bun install
|
||||
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
|
||||
|
||||
# you may also have to install sqlite3...
|
||||
```
|
||||
@@ -60,35 +58,34 @@ You can then run the binary.
|
||||
| -------------------------------------- | ------------------------------------------------------------------------------- | -------- | ------- |
|
||||
| `PASSPORT_DEV_MODE` | Enables dev mode | false | false |
|
||||
| `PASSPORT_ENABLE_PREFORK` | Enables preforking | false | false |
|
||||
| `PASSPORT_ENABLE_WEATHER` | Enables weather data, see [Weather configuration](#weather-configuration) | false | false |
|
||||
| `PASSPORT_ENABLE_UPTIME` | Enables uptime data, see [Uptime configuration](#uptime-configuration) | false | false |
|
||||
| `PASSPORT_ADMIN_USERNAME` | The username for the admin dashboard | true |
|
||||
| `PASSPORT_ADMIN_PASSWORD` | The password for the admin dashboard | true |
|
||||
| `PASSPORT_SEARCH_PROVIDER` | The search provider to use for the search bar, without any query parameters | true |
|
||||
| `PASSPORT_SEARCH_PROVIDER_QUERY_PARAM` | The query parameter to use for the search provider, e.g. `q` for most providers | false | q |
|
||||
|
||||
> [!NOTE]
|
||||
> Currently passport only supports search using a GET request.
|
||||
|
||||
#### Weather configuration
|
||||
|
||||
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.
|
||||
The following only applies if you are using the OpenWeather 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 |
|
||||
| ----------------------------- | ------------------------------------------------------------------------- | -------- | -------------- |
|
||||
| `OPENWEATHER_PROVIDER` | The weather provider to use, currently only `openweathermap` is supported | true | openweathermap |
|
||||
| `OPENWEATHER_API_KEY` | The OpenWeather API key | true | |
|
||||
| `OPENWEATHER_TEMP_UNITS` | The temperature units to use, either `metric` or `imperial` | false | metric |
|
||||
| `OPENWEATHER_LAT` | The latitude of your location | true | |
|
||||
| `OPENWEATHER_LON` | The longitude of your location | true | |
|
||||
| `OPENWEATHER_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.
|
||||
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 |
|
||||
| ----------------------------- | ------------------------------------------------- | -------- | ------- |
|
||||
| `UPTIMEROBOT_API_KEY` | The UptimeRobot API key | true | |
|
||||
| `UPTIMEROBOT_UPDATE_INTERVAL` | The interval in seconds to update the uptime data | false | 300 |
|
||||
|
||||
### Adding links and categories
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
BIN
assets/fonts/InstrumentSans-VariableFont_wdth,wght.woff2
Normal file
BIN
assets/fonts/InstrumentSans-VariableFont_wdth,wght.woff2
Normal file
Binary file not shown.
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
527
bun.lock
527
bun.lock
@@ -1,527 +0,0 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "passport-css-compiler",
|
||||
"devDependencies": {
|
||||
"@fullhuman/postcss-purgecss": "^7.0.2",
|
||||
"cssnano": "^7.1.1",
|
||||
"postcss": "^8.4.35",
|
||||
"postcss-cli": "^11.0.0",
|
||||
"postcss-import": "^16.1.1",
|
||||
"postcss-preset-env": "^10.4.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@csstools/cascade-layer-name-parser": ["@csstools/cascade-layer-name-parser@2.0.5", "", { "peerDependencies": { "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4" } }, "sha512-p1ko5eHgV+MgXFVa4STPKpvPxr6ReS8oS2jzTukjR74i5zJNyWO1ZM1m8YKBXnzDKWfBN1ztLYlHxbVemDD88A=="],
|
||||
|
||||
"@csstools/color-helpers": ["@csstools/color-helpers@5.1.0", "", {}, "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA=="],
|
||||
|
||||
"@csstools/css-calc": ["@csstools/css-calc@2.1.4", "", { "peerDependencies": { "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4" } }, "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ=="],
|
||||
|
||||
"@csstools/css-color-parser": ["@csstools/css-color-parser@3.1.0", "", { "dependencies": { "@csstools/color-helpers": "^5.1.0", "@csstools/css-calc": "^2.1.4" }, "peerDependencies": { "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4" } }, "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA=="],
|
||||
|
||||
"@csstools/css-parser-algorithms": ["@csstools/css-parser-algorithms@3.0.5", "", { "peerDependencies": { "@csstools/css-tokenizer": "^3.0.4" } }, "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ=="],
|
||||
|
||||
"@csstools/css-tokenizer": ["@csstools/css-tokenizer@3.0.4", "", {}, "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw=="],
|
||||
|
||||
"@csstools/media-query-list-parser": ["@csstools/media-query-list-parser@4.0.3", "", { "peerDependencies": { "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4" } }, "sha512-HAYH7d3TLRHDOUQK4mZKf9k9Ph/m8Akstg66ywKR4SFAigjs3yBiUeZtFxywiTm5moZMAp/5W/ZuFnNXXYLuuQ=="],
|
||||
|
||||
"@csstools/postcss-alpha-function": ["@csstools/postcss-alpha-function@1.0.1", "", { "dependencies": { "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", "@csstools/postcss-progressive-custom-properties": "^4.2.1", "@csstools/utilities": "^2.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-isfLLwksH3yHkFXfCI2Gcaqg7wGGHZZwunoJzEZk0yKYIokgre6hYVFibKL3SYAoR1kBXova8LB+JoO5vZzi9w=="],
|
||||
|
||||
"@csstools/postcss-cascade-layers": ["@csstools/postcss-cascade-layers@5.0.2", "", { "dependencies": { "@csstools/selector-specificity": "^5.0.0", "postcss-selector-parser": "^7.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-nWBE08nhO8uWl6kSAeCx4im7QfVko3zLrtgWZY4/bP87zrSPpSyN/3W3TDqz1jJuH+kbKOHXg5rJnK+ZVYcFFg=="],
|
||||
|
||||
"@csstools/postcss-color-function": ["@csstools/postcss-color-function@4.0.12", "", { "dependencies": { "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", "@csstools/postcss-progressive-custom-properties": "^4.2.1", "@csstools/utilities": "^2.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-yx3cljQKRaSBc2hfh8rMZFZzChaFgwmO2JfFgFr1vMcF3C/uyy5I4RFIBOIWGq1D+XbKCG789CGkG6zzkLpagA=="],
|
||||
|
||||
"@csstools/postcss-color-function-display-p3-linear": ["@csstools/postcss-color-function-display-p3-linear@1.0.1", "", { "dependencies": { "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", "@csstools/postcss-progressive-custom-properties": "^4.2.1", "@csstools/utilities": "^2.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-E5qusdzhlmO1TztYzDIi8XPdPoYOjoTY6HBYBCYSj+Gn4gQRBlvjgPQXzfzuPQqt8EhkC/SzPKObg4Mbn8/xMg=="],
|
||||
|
||||
"@csstools/postcss-color-mix-function": ["@csstools/postcss-color-mix-function@3.0.12", "", { "dependencies": { "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", "@csstools/postcss-progressive-custom-properties": "^4.2.1", "@csstools/utilities": "^2.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-4STERZfCP5Jcs13P1U5pTvI9SkgLgfMUMhdXW8IlJWkzOOOqhZIjcNhWtNJZes2nkBDsIKJ0CJtFtuaZ00moag=="],
|
||||
|
||||
"@csstools/postcss-color-mix-variadic-function-arguments": ["@csstools/postcss-color-mix-variadic-function-arguments@1.0.2", "", { "dependencies": { "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", "@csstools/postcss-progressive-custom-properties": "^4.2.1", "@csstools/utilities": "^2.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-rM67Gp9lRAkTo+X31DUqMEq+iK+EFqsidfecmhrteErxJZb6tUoJBVQca1Vn1GpDql1s1rD1pKcuYzMsg7Z1KQ=="],
|
||||
|
||||
"@csstools/postcss-content-alt-text": ["@csstools/postcss-content-alt-text@2.0.8", "", { "dependencies": { "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", "@csstools/postcss-progressive-custom-properties": "^4.2.1", "@csstools/utilities": "^2.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-9SfEW9QCxEpTlNMnpSqFaHyzsiRpZ5J5+KqCu1u5/eEJAWsMhzT40qf0FIbeeglEvrGRMdDzAxMIz3wqoGSb+Q=="],
|
||||
|
||||
"@csstools/postcss-contrast-color-function": ["@csstools/postcss-contrast-color-function@2.0.12", "", { "dependencies": { "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", "@csstools/postcss-progressive-custom-properties": "^4.2.1", "@csstools/utilities": "^2.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-YbwWckjK3qwKjeYz/CijgcS7WDUCtKTd8ShLztm3/i5dhh4NaqzsbYnhm4bjrpFpnLZ31jVcbK8YL77z3GBPzA=="],
|
||||
|
||||
"@csstools/postcss-exponential-functions": ["@csstools/postcss-exponential-functions@2.0.9", "", { "dependencies": { "@csstools/css-calc": "^2.1.4", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-abg2W/PI3HXwS/CZshSa79kNWNZHdJPMBXeZNyPQFbbj8sKO3jXxOt/wF7juJVjyDTc6JrvaUZYFcSBZBhaxjw=="],
|
||||
|
||||
"@csstools/postcss-font-format-keywords": ["@csstools/postcss-font-format-keywords@4.0.0", "", { "dependencies": { "@csstools/utilities": "^2.0.0", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-usBzw9aCRDvchpok6C+4TXC57btc4bJtmKQWOHQxOVKen1ZfVqBUuCZ/wuqdX5GHsD0NRSr9XTP+5ID1ZZQBXw=="],
|
||||
|
||||
"@csstools/postcss-gamut-mapping": ["@csstools/postcss-gamut-mapping@2.0.11", "", { "dependencies": { "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-fCpCUgZNE2piVJKC76zFsgVW1apF6dpYsqGyH8SIeCcM4pTEsRTWTLCaJIMKFEundsCKwY1rwfhtrio04RJ4Dw=="],
|
||||
|
||||
"@csstools/postcss-gradients-interpolation-method": ["@csstools/postcss-gradients-interpolation-method@5.0.12", "", { "dependencies": { "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", "@csstools/postcss-progressive-custom-properties": "^4.2.1", "@csstools/utilities": "^2.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-jugzjwkUY0wtNrZlFeyXzimUL3hN4xMvoPnIXxoZqxDvjZRiSh+itgHcVUWzJ2VwD/VAMEgCLvtaJHX+4Vj3Ow=="],
|
||||
|
||||
"@csstools/postcss-hwb-function": ["@csstools/postcss-hwb-function@4.0.12", "", { "dependencies": { "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", "@csstools/postcss-progressive-custom-properties": "^4.2.1", "@csstools/utilities": "^2.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-mL/+88Z53KrE4JdePYFJAQWFrcADEqsLprExCM04GDNgHIztwFzj0Mbhd/yxMBngq0NIlz58VVxjt5abNs1VhA=="],
|
||||
|
||||
"@csstools/postcss-ic-unit": ["@csstools/postcss-ic-unit@4.0.4", "", { "dependencies": { "@csstools/postcss-progressive-custom-properties": "^4.2.1", "@csstools/utilities": "^2.0.0", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-yQ4VmossuOAql65sCPppVO1yfb7hDscf4GseF0VCA/DTDaBc0Wtf8MTqVPfjGYlT5+2buokG0Gp7y0atYZpwjg=="],
|
||||
|
||||
"@csstools/postcss-initial": ["@csstools/postcss-initial@2.0.1", "", { "peerDependencies": { "postcss": "^8.4" } }, "sha512-L1wLVMSAZ4wovznquK0xmC7QSctzO4D0Is590bxpGqhqjboLXYA16dWZpfwImkdOgACdQ9PqXsuRroW6qPlEsg=="],
|
||||
|
||||
"@csstools/postcss-is-pseudo-class": ["@csstools/postcss-is-pseudo-class@5.0.3", "", { "dependencies": { "@csstools/selector-specificity": "^5.0.0", "postcss-selector-parser": "^7.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-jS/TY4SpG4gszAtIg7Qnf3AS2pjcUM5SzxpApOrlndMeGhIbaTzWBzzP/IApXoNWEW7OhcjkRT48jnAUIFXhAQ=="],
|
||||
|
||||
"@csstools/postcss-light-dark-function": ["@csstools/postcss-light-dark-function@2.0.11", "", { "dependencies": { "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", "@csstools/postcss-progressive-custom-properties": "^4.2.1", "@csstools/utilities": "^2.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-fNJcKXJdPM3Lyrbmgw2OBbaioU7yuKZtiXClf4sGdQttitijYlZMD5K7HrC/eF83VRWRrYq6OZ0Lx92leV2LFA=="],
|
||||
|
||||
"@csstools/postcss-logical-float-and-clear": ["@csstools/postcss-logical-float-and-clear@3.0.0", "", { "peerDependencies": { "postcss": "^8.4" } }, "sha512-SEmaHMszwakI2rqKRJgE+8rpotFfne1ZS6bZqBoQIicFyV+xT1UF42eORPxJkVJVrH9C0ctUgwMSn3BLOIZldQ=="],
|
||||
|
||||
"@csstools/postcss-logical-overflow": ["@csstools/postcss-logical-overflow@2.0.0", "", { "peerDependencies": { "postcss": "^8.4" } }, "sha512-spzR1MInxPuXKEX2csMamshR4LRaSZ3UXVaRGjeQxl70ySxOhMpP2252RAFsg8QyyBXBzuVOOdx1+bVO5bPIzA=="],
|
||||
|
||||
"@csstools/postcss-logical-overscroll-behavior": ["@csstools/postcss-logical-overscroll-behavior@2.0.0", "", { "peerDependencies": { "postcss": "^8.4" } }, "sha512-e/webMjoGOSYfqLunyzByZj5KKe5oyVg/YSbie99VEaSDE2kimFm0q1f6t/6Jo+VVCQ/jbe2Xy+uX+C4xzWs4w=="],
|
||||
|
||||
"@csstools/postcss-logical-resize": ["@csstools/postcss-logical-resize@3.0.0", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-DFbHQOFW/+I+MY4Ycd/QN6Dg4Hcbb50elIJCfnwkRTCX05G11SwViI5BbBlg9iHRl4ytB7pmY5ieAFk3ws7yyg=="],
|
||||
|
||||
"@csstools/postcss-logical-viewport-units": ["@csstools/postcss-logical-viewport-units@3.0.4", "", { "dependencies": { "@csstools/css-tokenizer": "^3.0.4", "@csstools/utilities": "^2.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-q+eHV1haXA4w9xBwZLKjVKAWn3W2CMqmpNpZUk5kRprvSiBEGMgrNH3/sJZ8UA3JgyHaOt3jwT9uFa4wLX4EqQ=="],
|
||||
|
||||
"@csstools/postcss-media-minmax": ["@csstools/postcss-media-minmax@2.0.9", "", { "dependencies": { "@csstools/css-calc": "^2.1.4", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", "@csstools/media-query-list-parser": "^4.0.3" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-af9Qw3uS3JhYLnCbqtZ9crTvvkR+0Se+bBqSr7ykAnl9yKhk6895z9rf+2F4dClIDJWxgn0iZZ1PSdkhrbs2ig=="],
|
||||
|
||||
"@csstools/postcss-media-queries-aspect-ratio-number-values": ["@csstools/postcss-media-queries-aspect-ratio-number-values@3.0.5", "", { "dependencies": { "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", "@csstools/media-query-list-parser": "^4.0.3" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-zhAe31xaaXOY2Px8IYfoVTB3wglbJUVigGphFLj6exb7cjZRH9A6adyE22XfFK3P2PzwRk0VDeTJmaxpluyrDg=="],
|
||||
|
||||
"@csstools/postcss-nested-calc": ["@csstools/postcss-nested-calc@4.0.0", "", { "dependencies": { "@csstools/utilities": "^2.0.0", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-jMYDdqrQQxE7k9+KjstC3NbsmC063n1FTPLCgCRS2/qHUbHM0mNy9pIn4QIiQGs9I/Bg98vMqw7mJXBxa0N88A=="],
|
||||
|
||||
"@csstools/postcss-normalize-display-values": ["@csstools/postcss-normalize-display-values@4.0.0", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-HlEoG0IDRoHXzXnkV4in47dzsxdsjdz6+j7MLjaACABX2NfvjFS6XVAnpaDyGesz9gK2SC7MbNwdCHusObKJ9Q=="],
|
||||
|
||||
"@csstools/postcss-oklab-function": ["@csstools/postcss-oklab-function@4.0.12", "", { "dependencies": { "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", "@csstools/postcss-progressive-custom-properties": "^4.2.1", "@csstools/utilities": "^2.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-HhlSmnE1NKBhXsTnNGjxvhryKtO7tJd1w42DKOGFD6jSHtYOrsJTQDKPMwvOfrzUAk8t7GcpIfRyM7ssqHpFjg=="],
|
||||
|
||||
"@csstools/postcss-progressive-custom-properties": ["@csstools/postcss-progressive-custom-properties@4.2.1", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-uPiiXf7IEKtUQXsxu6uWtOlRMXd2QWWy5fhxHDnPdXKCQckPP3E34ZgDoZ62r2iT+UOgWsSbM4NvHE5m3mAEdw=="],
|
||||
|
||||
"@csstools/postcss-random-function": ["@csstools/postcss-random-function@2.0.1", "", { "dependencies": { "@csstools/css-calc": "^2.1.4", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-q+FQaNiRBhnoSNo+GzqGOIBKoHQ43lYz0ICrV+UudfWnEF6ksS6DsBIJSISKQT2Bvu3g4k6r7t0zYrk5pDlo8w=="],
|
||||
|
||||
"@csstools/postcss-relative-color-syntax": ["@csstools/postcss-relative-color-syntax@3.0.12", "", { "dependencies": { "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", "@csstools/postcss-progressive-custom-properties": "^4.2.1", "@csstools/utilities": "^2.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-0RLIeONxu/mtxRtf3o41Lq2ghLimw0w9ByLWnnEVuy89exmEEq8bynveBxNW3nyHqLAFEeNtVEmC1QK9MZ8Huw=="],
|
||||
|
||||
"@csstools/postcss-scope-pseudo-class": ["@csstools/postcss-scope-pseudo-class@4.0.1", "", { "dependencies": { "postcss-selector-parser": "^7.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-IMi9FwtH6LMNuLea1bjVMQAsUhFxJnyLSgOp/cpv5hrzWmrUYU5fm0EguNDIIOHUqzXode8F/1qkC/tEo/qN8Q=="],
|
||||
|
||||
"@csstools/postcss-sign-functions": ["@csstools/postcss-sign-functions@1.1.4", "", { "dependencies": { "@csstools/css-calc": "^2.1.4", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-P97h1XqRPcfcJndFdG95Gv/6ZzxUBBISem0IDqPZ7WMvc/wlO+yU0c5D/OCpZ5TJoTt63Ok3knGk64N+o6L2Pg=="],
|
||||
|
||||
"@csstools/postcss-stepped-value-functions": ["@csstools/postcss-stepped-value-functions@4.0.9", "", { "dependencies": { "@csstools/css-calc": "^2.1.4", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-h9btycWrsex4dNLeQfyU3y3w40LMQooJWFMm/SK9lrKguHDcFl4VMkncKKoXi2z5rM9YGWbUQABI8BT2UydIcA=="],
|
||||
|
||||
"@csstools/postcss-text-decoration-shorthand": ["@csstools/postcss-text-decoration-shorthand@4.0.3", "", { "dependencies": { "@csstools/color-helpers": "^5.1.0", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-KSkGgZfx0kQjRIYnpsD7X2Om9BUXX/Kii77VBifQW9Ih929hK0KNjVngHDH0bFB9GmfWcR9vJYJJRvw/NQjkrA=="],
|
||||
|
||||
"@csstools/postcss-trigonometric-functions": ["@csstools/postcss-trigonometric-functions@4.0.9", "", { "dependencies": { "@csstools/css-calc": "^2.1.4", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-Hnh5zJUdpNrJqK9v1/E3BbrQhaDTj5YiX7P61TOvUhoDHnUmsNNxcDAgkQ32RrcWx9GVUvfUNPcUkn8R3vIX6A=="],
|
||||
|
||||
"@csstools/postcss-unset-value": ["@csstools/postcss-unset-value@4.0.0", "", { "peerDependencies": { "postcss": "^8.4" } }, "sha512-cBz3tOCI5Fw6NIFEwU3RiwK6mn3nKegjpJuzCndoGq3BZPkUjnsq7uQmIeMNeMbMk7YD2MfKcgCpZwX5jyXqCA=="],
|
||||
|
||||
"@csstools/selector-resolve-nested": ["@csstools/selector-resolve-nested@3.1.0", "", { "peerDependencies": { "postcss-selector-parser": "^7.0.0" } }, "sha512-mf1LEW0tJLKfWyvn5KdDrhpxHyuxpbNwTIwOYLIvsTffeyOf85j5oIzfG0yosxDgx/sswlqBnESYUcQH0vgZ0g=="],
|
||||
|
||||
"@csstools/selector-specificity": ["@csstools/selector-specificity@5.0.0", "", { "peerDependencies": { "postcss-selector-parser": "^7.0.0" } }, "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw=="],
|
||||
|
||||
"@csstools/utilities": ["@csstools/utilities@2.0.0", "", { "peerDependencies": { "postcss": "^8.4" } }, "sha512-5VdOr0Z71u+Yp3ozOx8T11N703wIFGVRgOWbOZMKgglPJsWA54MRIoMNVMa7shUToIhx5J8vX4sOZgD2XiihiQ=="],
|
||||
|
||||
"@fullhuman/postcss-purgecss": ["@fullhuman/postcss-purgecss@7.0.2", "", { "dependencies": { "purgecss": "^7.0.2" }, "peerDependencies": { "postcss": "^8.0.0" } }, "sha512-U4zAXNaVztbDxO9EdcLp51F3UxxYsb/7DN89rFxFJhfk2Wua2pvw2Kf3HdspbPhW/wpHjSjsxWYoIlbTgRSjbQ=="],
|
||||
|
||||
"@isaacs/balanced-match": ["@isaacs/balanced-match@4.0.1", "", {}, "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ=="],
|
||||
|
||||
"@isaacs/brace-expansion": ["@isaacs/brace-expansion@5.0.0", "", { "dependencies": { "@isaacs/balanced-match": "^4.0.1" } }, "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA=="],
|
||||
|
||||
"@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="],
|
||||
|
||||
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||
|
||||
"anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
|
||||
|
||||
"autoprefixer": ["autoprefixer@10.4.21", "", { "dependencies": { "browserslist": "^4.24.4", "caniuse-lite": "^1.0.30001702", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ=="],
|
||||
|
||||
"baseline-browser-mapping": ["baseline-browser-mapping@2.8.10", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-uLfgBi+7IBNay8ECBO2mVMGZAc1VgZWEChxm4lv+TobGdG82LnXMjuNGo/BSSZZL4UmkWhxEHP2f5ziLNwGWMA=="],
|
||||
|
||||
"binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="],
|
||||
|
||||
"boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="],
|
||||
|
||||
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
|
||||
|
||||
"browserslist": ["browserslist@4.26.3", "", { "dependencies": { "baseline-browser-mapping": "^2.8.9", "caniuse-lite": "^1.0.30001746", "electron-to-chromium": "^1.5.227", "node-releases": "^2.0.21", "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w=="],
|
||||
|
||||
"caniuse-api": ["caniuse-api@3.0.0", "", { "dependencies": { "browserslist": "^4.0.0", "caniuse-lite": "^1.0.0", "lodash.memoize": "^4.1.2", "lodash.uniq": "^4.5.0" } }, "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw=="],
|
||||
|
||||
"caniuse-lite": ["caniuse-lite@1.0.30001746", "", {}, "sha512-eA7Ys/DGw+pnkWWSE/id29f2IcPHVoE8wxtvE5JdvD2V28VTDPy1yEeo11Guz0sJ4ZeGRcm3uaTcAqK1LXaphA=="],
|
||||
|
||||
"chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
|
||||
|
||||
"cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
|
||||
|
||||
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
|
||||
|
||||
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
|
||||
|
||||
"colord": ["colord@2.9.3", "", {}, "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw=="],
|
||||
|
||||
"commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="],
|
||||
|
||||
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
|
||||
|
||||
"css-blank-pseudo": ["css-blank-pseudo@7.0.1", "", { "dependencies": { "postcss-selector-parser": "^7.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-jf+twWGDf6LDoXDUode+nc7ZlrqfaNphrBIBrcmeP3D8yw1uPaix1gCC8LUQUGQ6CycuK2opkbFFWFuq/a94ag=="],
|
||||
|
||||
"css-declaration-sorter": ["css-declaration-sorter@7.3.0", "", { "peerDependencies": { "postcss": "^8.0.9" } }, "sha512-LQF6N/3vkAMYF4xoHLJfG718HRJh34Z8BnNhd6bosOMIVjMlhuZK5++oZa3uYAgrI5+7x2o27gUqTR2U/KjUOQ=="],
|
||||
|
||||
"css-has-pseudo": ["css-has-pseudo@7.0.3", "", { "dependencies": { "@csstools/selector-specificity": "^5.0.0", "postcss-selector-parser": "^7.0.0", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-oG+vKuGyqe/xvEMoxAQrhi7uY16deJR3i7wwhBerVrGQKSqUC5GiOVxTpM9F9B9hw0J+eKeOWLH7E9gZ1Dr5rA=="],
|
||||
|
||||
"css-prefers-color-scheme": ["css-prefers-color-scheme@10.0.0", "", { "peerDependencies": { "postcss": "^8.4" } }, "sha512-VCtXZAWivRglTZditUfB4StnsWr6YVZ2PRtuxQLKTNRdtAf8tpzaVPE9zXIF3VaSc7O70iK/j1+NXxyQCqdPjQ=="],
|
||||
|
||||
"css-select": ["css-select@5.2.2", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" } }, "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw=="],
|
||||
|
||||
"css-tree": ["css-tree@3.1.0", "", { "dependencies": { "mdn-data": "2.12.2", "source-map-js": "^1.0.1" } }, "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w=="],
|
||||
|
||||
"css-what": ["css-what@6.2.2", "", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="],
|
||||
|
||||
"cssdb": ["cssdb@8.4.2", "", {}, "sha512-PzjkRkRUS+IHDJohtxkIczlxPPZqRo0nXplsYXOMBRPjcVRjj1W4DfvRgshUYTVuUigU7ptVYkFJQ7abUB0nyg=="],
|
||||
|
||||
"cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="],
|
||||
|
||||
"cssnano": ["cssnano@7.1.1", "", { "dependencies": { "cssnano-preset-default": "^7.0.9", "lilconfig": "^3.1.3" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-fm4D8ti0dQmFPeF8DXSAA//btEmqCOgAc/9Oa3C1LW94h5usNrJEfrON7b4FkPZgnDEn6OUs5NdxiJZmAtGOpQ=="],
|
||||
|
||||
"cssnano-preset-default": ["cssnano-preset-default@7.0.9", "", { "dependencies": { "browserslist": "^4.25.1", "css-declaration-sorter": "^7.2.0", "cssnano-utils": "^5.0.1", "postcss-calc": "^10.1.1", "postcss-colormin": "^7.0.4", "postcss-convert-values": "^7.0.7", "postcss-discard-comments": "^7.0.4", "postcss-discard-duplicates": "^7.0.2", "postcss-discard-empty": "^7.0.1", "postcss-discard-overridden": "^7.0.1", "postcss-merge-longhand": "^7.0.5", "postcss-merge-rules": "^7.0.6", "postcss-minify-font-values": "^7.0.1", "postcss-minify-gradients": "^7.0.1", "postcss-minify-params": "^7.0.4", "postcss-minify-selectors": "^7.0.5", "postcss-normalize-charset": "^7.0.1", "postcss-normalize-display-values": "^7.0.1", "postcss-normalize-positions": "^7.0.1", "postcss-normalize-repeat-style": "^7.0.1", "postcss-normalize-string": "^7.0.1", "postcss-normalize-timing-functions": "^7.0.1", "postcss-normalize-unicode": "^7.0.4", "postcss-normalize-url": "^7.0.1", "postcss-normalize-whitespace": "^7.0.1", "postcss-ordered-values": "^7.0.2", "postcss-reduce-initial": "^7.0.4", "postcss-reduce-transforms": "^7.0.1", "postcss-svgo": "^7.1.0", "postcss-unique-selectors": "^7.0.4" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-tCD6AAFgYBOVpMBX41KjbvRh9c2uUjLXRyV7KHSIrwHiq5Z9o0TFfUCoM3TwVrRsRteN3sVXGNvjVNxYzkpTsA=="],
|
||||
|
||||
"cssnano-utils": ["cssnano-utils@5.0.1", "", { "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-ZIP71eQgG9JwjVZsTPSqhc6GHgEr53uJ7tK5///VfyWj6Xp2DBmixWHqJgPno+PqATzn48pL42ww9x5SSGmhZg=="],
|
||||
|
||||
"csso": ["csso@5.0.5", "", { "dependencies": { "css-tree": "~2.2.0" } }, "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ=="],
|
||||
|
||||
"dependency-graph": ["dependency-graph@1.0.0", "", {}, "sha512-cW3gggJ28HZ/LExwxP2B++aiKxhJXMSIt9K48FOXQkm+vuG5gyatXnLsONRJdzO/7VfjDIiaOOa/bs4l464Lwg=="],
|
||||
|
||||
"dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="],
|
||||
|
||||
"domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="],
|
||||
|
||||
"domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="],
|
||||
|
||||
"domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="],
|
||||
|
||||
"eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="],
|
||||
|
||||
"electron-to-chromium": ["electron-to-chromium@1.5.229", "", {}, "sha512-cwhDcZKGcT/rEthLRJ9eBlMDkh1sorgsuk+6dpsehV0g9CABsIqBxU4rLRjG+d/U6pYU1s37A4lSKrVc5lSQYg=="],
|
||||
|
||||
"emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
|
||||
"entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
|
||||
|
||||
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
||||
|
||||
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
||||
|
||||
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
|
||||
|
||||
"foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="],
|
||||
|
||||
"fraction.js": ["fraction.js@4.3.7", "", {}, "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew=="],
|
||||
|
||||
"fs-extra": ["fs-extra@11.3.2", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A=="],
|
||||
|
||||
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||
|
||||
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
|
||||
|
||||
"get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
|
||||
|
||||
"glob": ["glob@11.0.3", "", { "dependencies": { "foreground-child": "^3.3.1", "jackspeak": "^4.1.1", "minimatch": "^10.0.3", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA=="],
|
||||
|
||||
"glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
||||
|
||||
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
||||
|
||||
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
|
||||
|
||||
"is-binary-path": ["is-binary-path@2.1.0", "", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="],
|
||||
|
||||
"is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="],
|
||||
|
||||
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
|
||||
|
||||
"is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
|
||||
|
||||
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
|
||||
|
||||
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
|
||||
|
||||
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
||||
|
||||
"jackspeak": ["jackspeak@4.1.1", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" } }, "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ=="],
|
||||
|
||||
"jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="],
|
||||
|
||||
"lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="],
|
||||
|
||||
"lodash.memoize": ["lodash.memoize@4.1.2", "", {}, "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag=="],
|
||||
|
||||
"lodash.uniq": ["lodash.uniq@4.5.0", "", {}, "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ=="],
|
||||
|
||||
"lru-cache": ["lru-cache@11.2.2", "", {}, "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg=="],
|
||||
|
||||
"mdn-data": ["mdn-data@2.12.2", "", {}, "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA=="],
|
||||
|
||||
"minimatch": ["minimatch@10.0.3", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw=="],
|
||||
|
||||
"minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
|
||||
|
||||
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
|
||||
|
||||
"node-releases": ["node-releases@2.0.21", "", {}, "sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw=="],
|
||||
|
||||
"normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
|
||||
|
||||
"normalize-range": ["normalize-range@0.1.2", "", {}, "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA=="],
|
||||
|
||||
"nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="],
|
||||
|
||||
"package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="],
|
||||
|
||||
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
|
||||
|
||||
"path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
|
||||
|
||||
"path-scurry": ["path-scurry@2.0.0", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg=="],
|
||||
|
||||
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||
|
||||
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
|
||||
|
||||
"pify": ["pify@2.3.0", "", {}, "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog=="],
|
||||
|
||||
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
|
||||
|
||||
"postcss-attribute-case-insensitive": ["postcss-attribute-case-insensitive@7.0.1", "", { "dependencies": { "postcss-selector-parser": "^7.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-Uai+SupNSqzlschRyNx3kbCTWgY/2hcwtHEI/ej2LJWc9JJ77qKgGptd8DHwY1mXtZ7Aoh4z4yxfwMBue9eNgw=="],
|
||||
|
||||
"postcss-calc": ["postcss-calc@10.1.1", "", { "dependencies": { "postcss-selector-parser": "^7.0.0", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4.38" } }, "sha512-NYEsLHh8DgG/PRH2+G9BTuUdtf9ViS+vdoQ0YA5OQdGsfN4ztiwtDWNtBl9EKeqNMFnIu8IKZ0cLxEQ5r5KVMw=="],
|
||||
|
||||
"postcss-clamp": ["postcss-clamp@4.1.0", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4.6" } }, "sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow=="],
|
||||
|
||||
"postcss-cli": ["postcss-cli@11.0.1", "", { "dependencies": { "chokidar": "^3.3.0", "dependency-graph": "^1.0.0", "fs-extra": "^11.0.0", "picocolors": "^1.0.0", "postcss-load-config": "^5.0.0", "postcss-reporter": "^7.0.0", "pretty-hrtime": "^1.0.3", "read-cache": "^1.0.0", "slash": "^5.0.0", "tinyglobby": "^0.2.12", "yargs": "^17.0.0" }, "peerDependencies": { "postcss": "^8.0.0" }, "bin": { "postcss": "index.js" } }, "sha512-0UnkNPSayHKRe/tc2YGW6XnSqqOA9eqpiRMgRlV1S6HdGi16vwJBx7lviARzbV1HpQHqLLRH3o8vTcB0cLc+5g=="],
|
||||
|
||||
"postcss-color-functional-notation": ["postcss-color-functional-notation@7.0.12", "", { "dependencies": { "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", "@csstools/postcss-progressive-custom-properties": "^4.2.1", "@csstools/utilities": "^2.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-TLCW9fN5kvO/u38/uesdpbx3e8AkTYhMvDZYa9JpmImWuTE99bDQ7GU7hdOADIZsiI9/zuxfAJxny/khknp1Zw=="],
|
||||
|
||||
"postcss-color-hex-alpha": ["postcss-color-hex-alpha@10.0.0", "", { "dependencies": { "@csstools/utilities": "^2.0.0", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-1kervM2cnlgPs2a8Vt/Qbe5cQ++N7rkYo/2rz2BkqJZIHQwaVuJgQH38REHrAi4uM0b1fqxMkWYmese94iMp3w=="],
|
||||
|
||||
"postcss-color-rebeccapurple": ["postcss-color-rebeccapurple@10.0.0", "", { "dependencies": { "@csstools/utilities": "^2.0.0", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-JFta737jSP+hdAIEhk1Vs0q0YF5P8fFcj+09pweS8ktuGuZ8pPlykHsk6mPxZ8awDl4TrcxUqJo9l1IhVr/OjQ=="],
|
||||
|
||||
"postcss-colormin": ["postcss-colormin@7.0.4", "", { "dependencies": { "browserslist": "^4.25.1", "caniuse-api": "^3.0.0", "colord": "^2.9.3", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-ziQuVzQZBROpKpfeDwmrG+Vvlr0YWmY/ZAk99XD+mGEBuEojoFekL41NCsdhyNUtZI7DPOoIWIR7vQQK9xwluw=="],
|
||||
|
||||
"postcss-convert-values": ["postcss-convert-values@7.0.7", "", { "dependencies": { "browserslist": "^4.25.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-HR9DZLN04Xbe6xugRH6lS4ZQH2zm/bFh/ZyRkpedZozhvh+awAfbA0P36InO4fZfDhvYfNJeNvlTf1sjwGbw/A=="],
|
||||
|
||||
"postcss-custom-media": ["postcss-custom-media@11.0.6", "", { "dependencies": { "@csstools/cascade-layer-name-parser": "^2.0.5", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", "@csstools/media-query-list-parser": "^4.0.3" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-C4lD4b7mUIw+RZhtY7qUbf4eADmb7Ey8BFA2px9jUbwg7pjTZDl4KY4bvlUV+/vXQvzQRfiGEVJyAbtOsCMInw=="],
|
||||
|
||||
"postcss-custom-properties": ["postcss-custom-properties@14.0.6", "", { "dependencies": { "@csstools/cascade-layer-name-parser": "^2.0.5", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", "@csstools/utilities": "^2.0.0", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-fTYSp3xuk4BUeVhxCSJdIPhDLpJfNakZKoiTDx7yRGCdlZrSJR7mWKVOBS4sBF+5poPQFMj2YdXx1VHItBGihQ=="],
|
||||
|
||||
"postcss-custom-selectors": ["postcss-custom-selectors@8.0.5", "", { "dependencies": { "@csstools/cascade-layer-name-parser": "^2.0.5", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", "postcss-selector-parser": "^7.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-9PGmckHQswiB2usSO6XMSswO2yFWVoCAuih1yl9FVcwkscLjRKjwsjM3t+NIWpSU2Jx3eOiK2+t4vVTQaoCHHg=="],
|
||||
|
||||
"postcss-dir-pseudo-class": ["postcss-dir-pseudo-class@9.0.1", "", { "dependencies": { "postcss-selector-parser": "^7.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-tRBEK0MHYvcMUrAuYMEOa0zg9APqirBcgzi6P21OhxtJyJADo/SWBwY1CAwEohQ/6HDaa9jCjLRG7K3PVQYHEA=="],
|
||||
|
||||
"postcss-discard-comments": ["postcss-discard-comments@7.0.4", "", { "dependencies": { "postcss-selector-parser": "^7.1.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-6tCUoql/ipWwKtVP/xYiFf1U9QgJ0PUvxN7pTcsQ8Ns3Fnwq1pU5D5s1MhT/XySeLq6GXNvn37U46Ded0TckWg=="],
|
||||
|
||||
"postcss-discard-duplicates": ["postcss-discard-duplicates@7.0.2", "", { "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-eTonaQvPZ/3i1ASDHOKkYwAybiM45zFIc7KXils4mQmHLqIswXD9XNOKEVxtTFnsmwYzF66u4LMgSr0abDlh5w=="],
|
||||
|
||||
"postcss-discard-empty": ["postcss-discard-empty@7.0.1", "", { "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-cFrJKZvcg/uxB6Ijr4l6qmn3pXQBna9zyrPC+sK0zjbkDUZew+6xDltSF7OeB7rAtzaaMVYSdbod+sZOCWnMOg=="],
|
||||
|
||||
"postcss-discard-overridden": ["postcss-discard-overridden@7.0.1", "", { "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-7c3MMjjSZ/qYrx3uc1940GSOzN1Iqjtlqe8uoSg+qdVPYyRb0TILSqqmtlSFuE4mTDECwsm397Ya7iXGzfF7lg=="],
|
||||
|
||||
"postcss-double-position-gradients": ["postcss-double-position-gradients@6.0.4", "", { "dependencies": { "@csstools/postcss-progressive-custom-properties": "^4.2.1", "@csstools/utilities": "^2.0.0", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-m6IKmxo7FxSP5nF2l63QbCC3r+bWpFUWmZXZf096WxG0m7Vl1Q1+ruFOhpdDRmKrRS+S3Jtk+TVk/7z0+BVK6g=="],
|
||||
|
||||
"postcss-focus-visible": ["postcss-focus-visible@10.0.1", "", { "dependencies": { "postcss-selector-parser": "^7.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-U58wyjS/I1GZgjRok33aE8juW9qQgQUNwTSdxQGuShHzwuYdcklnvK/+qOWX1Q9kr7ysbraQ6ht6r+udansalA=="],
|
||||
|
||||
"postcss-focus-within": ["postcss-focus-within@9.0.1", "", { "dependencies": { "postcss-selector-parser": "^7.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-fzNUyS1yOYa7mOjpci/bR+u+ESvdar6hk8XNK/TRR0fiGTp2QT5N+ducP0n3rfH/m9I7H/EQU6lsa2BrgxkEjw=="],
|
||||
|
||||
"postcss-font-variant": ["postcss-font-variant@5.0.0", "", { "peerDependencies": { "postcss": "^8.1.0" } }, "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA=="],
|
||||
|
||||
"postcss-gap-properties": ["postcss-gap-properties@6.0.0", "", { "peerDependencies": { "postcss": "^8.4" } }, "sha512-Om0WPjEwiM9Ru+VhfEDPZJAKWUd0mV1HmNXqp2C29z80aQ2uP9UVhLc7e3aYMIor/S5cVhoPgYQ7RtfeZpYTRw=="],
|
||||
|
||||
"postcss-image-set-function": ["postcss-image-set-function@7.0.0", "", { "dependencies": { "@csstools/utilities": "^2.0.0", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-QL7W7QNlZuzOwBTeXEmbVckNt1FSmhQtbMRvGGqqU4Nf4xk6KUEQhAoWuMzwbSv5jxiRiSZ5Tv7eiDB9U87znA=="],
|
||||
|
||||
"postcss-import": ["postcss-import@16.1.1", "", { "dependencies": { "postcss-value-parser": "^4.0.0", "read-cache": "^1.0.0", "resolve": "^1.1.7" }, "peerDependencies": { "postcss": "^8.0.0" } }, "sha512-2xVS1NCZAfjtVdvXiyegxzJ447GyqCeEI5V7ApgQVOWnros1p5lGNovJNapwPpMombyFBfqDwt7AD3n2l0KOfQ=="],
|
||||
|
||||
"postcss-lab-function": ["postcss-lab-function@7.0.12", "", { "dependencies": { "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", "@csstools/postcss-progressive-custom-properties": "^4.2.1", "@csstools/utilities": "^2.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-tUcyRk1ZTPec3OuKFsqtRzW2Go5lehW29XA21lZ65XmzQkz43VY2tyWEC202F7W3mILOjw0voOiuxRGTsN+J9w=="],
|
||||
|
||||
"postcss-load-config": ["postcss-load-config@5.1.0", "", { "dependencies": { "lilconfig": "^3.1.1", "yaml": "^2.4.2" }, "peerDependencies": { "jiti": ">=1.21.0", "postcss": ">=8.0.9", "tsx": "^4.8.1" }, "optionalPeers": ["jiti", "postcss", "tsx"] }, "sha512-G5AJ+IX0aD0dygOE0yFZQ/huFFMSNneyfp0e3/bT05a8OfPC5FUoZRPfGijUdGOJNMewJiwzcHJXFafFzeKFVA=="],
|
||||
|
||||
"postcss-logical": ["postcss-logical@8.1.0", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-pL1hXFQ2fEXNKiNiAgtfA005T9FBxky5zkX6s4GZM2D8RkVgRqz3f4g1JUoq925zXv495qk8UNldDwh8uGEDoA=="],
|
||||
|
||||
"postcss-merge-longhand": ["postcss-merge-longhand@7.0.5", "", { "dependencies": { "postcss-value-parser": "^4.2.0", "stylehacks": "^7.0.5" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-Kpu5v4Ys6QI59FxmxtNB/iHUVDn9Y9sYw66D6+SZoIk4QTz1prC4aYkhIESu+ieG1iylod1f8MILMs1Em3mmIw=="],
|
||||
|
||||
"postcss-merge-rules": ["postcss-merge-rules@7.0.6", "", { "dependencies": { "browserslist": "^4.25.1", "caniuse-api": "^3.0.0", "cssnano-utils": "^5.0.1", "postcss-selector-parser": "^7.1.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-2jIPT4Tzs8K87tvgCpSukRQ2jjd+hH6Bb8rEEOUDmmhOeTcqDg5fEFK8uKIu+Pvc3//sm3Uu6FRqfyv7YF7+BQ=="],
|
||||
|
||||
"postcss-minify-font-values": ["postcss-minify-font-values@7.0.1", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-2m1uiuJeTplll+tq4ENOQSzB8LRnSUChBv7oSyFLsJRtUgAAJGP6LLz0/8lkinTgxrmJSPOEhgY1bMXOQ4ZXhQ=="],
|
||||
|
||||
"postcss-minify-gradients": ["postcss-minify-gradients@7.0.1", "", { "dependencies": { "colord": "^2.9.3", "cssnano-utils": "^5.0.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-X9JjaysZJwlqNkJbUDgOclyG3jZEpAMOfof6PUZjPnPrePnPG62pS17CjdM32uT1Uq1jFvNSff9l7kNbmMSL2A=="],
|
||||
|
||||
"postcss-minify-params": ["postcss-minify-params@7.0.4", "", { "dependencies": { "browserslist": "^4.25.1", "cssnano-utils": "^5.0.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-3OqqUddfH8c2e7M35W6zIwv7jssM/3miF9cbCSb1iJiWvtguQjlxZGIHK9JRmc8XAKmE2PFGtHSM7g/VcW97sw=="],
|
||||
|
||||
"postcss-minify-selectors": ["postcss-minify-selectors@7.0.5", "", { "dependencies": { "cssesc": "^3.0.0", "postcss-selector-parser": "^7.1.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-x2/IvofHcdIrAm9Q+p06ZD1h6FPcQ32WtCRVodJLDR+WMn8EVHI1kvLxZuGKz/9EY5nAmI6lIQIrpo4tBy5+ug=="],
|
||||
|
||||
"postcss-nesting": ["postcss-nesting@13.0.2", "", { "dependencies": { "@csstools/selector-resolve-nested": "^3.1.0", "@csstools/selector-specificity": "^5.0.0", "postcss-selector-parser": "^7.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-1YCI290TX+VP0U/K/aFxzHzQWHWURL+CtHMSbex1lCdpXD1SoR2sYuxDu5aNI9lPoXpKTCggFZiDJbwylU0LEQ=="],
|
||||
|
||||
"postcss-normalize-charset": ["postcss-normalize-charset@7.0.1", "", { "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-sn413ofhSQHlZFae//m9FTOfkmiZ+YQXsbosqOWRiVQncU2BA3daX3n0VF3cG6rGLSFVc5Di/yns0dFfh8NFgQ=="],
|
||||
|
||||
"postcss-normalize-display-values": ["postcss-normalize-display-values@7.0.1", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-E5nnB26XjSYz/mGITm6JgiDpAbVuAkzXwLzRZtts19jHDUBFxZ0BkXAehy0uimrOjYJbocby4FVswA/5noOxrQ=="],
|
||||
|
||||
"postcss-normalize-positions": ["postcss-normalize-positions@7.0.1", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-pB/SzrIP2l50ZIYu+yQZyMNmnAcwyYb9R1fVWPRxm4zcUFCY2ign7rcntGFuMXDdd9L2pPNUgoODDk91PzRZuQ=="],
|
||||
|
||||
"postcss-normalize-repeat-style": ["postcss-normalize-repeat-style@7.0.1", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-NsSQJ8zj8TIDiF0ig44Byo3Jk9e4gNt9x2VIlJudnQQ5DhWAHJPF4Tr1ITwyHio2BUi/I6Iv0HRO7beHYOloYQ=="],
|
||||
|
||||
"postcss-normalize-string": ["postcss-normalize-string@7.0.1", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-QByrI7hAhsoze992kpbMlJSbZ8FuCEc1OT9EFbZ6HldXNpsdpZr+YXC5di3UEv0+jeZlHbZcoCADgb7a+lPmmQ=="],
|
||||
|
||||
"postcss-normalize-timing-functions": ["postcss-normalize-timing-functions@7.0.1", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-bHifyuuSNdKKsnNJ0s8fmfLMlvsQwYVxIoUBnowIVl2ZAdrkYQNGVB4RxjfpvkMjipqvbz0u7feBZybkl/6NJg=="],
|
||||
|
||||
"postcss-normalize-unicode": ["postcss-normalize-unicode@7.0.4", "", { "dependencies": { "browserslist": "^4.25.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-LvIURTi1sQoZqj8mEIE8R15yvM+OhbR1avynMtI9bUzj5gGKR/gfZFd8O7VMj0QgJaIFzxDwxGl/ASMYAkqO8g=="],
|
||||
|
||||
"postcss-normalize-url": ["postcss-normalize-url@7.0.1", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-sUcD2cWtyK1AOL/82Fwy1aIVm/wwj5SdZkgZ3QiUzSzQQofrbq15jWJ3BA7Z+yVRwamCjJgZJN0I9IS7c6tgeQ=="],
|
||||
|
||||
"postcss-normalize-whitespace": ["postcss-normalize-whitespace@7.0.1", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-vsbgFHMFQrJBJKrUFJNZ2pgBeBkC2IvvoHjz1to0/0Xk7sII24T0qFOiJzG6Fu3zJoq/0yI4rKWi7WhApW+EFA=="],
|
||||
|
||||
"postcss-opacity-percentage": ["postcss-opacity-percentage@3.0.0", "", { "peerDependencies": { "postcss": "^8.4" } }, "sha512-K6HGVzyxUxd/VgZdX04DCtdwWJ4NGLG212US4/LA1TLAbHgmAsTWVR86o+gGIbFtnTkfOpb9sCRBx8K7HO66qQ=="],
|
||||
|
||||
"postcss-ordered-values": ["postcss-ordered-values@7.0.2", "", { "dependencies": { "cssnano-utils": "^5.0.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-AMJjt1ECBffF7CEON/Y0rekRLS6KsePU6PRP08UqYW4UGFRnTXNrByUzYK1h8AC7UWTZdQ9O3Oq9kFIhm0SFEw=="],
|
||||
|
||||
"postcss-overflow-shorthand": ["postcss-overflow-shorthand@6.0.0", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-BdDl/AbVkDjoTofzDQnwDdm/Ym6oS9KgmO7Gr+LHYjNWJ6ExORe4+3pcLQsLA9gIROMkiGVjjwZNoL/mpXHd5Q=="],
|
||||
|
||||
"postcss-page-break": ["postcss-page-break@3.0.4", "", { "peerDependencies": { "postcss": "^8" } }, "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ=="],
|
||||
|
||||
"postcss-place": ["postcss-place@10.0.0", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-5EBrMzat2pPAxQNWYavwAfoKfYcTADJ8AXGVPcUZ2UkNloUTWzJQExgrzrDkh3EKzmAx1evfTAzF9I8NGcc+qw=="],
|
||||
|
||||
"postcss-preset-env": ["postcss-preset-env@10.4.0", "", { "dependencies": { "@csstools/postcss-alpha-function": "^1.0.1", "@csstools/postcss-cascade-layers": "^5.0.2", "@csstools/postcss-color-function": "^4.0.12", "@csstools/postcss-color-function-display-p3-linear": "^1.0.1", "@csstools/postcss-color-mix-function": "^3.0.12", "@csstools/postcss-color-mix-variadic-function-arguments": "^1.0.2", "@csstools/postcss-content-alt-text": "^2.0.8", "@csstools/postcss-contrast-color-function": "^2.0.12", "@csstools/postcss-exponential-functions": "^2.0.9", "@csstools/postcss-font-format-keywords": "^4.0.0", "@csstools/postcss-gamut-mapping": "^2.0.11", "@csstools/postcss-gradients-interpolation-method": "^5.0.12", "@csstools/postcss-hwb-function": "^4.0.12", "@csstools/postcss-ic-unit": "^4.0.4", "@csstools/postcss-initial": "^2.0.1", "@csstools/postcss-is-pseudo-class": "^5.0.3", "@csstools/postcss-light-dark-function": "^2.0.11", "@csstools/postcss-logical-float-and-clear": "^3.0.0", "@csstools/postcss-logical-overflow": "^2.0.0", "@csstools/postcss-logical-overscroll-behavior": "^2.0.0", "@csstools/postcss-logical-resize": "^3.0.0", "@csstools/postcss-logical-viewport-units": "^3.0.4", "@csstools/postcss-media-minmax": "^2.0.9", "@csstools/postcss-media-queries-aspect-ratio-number-values": "^3.0.5", "@csstools/postcss-nested-calc": "^4.0.0", "@csstools/postcss-normalize-display-values": "^4.0.0", "@csstools/postcss-oklab-function": "^4.0.12", "@csstools/postcss-progressive-custom-properties": "^4.2.1", "@csstools/postcss-random-function": "^2.0.1", "@csstools/postcss-relative-color-syntax": "^3.0.12", "@csstools/postcss-scope-pseudo-class": "^4.0.1", "@csstools/postcss-sign-functions": "^1.1.4", "@csstools/postcss-stepped-value-functions": "^4.0.9", "@csstools/postcss-text-decoration-shorthand": "^4.0.3", "@csstools/postcss-trigonometric-functions": "^4.0.9", "@csstools/postcss-unset-value": "^4.0.0", "autoprefixer": "^10.4.21", "browserslist": "^4.26.0", "css-blank-pseudo": "^7.0.1", "css-has-pseudo": "^7.0.3", "css-prefers-color-scheme": "^10.0.0", "cssdb": "^8.4.2", "postcss-attribute-case-insensitive": "^7.0.1", "postcss-clamp": "^4.1.0", "postcss-color-functional-notation": "^7.0.12", "postcss-color-hex-alpha": "^10.0.0", "postcss-color-rebeccapurple": "^10.0.0", "postcss-custom-media": "^11.0.6", "postcss-custom-properties": "^14.0.6", "postcss-custom-selectors": "^8.0.5", "postcss-dir-pseudo-class": "^9.0.1", "postcss-double-position-gradients": "^6.0.4", "postcss-focus-visible": "^10.0.1", "postcss-focus-within": "^9.0.1", "postcss-font-variant": "^5.0.0", "postcss-gap-properties": "^6.0.0", "postcss-image-set-function": "^7.0.0", "postcss-lab-function": "^7.0.12", "postcss-logical": "^8.1.0", "postcss-nesting": "^13.0.2", "postcss-opacity-percentage": "^3.0.0", "postcss-overflow-shorthand": "^6.0.0", "postcss-page-break": "^3.0.4", "postcss-place": "^10.0.0", "postcss-pseudo-class-any-link": "^10.0.1", "postcss-replace-overflow-wrap": "^4.0.0", "postcss-selector-not": "^8.0.1" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-2kqpOthQ6JhxqQq1FSAAZGe9COQv75Aw8WbsOvQVNJ2nSevc9Yx/IKZGuZ7XJ+iOTtVon7LfO7ELRzg8AZ+sdw=="],
|
||||
|
||||
"postcss-pseudo-class-any-link": ["postcss-pseudo-class-any-link@10.0.1", "", { "dependencies": { "postcss-selector-parser": "^7.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-3el9rXlBOqTFaMFkWDOkHUTQekFIYnaQY55Rsp8As8QQkpiSgIYEcF/6Ond93oHiDsGb4kad8zjt+NPlOC1H0Q=="],
|
||||
|
||||
"postcss-reduce-initial": ["postcss-reduce-initial@7.0.4", "", { "dependencies": { "browserslist": "^4.25.1", "caniuse-api": "^3.0.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-rdIC9IlMBn7zJo6puim58Xd++0HdbvHeHaPgXsimMfG1ijC5A9ULvNLSE0rUKVJOvNMcwewW4Ga21ngyJjY/+Q=="],
|
||||
|
||||
"postcss-reduce-transforms": ["postcss-reduce-transforms@7.0.1", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-MhyEbfrm+Mlp/36hvZ9mT9DaO7dbncU0CvWI8V93LRkY6IYlu38OPg3FObnuKTUxJ4qA8HpurdQOo5CyqqO76g=="],
|
||||
|
||||
"postcss-replace-overflow-wrap": ["postcss-replace-overflow-wrap@4.0.0", "", { "peerDependencies": { "postcss": "^8.0.3" } }, "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw=="],
|
||||
|
||||
"postcss-reporter": ["postcss-reporter@7.1.0", "", { "dependencies": { "picocolors": "^1.0.0", "thenby": "^1.3.4" }, "peerDependencies": { "postcss": "^8.1.0" } }, "sha512-/eoEylGWyy6/DOiMP5lmFRdmDKThqgn7D6hP2dXKJI/0rJSO1ADFNngZfDzxL0YAxFvws+Rtpuji1YIHj4mySA=="],
|
||||
|
||||
"postcss-selector-not": ["postcss-selector-not@8.0.1", "", { "dependencies": { "postcss-selector-parser": "^7.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-kmVy/5PYVb2UOhy0+LqUYAhKj7DUGDpSWa5LZqlkWJaaAV+dxxsOG3+St0yNLu6vsKD7Dmqx+nWQt0iil89+WA=="],
|
||||
|
||||
"postcss-selector-parser": ["postcss-selector-parser@6.1.2", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg=="],
|
||||
|
||||
"postcss-svgo": ["postcss-svgo@7.1.0", "", { "dependencies": { "postcss-value-parser": "^4.2.0", "svgo": "^4.0.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-KnAlfmhtoLz6IuU3Sij2ycusNs4jPW+QoFE5kuuUOK8awR6tMxZQrs5Ey3BUz7nFCzT3eqyFgqkyrHiaU2xx3w=="],
|
||||
|
||||
"postcss-unique-selectors": ["postcss-unique-selectors@7.0.4", "", { "dependencies": { "postcss-selector-parser": "^7.1.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-pmlZjsmEAG7cHd7uK3ZiNSW6otSZ13RHuZ/4cDN/bVglS5EpF2r2oxY99SuOHa8m7AWoBCelTS3JPpzsIs8skQ=="],
|
||||
|
||||
"postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="],
|
||||
|
||||
"pretty-hrtime": ["pretty-hrtime@1.0.3", "", {}, "sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A=="],
|
||||
|
||||
"purgecss": ["purgecss@7.0.2", "", { "dependencies": { "commander": "^12.1.0", "glob": "^11.0.0", "postcss": "^8.4.47", "postcss-selector-parser": "^6.1.2" }, "bin": { "purgecss": "bin/purgecss.js" } }, "sha512-4Ku8KoxNhOWi9X1XJ73XY5fv+I+hhTRedKpGs/2gaBKU8ijUiIKF/uyyIyh7Wo713bELSICF5/NswjcuOqYouQ=="],
|
||||
|
||||
"read-cache": ["read-cache@1.0.0", "", { "dependencies": { "pify": "^2.3.0" } }, "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA=="],
|
||||
|
||||
"readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
|
||||
|
||||
"require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
|
||||
|
||||
"resolve": ["resolve@1.22.10", "", { "dependencies": { "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w=="],
|
||||
|
||||
"sax": ["sax@1.4.1", "", {}, "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg=="],
|
||||
|
||||
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
|
||||
|
||||
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
|
||||
|
||||
"signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
|
||||
|
||||
"slash": ["slash@5.1.0", "", {}, "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg=="],
|
||||
|
||||
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||
|
||||
"string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
|
||||
"string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
|
||||
"strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"stylehacks": ["stylehacks@7.0.6", "", { "dependencies": { "browserslist": "^4.25.1", "postcss-selector-parser": "^7.1.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-iitguKivmsueOmTO0wmxURXBP8uqOO+zikLGZ7Mm9e/94R4w5T999Js2taS/KBOnQ/wdC3jN3vNSrkGDrlnqQg=="],
|
||||
|
||||
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
|
||||
|
||||
"svgo": ["svgo@4.0.0", "", { "dependencies": { "commander": "^11.1.0", "css-select": "^5.1.0", "css-tree": "^3.0.1", "css-what": "^6.1.0", "csso": "^5.0.5", "picocolors": "^1.1.1", "sax": "^1.4.1" }, "bin": "./bin/svgo.js" }, "sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw=="],
|
||||
|
||||
"thenby": ["thenby@1.3.4", "", {}, "sha512-89Gi5raiWA3QZ4b2ePcEwswC3me9JIg+ToSgtE0JWeCynLnLxNr/f9G+xfo9K+Oj4AFdom8YNJjibIARTJmapQ=="],
|
||||
|
||||
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
|
||||
|
||||
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
|
||||
|
||||
"universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="],
|
||||
|
||||
"update-browserslist-db": ["update-browserslist-db@1.1.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw=="],
|
||||
|
||||
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
|
||||
|
||||
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||
|
||||
"wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
|
||||
|
||||
"wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
|
||||
|
||||
"y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
|
||||
|
||||
"yaml": ["yaml@2.8.1", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw=="],
|
||||
|
||||
"yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
|
||||
|
||||
"yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
|
||||
|
||||
"@csstools/postcss-cascade-layers/postcss-selector-parser": ["postcss-selector-parser@7.1.0", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA=="],
|
||||
|
||||
"@csstools/postcss-is-pseudo-class/postcss-selector-parser": ["postcss-selector-parser@7.1.0", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA=="],
|
||||
|
||||
"@csstools/postcss-scope-pseudo-class/postcss-selector-parser": ["postcss-selector-parser@7.1.0", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA=="],
|
||||
|
||||
"@csstools/selector-resolve-nested/postcss-selector-parser": ["postcss-selector-parser@7.1.0", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA=="],
|
||||
|
||||
"@csstools/selector-specificity/postcss-selector-parser": ["postcss-selector-parser@7.1.0", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA=="],
|
||||
|
||||
"@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="],
|
||||
|
||||
"@isaacs/cliui/strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="],
|
||||
|
||||
"@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
|
||||
|
||||
"anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||
|
||||
"css-blank-pseudo/postcss-selector-parser": ["postcss-selector-parser@7.1.0", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA=="],
|
||||
|
||||
"css-has-pseudo/postcss-selector-parser": ["postcss-selector-parser@7.1.0", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA=="],
|
||||
|
||||
"csso/css-tree": ["css-tree@2.2.1", "", { "dependencies": { "mdn-data": "2.0.28", "source-map-js": "^1.0.1" } }, "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA=="],
|
||||
|
||||
"postcss-attribute-case-insensitive/postcss-selector-parser": ["postcss-selector-parser@7.1.0", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA=="],
|
||||
|
||||
"postcss-calc/postcss-selector-parser": ["postcss-selector-parser@7.1.0", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA=="],
|
||||
|
||||
"postcss-custom-selectors/postcss-selector-parser": ["postcss-selector-parser@7.1.0", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA=="],
|
||||
|
||||
"postcss-dir-pseudo-class/postcss-selector-parser": ["postcss-selector-parser@7.1.0", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA=="],
|
||||
|
||||
"postcss-discard-comments/postcss-selector-parser": ["postcss-selector-parser@7.1.0", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA=="],
|
||||
|
||||
"postcss-focus-visible/postcss-selector-parser": ["postcss-selector-parser@7.1.0", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA=="],
|
||||
|
||||
"postcss-focus-within/postcss-selector-parser": ["postcss-selector-parser@7.1.0", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA=="],
|
||||
|
||||
"postcss-merge-rules/postcss-selector-parser": ["postcss-selector-parser@7.1.0", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA=="],
|
||||
|
||||
"postcss-minify-selectors/postcss-selector-parser": ["postcss-selector-parser@7.1.0", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA=="],
|
||||
|
||||
"postcss-nesting/postcss-selector-parser": ["postcss-selector-parser@7.1.0", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA=="],
|
||||
|
||||
"postcss-pseudo-class-any-link/postcss-selector-parser": ["postcss-selector-parser@7.1.0", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA=="],
|
||||
|
||||
"postcss-selector-not/postcss-selector-parser": ["postcss-selector-parser@7.1.0", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA=="],
|
||||
|
||||
"postcss-unique-selectors/postcss-selector-parser": ["postcss-selector-parser@7.1.0", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA=="],
|
||||
|
||||
"readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||
|
||||
"stylehacks/postcss-selector-parser": ["postcss-selector-parser@7.1.0", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA=="],
|
||||
|
||||
"svgo/commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="],
|
||||
|
||||
"@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
|
||||
|
||||
"@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
|
||||
|
||||
"@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
|
||||
|
||||
"csso/css-tree/mdn-data": ["mdn-data@2.0.28", "", {}, "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g=="],
|
||||
}
|
||||
}
|
||||
8
go.mod
8
go.mod
@@ -4,11 +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
|
||||
golang.org/x/image v0.24.0
|
||||
modernc.org/sqlite v1.39.0
|
||||
)
|
||||
|
||||
@@ -20,10 +16,10 @@ 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
|
||||
golang.org/x/image v0.24.0 // indirect
|
||||
golang.org/x/net v0.44.0 // indirect
|
||||
golang.org/x/text v0.29.0 // indirect
|
||||
modernc.org/libc v1.66.3 // indirect
|
||||
@@ -45,8 +41,8 @@ require (
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
|
||||
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
|
||||
|
||||
16
go.sum
16
go.sum
@@ -1,7 +1,5 @@
|
||||
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=
|
||||
@@ -9,8 +7,6 @@ github.com/caarlos0/env/v11 v11.3.1/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vaui
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
|
||||
@@ -47,6 +43,8 @@ github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZ
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||
github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
|
||||
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
@@ -55,8 +53,6 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd h1:CmH9+J6ZSsIjUK3dcGsnCnO41eRBOnY12zwkn5qVwgc=
|
||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
|
||||
github.com/shamaton/msgpack/v2 v2.3.0 h1:eawIa7lQmwRv0V6rdmL/5Ev9KdJHk07eQH3ceJi3BUw=
|
||||
github.com/shamaton/msgpack/v2 v2.3.0/go.mod h1:6khjYnkx73f7VQU7wjcFS9DFjs+59naVWJv1TB7qdOI=
|
||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||
@@ -66,12 +62,6 @@ 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=
|
||||
@@ -86,7 +76,6 @@ golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
|
||||
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
|
||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
|
||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ=
|
||||
golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8=
|
||||
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
|
||||
@@ -99,7 +88,6 @@ golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:generate bun run build
|
||||
//go:generate tailwindcss -i styles/main.css -o assets/tailwind.css --minify
|
||||
|
||||
package main
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"embed"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
@@ -22,63 +23,45 @@ import (
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"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"
|
||||
"github.com/google/uuid"
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/juls0730/passport/src/middleware"
|
||||
"github.com/juls0730/passport/src/services"
|
||||
"github.com/rwcarlsen/goexif/exif"
|
||||
"github.com/rwcarlsen/goexif/tiff"
|
||||
"golang.org/x/image/draw"
|
||||
"github.com/juls0730/passport/middleware"
|
||||
"github.com/nfnt/resize"
|
||||
_ "modernc.org/sqlite"
|
||||
)
|
||||
|
||||
//go:embed assets/** templates/** schema.sql scripts/**.js
|
||||
//go:embed assets/** templates/** schema.sql
|
||||
var embeddedAssets embed.FS
|
||||
|
||||
var devContent = `<script>
|
||||
let host = window.location.hostname;
|
||||
let protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
const socket = new WebSocket(protocol + '//' + host + ':2067/ws');
|
||||
const socket = new WebSocket('ws://' + host + ':2067/ws');
|
||||
|
||||
socket.addEventListener('message', (event) => {
|
||||
if (event.data === 'refresh') {
|
||||
console.log('Got refresh signal');
|
||||
|
||||
let attempts = 0;
|
||||
let delay = 100;
|
||||
|
||||
async function testPage() {
|
||||
try {
|
||||
let res = await fetch(window.location.href)
|
||||
let res = await fetch(window.location.href)
|
||||
} catch (error) {
|
||||
if (attempts > 5) {
|
||||
return;
|
||||
}
|
||||
setTimeout(testPage, delay);
|
||||
|
||||
// exponential backoff
|
||||
attempts++;
|
||||
delay = 100 * Math.pow(2, attempts);
|
||||
|
||||
console.error(error);
|
||||
setTimeout(testPage, 300);
|
||||
return;
|
||||
}
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
setTimeout(testPage, 150);
|
||||
}
|
||||
testPage();
|
||||
}
|
||||
});
|
||||
</script>`
|
||||
|
||||
@@ -87,15 +70,37 @@ var (
|
||||
insertLinkStmt *sql.Stmt
|
||||
)
|
||||
|
||||
type WeatherProvider string
|
||||
|
||||
const (
|
||||
OpenWeatherMap WeatherProvider = "openweathermap"
|
||||
)
|
||||
|
||||
type WeatherConfig struct {
|
||||
Provider WeatherProvider `env:"OPENWEATHER_PROVIDER" envDefault:"openweathermap"`
|
||||
OpenWeather struct {
|
||||
APIKey string `env:"OPENWEATHER_API_KEY"`
|
||||
Units string `env:"OPENWEATHER_TEMP_UNITS" envDefault:"metric"`
|
||||
Lat float64 `env:"OPENWEATHER_LAT"`
|
||||
Lon float64 `env:"OPENWEATHER_LON"`
|
||||
}
|
||||
UpdateInterval int `env:"OPENWEATHER_UPDATE_INTERVAL" envDefault:"15"`
|
||||
}
|
||||
|
||||
type UptimeConfig struct {
|
||||
APIKey string `env:"UPTIMEROBOT_API_KEY"`
|
||||
UpdateInterval int `env:"UPTIMEROBOT_UPDATE_INTERVAL" envDefault:"300"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
DevMode bool `env:"PASSPORT_DEV_MODE" envDefault:"false"`
|
||||
Prefork bool `env:"PASSPORT_ENABLE_PREFORK" envDefault:"false"`
|
||||
|
||||
WeatherAPIKey string `env:"PASSPORT_WEATHER_API_KEY"`
|
||||
Weather *services.WeatherConfig
|
||||
WeatherEnabled bool `env:"PASSPORT_ENABLE_WEATHER" envDefault:"false"`
|
||||
Weather *WeatherConfig
|
||||
|
||||
UptimeAPIKey string `env:"PASSPORT_UPTIME_API_KEY"`
|
||||
Uptime *services.UptimeConfig
|
||||
UptimeEnabled bool `env:"PASSPORT_ENABLE_UPTIME" envDefault:"false"`
|
||||
Uptime *UptimeConfig
|
||||
|
||||
Admin struct {
|
||||
Username string `env:"PASSPORT_ADMIN_USERNAME"`
|
||||
@@ -106,11 +111,6 @@ type Config struct {
|
||||
URL string `env:"PASSPORT_SEARCH_PROVIDER"`
|
||||
Query string `env:"PASSPORT_SEARCH_PROVIDER_QUERY_PARAM" envDefault:"q"`
|
||||
}
|
||||
|
||||
Depricated struct {
|
||||
WeatherEnabled bool `env:"PASSPORT_ENABLE_WEATHER" envDefault:"false"`
|
||||
UptimeEnabled bool `env:"PASSPORT_ENABLE_UPTIME" envDefault:"false"`
|
||||
}
|
||||
}
|
||||
|
||||
func ParseConfig() (*Config, error) {
|
||||
@@ -121,46 +121,18 @@ func ParseConfig() (*Config, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if config.WeatherAPIKey != "" {
|
||||
config.Weather = &services.WeatherConfig{
|
||||
APIKey: config.WeatherAPIKey,
|
||||
}
|
||||
if config.WeatherEnabled {
|
||||
config.Weather = &WeatherConfig{}
|
||||
if err := env.Parse(config.Weather); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if config.Depricated.WeatherEnabled {
|
||||
slog.Warn("Your configuration file contains depricated Weather settings. Please update your configuration file!")
|
||||
depricatedWeatherConfig := &services.DepricatedWeatherConfig{}
|
||||
if err := env.Parse(depricatedWeatherConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config.Weather = &services.WeatherConfig{}
|
||||
config.Weather.Provider = depricatedWeatherConfig.OpenWeather.Provider
|
||||
config.Weather.APIKey = depricatedWeatherConfig.OpenWeather.APIKey
|
||||
config.Weather.Units = depricatedWeatherConfig.OpenWeather.Units
|
||||
config.Weather.Lat = depricatedWeatherConfig.OpenWeather.Lat
|
||||
config.Weather.Lon = depricatedWeatherConfig.OpenWeather.Lon
|
||||
config.Weather.UpdateInterval = depricatedWeatherConfig.UpdateInterval
|
||||
}
|
||||
|
||||
if config.UptimeAPIKey != "" {
|
||||
config.Uptime = &services.UptimeConfig{
|
||||
APIKey: config.UptimeAPIKey,
|
||||
}
|
||||
if config.UptimeEnabled {
|
||||
config.Uptime = &UptimeConfig{}
|
||||
if err := env.Parse(config.Uptime); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if config.Depricated.UptimeEnabled {
|
||||
slog.Warn("Your configuration file contains depricated Uptime settings. Please update your configuration file!")
|
||||
depricatedUptimeConfig := &services.DepricatedUptimeConfig{}
|
||||
if err := env.Parse(depricatedUptimeConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config.Uptime = &services.UptimeConfig{}
|
||||
config.Uptime.APIKey = depricatedUptimeConfig.APIKey
|
||||
config.Uptime.UpdateInterval = depricatedUptimeConfig.UpdateInterval
|
||||
}
|
||||
|
||||
return &config, nil
|
||||
@@ -169,8 +141,8 @@ func ParseConfig() (*Config, error) {
|
||||
type App struct {
|
||||
*Config
|
||||
*CategoryManager
|
||||
*services.WeatherManager
|
||||
*services.UptimeManager
|
||||
*WeatherCache
|
||||
*UptimeManager
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
@@ -227,71 +199,245 @@ func NewApp(dbPath string, options map[string]any) (*App, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var weatherCache *services.WeatherManager
|
||||
if config.WeatherAPIKey != "" {
|
||||
weatherCache = services.NewWeatherManager(config.Weather)
|
||||
var weatherCache *WeatherCache
|
||||
if config.WeatherEnabled {
|
||||
weatherCache = NewWeatherCache(config.Weather)
|
||||
}
|
||||
|
||||
var uptimeManager *services.UptimeManager
|
||||
if config.UptimeAPIKey != "" {
|
||||
uptimeManager = services.NewUptimeManager(config.Uptime)
|
||||
var uptimeManager *UptimeManager
|
||||
if config.UptimeEnabled {
|
||||
uptimeManager = NewUptimeManager(config.Uptime)
|
||||
}
|
||||
|
||||
return &App{
|
||||
Config: config,
|
||||
WeatherManager: weatherCache,
|
||||
WeatherCache: weatherCache,
|
||||
CategoryManager: categoryManager,
|
||||
UptimeManager: uptimeManager,
|
||||
db: db,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func CropToCenter(img image.Image, outputSize int) (image.Image, error) {
|
||||
if img == nil {
|
||||
return nil, fmt.Errorf("input image is nil")
|
||||
}
|
||||
if outputSize <= 0 {
|
||||
return nil, fmt.Errorf("output size must be positive")
|
||||
}
|
||||
|
||||
srcBounds := img.Bounds()
|
||||
srcWidth := srcBounds.Dx()
|
||||
srcHeight := srcBounds.Dy()
|
||||
|
||||
squareSide := min(srcWidth, srcHeight)
|
||||
|
||||
cropX := (srcWidth - squareSide) / 2
|
||||
cropY := (srcHeight - squareSide) / 2
|
||||
|
||||
srcCropRect := image.Rect(cropX, cropY, cropX+squareSide, cropY+squareSide)
|
||||
|
||||
croppedSquareImg := image.NewRGBA(image.Rect(0, 0, squareSide, squareSide))
|
||||
draw.Draw(croppedSquareImg, croppedSquareImg.Rect, img, srcCropRect.Min, draw.Src)
|
||||
|
||||
if squareSide == outputSize {
|
||||
return croppedSquareImg, nil
|
||||
}
|
||||
|
||||
outputImg := image.NewRGBA(image.Rect(0, 0, outputSize, outputSize))
|
||||
|
||||
draw.CatmullRom.Scale(outputImg, outputImg.Rect, croppedSquareImg, croppedSquareImg.Bounds(), draw.Src, nil)
|
||||
|
||||
return outputImg, nil
|
||||
type UptimeRobotSite struct {
|
||||
FriendlyName string `json:"friendly_name"`
|
||||
Url string `json:"url"`
|
||||
Status int `json:"status"`
|
||||
}
|
||||
|
||||
func UploadFile(file *multipart.FileHeader, contentType string, c fiber.Ctx) (string, error) {
|
||||
fileId, err := uuid.NewV7()
|
||||
type UptimeManager struct {
|
||||
sites []UptimeRobotSite
|
||||
lastUpdate time.Time
|
||||
mutex sync.RWMutex
|
||||
updateChan chan struct{}
|
||||
updateInterval int
|
||||
apiKey string
|
||||
}
|
||||
|
||||
func NewUptimeManager(config *UptimeConfig) *UptimeManager {
|
||||
if config.APIKey == "" {
|
||||
log.Fatalln("UptimeRobot API Key is required!")
|
||||
return nil
|
||||
}
|
||||
|
||||
updateInterval := config.UpdateInterval
|
||||
if updateInterval < 1 {
|
||||
updateInterval = 300
|
||||
}
|
||||
|
||||
uptimeManager := &UptimeManager{
|
||||
updateChan: make(chan struct{}),
|
||||
updateInterval: updateInterval,
|
||||
apiKey: config.APIKey,
|
||||
sites: []UptimeRobotSite{},
|
||||
}
|
||||
|
||||
go uptimeManager.updateWorker()
|
||||
|
||||
uptimeManager.updateChan <- struct{}{}
|
||||
|
||||
return uptimeManager
|
||||
}
|
||||
|
||||
func (u *UptimeManager) getUptime() []UptimeRobotSite {
|
||||
u.mutex.RLock()
|
||||
defer u.mutex.RUnlock()
|
||||
return u.sites
|
||||
}
|
||||
|
||||
func (u *UptimeManager) updateWorker() {
|
||||
ticker := time.NewTicker(time.Duration(u.updateInterval) * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-u.updateChan:
|
||||
u.update()
|
||||
case <-ticker.C:
|
||||
u.update()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type UptimeRobotResponse struct {
|
||||
Monitors []UptimeRobotSite `json:"monitors"`
|
||||
}
|
||||
|
||||
func (u *UptimeManager) update() {
|
||||
resp, err := http.Post("https://api.uptimerobot.com/v2/getMonitors?api_key="+u.apiKey, "application/json", nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
fmt.Printf("Error fetching uptime data: %v\n", err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
fmt.Printf("Error reading response: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
var fileName string
|
||||
if filepath.Ext(file.Filename) != ".svg" {
|
||||
fileName = fmt.Sprintf("%s.webp", fileId.String())
|
||||
} else {
|
||||
fileName = fmt.Sprintf("%s.svg", fileId.String())
|
||||
var monitors UptimeRobotResponse
|
||||
if err := json.Unmarshal(body, &monitors); err != nil {
|
||||
fmt.Printf("Error parsing uptime data: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
u.mutex.Lock()
|
||||
u.sites = monitors.Monitors
|
||||
u.lastUpdate = time.Now()
|
||||
u.mutex.Unlock()
|
||||
}
|
||||
|
||||
type OpenWeatherResponse struct {
|
||||
Weather []struct {
|
||||
Name string `json:"main"`
|
||||
IconId string `json:"icon"`
|
||||
} `json:"weather"`
|
||||
Main struct {
|
||||
Temp float64 `json:"temp"`
|
||||
} `json:"main"`
|
||||
Code int `json:"cod"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type WeatherData struct {
|
||||
Temperature float64
|
||||
WeatherText string
|
||||
Icon string
|
||||
}
|
||||
|
||||
type WeatherCache struct {
|
||||
data *WeatherData
|
||||
lastUpdate time.Time
|
||||
mutex sync.RWMutex
|
||||
updateChan chan struct{}
|
||||
tempUnits string
|
||||
updateInterval int
|
||||
apiKey string
|
||||
lat float64
|
||||
lon float64
|
||||
}
|
||||
|
||||
func NewWeatherCache(config *WeatherConfig) *WeatherCache {
|
||||
if config.Provider != OpenWeatherMap {
|
||||
log.Fatalln("Only OpenWeatherMap is supported!")
|
||||
return nil
|
||||
}
|
||||
|
||||
if config.OpenWeather.APIKey == "" {
|
||||
log.Fatalln("An API Key required for OpenWeather!")
|
||||
return nil
|
||||
}
|
||||
|
||||
updateInterval := config.UpdateInterval
|
||||
if updateInterval < 1 {
|
||||
updateInterval = 15
|
||||
}
|
||||
|
||||
units := config.OpenWeather.Units
|
||||
if units == "" {
|
||||
units = "metric"
|
||||
}
|
||||
|
||||
cache := &WeatherCache{
|
||||
data: &WeatherData{},
|
||||
updateChan: make(chan struct{}),
|
||||
tempUnits: units,
|
||||
updateInterval: updateInterval,
|
||||
apiKey: config.OpenWeather.APIKey,
|
||||
lat: config.OpenWeather.Lat,
|
||||
lon: config.OpenWeather.Lon,
|
||||
}
|
||||
|
||||
go cache.weatherWorker()
|
||||
|
||||
cache.updateChan <- struct{}{}
|
||||
|
||||
return cache
|
||||
}
|
||||
|
||||
func (c *WeatherCache) GetWeather() WeatherData {
|
||||
c.mutex.RLock()
|
||||
defer c.mutex.RUnlock()
|
||||
return *c.data
|
||||
}
|
||||
|
||||
func (c *WeatherCache) weatherWorker() {
|
||||
ticker := time.NewTicker(time.Duration(c.updateInterval) * time.Minute)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-c.updateChan:
|
||||
c.updateWeather()
|
||||
case <-ticker.C:
|
||||
c.updateWeather()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *WeatherCache) updateWeather() {
|
||||
url := fmt.Sprintf("https://api.openweathermap.org/data/2.5/weather?lat=%f&lon=%f&appid=%s&units=%s",
|
||||
c.lat, c.lon, c.apiKey, c.tempUnits)
|
||||
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
fmt.Printf("Error fetching weather: %v\n", err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
fmt.Printf("Error reading response: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
var weatherResp OpenWeatherResponse
|
||||
if err := json.Unmarshal(body, &weatherResp); err != nil {
|
||||
fmt.Printf("Error parsing weather data: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// if the request failed
|
||||
if weatherResp.Code != 200 {
|
||||
// if there is no pre-existing data in the cache
|
||||
if c.data.WeatherText == "" {
|
||||
log.Fatalf("Fetching the weather data failed!\n%s\n", weatherResp.Message)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c.mutex.Lock()
|
||||
c.data.Temperature = weatherResp.Main.Temp
|
||||
c.data.WeatherText = weatherResp.Weather[0].Name
|
||||
c.data.Icon = weatherResp.Weather[0].IconId
|
||||
c.lastUpdate = time.Now()
|
||||
c.mutex.Unlock()
|
||||
}
|
||||
|
||||
func UploadFile(file *multipart.FileHeader, fileName, contentType string, c fiber.Ctx) (string, error) {
|
||||
srcFile, err := file.Open()
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -316,67 +462,12 @@ func UploadFile(file *multipart.FileHeader, contentType string, c fiber.Ctx) (st
|
||||
return "", err
|
||||
}
|
||||
|
||||
if contentType != "image/svg+xml" {
|
||||
off, err := srcFile.Seek(0, io.SeekStart)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to seek to start of file: %v", err)
|
||||
}
|
||||
|
||||
if off != 0 {
|
||||
return "", fmt.Errorf("failed to seek to start of file: %v", err)
|
||||
}
|
||||
|
||||
x, err := exif.Decode(srcFile)
|
||||
// if there *is* exif, parse it
|
||||
if err == nil {
|
||||
tag, err := x.Get(exif.Orientation)
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
img, err = CropToCenter(img, 96)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
assetsDir := "public/uploads"
|
||||
|
||||
iconPath := filepath.Join(assetsDir, fileName)
|
||||
|
||||
if contentType == "image/svg+xml" {
|
||||
// replace currentColor with a text color
|
||||
outFile, err := os.Create(iconPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer outFile.Close()
|
||||
|
||||
svgText, err := io.ReadAll(srcFile)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
svgText = bytes.ReplaceAll(svgText, []byte("currentColor"), []byte(`oklch(87% 0.015 286)`))
|
||||
_, err = outFile.Write(svgText)
|
||||
if err != nil {
|
||||
if err = c.SaveFile(file, iconPath); err != nil {
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
@@ -386,9 +477,11 @@ func UploadFile(file *multipart.FileHeader, contentType string, c fiber.Ctx) (st
|
||||
}
|
||||
defer outFile.Close()
|
||||
|
||||
resizedImg := resize.Resize(64, 0, img, resize.MitchellNetravali)
|
||||
|
||||
var buf bytes.Buffer
|
||||
options := &nativewebp.Options{}
|
||||
if err := nativewebp.Encode(&buf, img, options); err != nil {
|
||||
if err := nativewebp.Encode(&buf, resizedImg, options); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -715,28 +808,16 @@ 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("eq", func(a, b any) bool {
|
||||
return a == b
|
||||
})
|
||||
|
||||
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("inlineCSS", func() string {
|
||||
return string(css)
|
||||
})
|
||||
|
||||
engine.AddFunc("devContent", func() string {
|
||||
@@ -746,6 +827,10 @@ func main() {
|
||||
return ""
|
||||
})
|
||||
|
||||
engine.AddFunc("eq", func(a, b any) bool {
|
||||
return a == b
|
||||
})
|
||||
|
||||
router := fiber.New(fiber.Config{
|
||||
Views: engine,
|
||||
})
|
||||
@@ -757,17 +842,6 @@ 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,
|
||||
@@ -775,21 +849,18 @@ 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,
|
||||
"Categories": app.CategoryManager.GetCategories(),
|
||||
}
|
||||
|
||||
if app.Config.WeatherAPIKey != "" {
|
||||
weather := app.WeatherManager.GetWeather()
|
||||
if app.Config.WeatherEnabled {
|
||||
weather := app.WeatherCache.GetWeather()
|
||||
|
||||
renderData["WeatherData"] = fiber.Map{
|
||||
"Temp": weather.Temperature,
|
||||
@@ -798,11 +869,11 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
if app.Config.UptimeAPIKey != "" {
|
||||
renderData["UptimeData"] = app.UptimeManager.GetUptime()
|
||||
if app.Config.UptimeEnabled {
|
||||
renderData["UptimeData"] = app.UptimeManager.getUptime()
|
||||
}
|
||||
|
||||
return c.Render("views/index", renderData)
|
||||
return c.Render("views/index", renderData, "layouts/main")
|
||||
})
|
||||
|
||||
router.Use(middleware.AdminMiddleware(app.db))
|
||||
@@ -812,7 +883,7 @@ func main() {
|
||||
return c.Redirect().To("/admin")
|
||||
}
|
||||
|
||||
return c.Render("views/admin/login", fiber.Map{})
|
||||
return c.Render("views/admin/login", fiber.Map{}, "layouts/main")
|
||||
})
|
||||
|
||||
router.Post("/admin/login", func(c fiber.Ctx) error {
|
||||
@@ -861,8 +932,7 @@ func main() {
|
||||
|
||||
return c.Render("views/admin/index", fiber.Map{
|
||||
"Categories": app.CategoryManager.GetCategories(),
|
||||
"IsAdmin": true,
|
||||
})
|
||||
}, "layouts/main")
|
||||
})
|
||||
|
||||
api := router.Group("/api")
|
||||
@@ -891,14 +961,6 @@ func main() {
|
||||
})
|
||||
}
|
||||
|
||||
req.Name = strings.TrimSpace(req.Name)
|
||||
|
||||
if len(req.Name) > 50 {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
"message": "Name is too long. Maximum length is 50 characters",
|
||||
})
|
||||
}
|
||||
|
||||
file, err := c.FormFile("icon")
|
||||
if err != nil || file == nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
@@ -919,13 +981,17 @@ func main() {
|
||||
})
|
||||
}
|
||||
|
||||
iconPath, err := UploadFile(file, contentType, c)
|
||||
filename := fmt.Sprintf("%d_%s.svg", time.Now().Unix(), strings.ReplaceAll(req.Name, " ", "_"))
|
||||
|
||||
iconPath, err := UploadFile(file, filename, contentType, c)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
"message": "Failed to upload file, please try again!",
|
||||
})
|
||||
}
|
||||
|
||||
UploadFile(file, iconPath, contentType, c)
|
||||
|
||||
category, err := app.CategoryManager.CreateCategory(Category{
|
||||
Name: req.Name,
|
||||
Icon: iconPath,
|
||||
@@ -962,23 +1028,6 @@ func main() {
|
||||
})
|
||||
}
|
||||
|
||||
req.Name = strings.TrimSpace(req.Name)
|
||||
if req.Description != "" {
|
||||
req.Description = strings.TrimSpace(req.Description)
|
||||
}
|
||||
|
||||
if len(req.Name) > 50 {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
"message": "Name is too long. Maximum length is 50 characters",
|
||||
})
|
||||
}
|
||||
|
||||
if len(req.Description) > 150 {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
"message": "Description is too long. Maximum length is 150 characters",
|
||||
})
|
||||
}
|
||||
|
||||
categoryID, err := strconv.ParseInt(c.Params("id"), 10, 64)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
@@ -1012,7 +1061,9 @@ func main() {
|
||||
})
|
||||
}
|
||||
|
||||
iconPath, err := UploadFile(file, contentType, c)
|
||||
filename := fmt.Sprintf("%d_%s.webp", time.Now().Unix(), strings.ReplaceAll(req.Name, " ", "_"))
|
||||
|
||||
iconPath, err := UploadFile(file, filename, contentType, c)
|
||||
if err != nil {
|
||||
slog.Error("Failed to upload file", "error", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
@@ -1020,6 +1071,8 @@ func main() {
|
||||
})
|
||||
}
|
||||
|
||||
UploadFile(file, iconPath, contentType, c)
|
||||
|
||||
link, err := app.CategoryManager.CreateLink(app.CategoryManager.db, Link{
|
||||
CategoryID: categoryID,
|
||||
Name: req.Name,
|
||||
@@ -1040,241 +1093,6 @@ func main() {
|
||||
})
|
||||
})
|
||||
|
||||
api.Patch("/category/:id", func(c fiber.Ctx) error {
|
||||
var req struct {
|
||||
Name string `form:"name"`
|
||||
}
|
||||
|
||||
if c.Params("id") == "" {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
"message": "ID is required",
|
||||
})
|
||||
}
|
||||
|
||||
id, err := strconv.ParseInt(c.Params("id"), 10, 64)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
"message": fmt.Sprintf("Failed to parse category ID: %v", err),
|
||||
})
|
||||
}
|
||||
|
||||
if err := c.Bind().Form(&req); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
"message": "Failed to parse request",
|
||||
})
|
||||
}
|
||||
|
||||
if req.Name != "" {
|
||||
if len(req.Name) > 50 {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
"message": "Name is too long. Maximum length is 50 characters",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
category := app.CategoryManager.GetCategory(id)
|
||||
if category == nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
"message": "Category not found",
|
||||
})
|
||||
}
|
||||
|
||||
tx, err := app.db.Begin()
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
"message": "Failed to start transaction",
|
||||
})
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
file, err := c.FormFile("icon")
|
||||
if err == nil {
|
||||
if file.Size > 5*1024*1024 {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
"message": "File size too large. Maximum size is 5MB",
|
||||
})
|
||||
}
|
||||
|
||||
contentType := file.Header.Get("Content-Type")
|
||||
if contentType != "image/svg+xml" {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
"message": "Only svg files are allowed",
|
||||
})
|
||||
}
|
||||
|
||||
oldIconPath := category.Icon
|
||||
|
||||
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!",
|
||||
})
|
||||
}
|
||||
|
||||
_, err = tx.Exec("UPDATE categories SET icon = ? WHERE id = ?", iconPath, id)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
"message": "Failed to update category",
|
||||
})
|
||||
}
|
||||
|
||||
err = os.Remove(filepath.Join("public/", oldIconPath))
|
||||
if err != nil {
|
||||
slog.Error("Failed to delete icon", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
if req.Name != "" {
|
||||
_, err = tx.Exec("UPDATE categories SET name = ? WHERE id = ?", req.Name, category.ID)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
"message": "Failed to update category",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
"message": "Failed to commit transaction",
|
||||
})
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).JSON(fiber.Map{
|
||||
"message": "Category updated successfully",
|
||||
})
|
||||
})
|
||||
|
||||
api.Patch("/category/:categoryID/link/:linkID", func(c fiber.Ctx) error {
|
||||
var req struct {
|
||||
Name string `form:"name"`
|
||||
Description string `form:"description"`
|
||||
Icon string `form:"icon"`
|
||||
}
|
||||
if err := c.Bind().Form(&req); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
"message": "Failed to parse request",
|
||||
})
|
||||
}
|
||||
|
||||
if len(req.Name) > 50 {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
"message": "Name is too long. Maximum length is 50 characters",
|
||||
})
|
||||
}
|
||||
|
||||
if len(req.Description) > 150 {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
"message": "Description is too long. Maximum length is 150 characters",
|
||||
})
|
||||
}
|
||||
|
||||
linkID, err := strconv.ParseInt(c.Params("linkID"), 10, 64)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
"message": fmt.Sprintf("Failed to parse link ID: %v", err),
|
||||
})
|
||||
}
|
||||
|
||||
categoryID, err := strconv.ParseInt(c.Params("categoryID"), 10, 64)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
"message": fmt.Sprintf("Failed to parse category ID: %v", err),
|
||||
})
|
||||
}
|
||||
|
||||
if app.CategoryManager.GetCategory(categoryID) == nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
"message": "Category not found",
|
||||
})
|
||||
}
|
||||
|
||||
link := app.CategoryManager.GetLink(linkID)
|
||||
if link == nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
"message": "Link not found",
|
||||
})
|
||||
}
|
||||
|
||||
tx, err := app.db.Begin()
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
"message": "Failed to start transaction",
|
||||
})
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
file, err := c.FormFile("icon")
|
||||
if err == nil {
|
||||
if file.Size > 5*1024*1024 {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
"message": "File size too large. Maximum size is 5MB",
|
||||
})
|
||||
}
|
||||
|
||||
contentType := file.Header.Get("Content-Type")
|
||||
if !strings.HasPrefix(contentType, "image/") {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
"message": "Only image files are allowed",
|
||||
})
|
||||
}
|
||||
|
||||
oldIconPath := link.Icon
|
||||
|
||||
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!",
|
||||
})
|
||||
}
|
||||
|
||||
_, err = tx.Exec("UPDATE links SET icon = ? WHERE id = ?", iconPath, linkID)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
"message": "Failed to update link",
|
||||
})
|
||||
}
|
||||
|
||||
err = os.Remove(filepath.Join("public/", oldIconPath))
|
||||
if err != nil {
|
||||
slog.Error("Failed to delete icon", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
if req.Name != "" {
|
||||
_, err = tx.Exec("UPDATE links SET name = ? WHERE id = ?", req.Name, linkID)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
"message": "Failed to update link",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if req.Description != "" {
|
||||
_, err = tx.Exec("UPDATE links SET description = ? WHERE id = ?", req.Description, linkID)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
"message": "Failed to update link",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
"message": "Failed to commit transaction",
|
||||
})
|
||||
}
|
||||
|
||||
slog.Info("Link updated successfully", "id", linkID, "name", req.Name)
|
||||
|
||||
return c.Status(fiber.StatusOK).JSON(fiber.Map{
|
||||
"message": "Link updated successfully",
|
||||
})
|
||||
})
|
||||
|
||||
api.Delete("/category/:categoryID/link/:linkID", func(c fiber.Ctx) error {
|
||||
linkID, err := strconv.ParseInt(c.Params("linkID"), 10, 64)
|
||||
if err != nil {
|
||||
18
package.json
18
package.json
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"name": "passport-css-compiler",
|
||||
"private": true,
|
||||
"description": "A manifest to acquire CLI tools for passport. Not a Node app, only required for compiling CSS.",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "postcss src/styles/*.css !src/styles/base.css --dir src/assets/styles"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@fullhuman/postcss-purgecss": "^7.0.2",
|
||||
"cssnano": "^7.1.1",
|
||||
"postcss": "^8.4.35",
|
||||
"postcss-cli": "^11.0.0",
|
||||
"postcss-import": "^16.1.1",
|
||||
"postcss-preset-env": "^10.4.0"
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
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.
1131
src/scripts/admin.js
1131
src/scripts/admin.js
File diff suppressed because it is too large
Load Diff
@@ -1,111 +0,0 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type DepricatedUptimeConfig struct {
|
||||
APIKey string `env:"UPTIMEROBOT_API_KEY"`
|
||||
UpdateInterval int `env:"UPTIMEROBOT_UPDATE_INTERVAL" envDefault:"300"`
|
||||
}
|
||||
|
||||
type UptimeConfig struct {
|
||||
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 UptimeManager struct {
|
||||
sites []UptimeRobotSite
|
||||
lastUpdate time.Time
|
||||
mutex sync.RWMutex
|
||||
updateChan chan struct{}
|
||||
updateInterval int
|
||||
apiKey string
|
||||
}
|
||||
|
||||
func NewUptimeManager(config *UptimeConfig) *UptimeManager {
|
||||
if config.APIKey == "" {
|
||||
log.Fatalln("UptimeRobot API Key is required!")
|
||||
return nil
|
||||
}
|
||||
|
||||
updateInterval := config.UpdateInterval
|
||||
if updateInterval < 1 {
|
||||
updateInterval = 300
|
||||
}
|
||||
|
||||
uptimeManager := &UptimeManager{
|
||||
updateChan: make(chan struct{}),
|
||||
updateInterval: updateInterval,
|
||||
apiKey: config.APIKey,
|
||||
sites: []UptimeRobotSite{},
|
||||
}
|
||||
|
||||
go uptimeManager.updateWorker()
|
||||
|
||||
uptimeManager.updateChan <- struct{}{}
|
||||
|
||||
return uptimeManager
|
||||
}
|
||||
|
||||
func (u *UptimeManager) GetUptime() []UptimeRobotSite {
|
||||
u.mutex.RLock()
|
||||
defer u.mutex.RUnlock()
|
||||
return u.sites
|
||||
}
|
||||
|
||||
func (u *UptimeManager) updateWorker() {
|
||||
ticker := time.NewTicker(time.Duration(u.updateInterval) * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-u.updateChan:
|
||||
u.update()
|
||||
case <-ticker.C:
|
||||
u.update()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type UptimeRobotResponse struct {
|
||||
Monitors []UptimeRobotSite `json:"monitors"`
|
||||
}
|
||||
|
||||
func (u *UptimeManager) update() {
|
||||
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
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
fmt.Printf("Error reading response: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
var monitors UptimeRobotResponse
|
||||
if err := json.Unmarshal(body, &monitors); err != nil {
|
||||
fmt.Printf("Error parsing uptime data: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
u.mutex.Lock()
|
||||
u.sites = monitors.Monitors
|
||||
u.lastUpdate = time.Now()
|
||||
u.mutex.Unlock()
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type WeatherProvider string
|
||||
|
||||
const (
|
||||
OpenWeatherMap WeatherProvider = "openweathermap"
|
||||
)
|
||||
|
||||
type DepricatedWeatherConfig struct {
|
||||
OpenWeather struct {
|
||||
Provider WeatherProvider `env:"OPENWEATHER_PROVIDER" envDefault:"openweathermap"`
|
||||
APIKey string `env:"OPENWEATHER_API_KEY"`
|
||||
Units string `env:"OPENWEATHER_TEMP_UNITS" envDefault:"metric"`
|
||||
Lat float64 `env:"OPENWEATHER_LAT"`
|
||||
Lon float64 `env:"OPENWEATHER_LON"`
|
||||
}
|
||||
UpdateInterval int `env:"OPENWEATHER_UPDATE_INTERVAL" envDefault:"15"`
|
||||
}
|
||||
|
||||
type WeatherConfig struct {
|
||||
Provider WeatherProvider `env:"WEATHER_PROVIDER" envDefault:"openweathermap"`
|
||||
APIKey string `env:"WEATHER_API_KEY"`
|
||||
Units string `env:"WEATHER_TEMP_UNITS" envDefault:"metric"`
|
||||
Lat float64 `env:"WEATHER_LAT"`
|
||||
Lon float64 `env:"WEATHER_LON"`
|
||||
UpdateInterval int `env:"WEATHER_UPDATE_INTERVAL" envDefault:"15"`
|
||||
}
|
||||
|
||||
type OpenWeatherResponse struct {
|
||||
Weather []struct {
|
||||
Name string `json:"main"`
|
||||
IconId string `json:"icon"`
|
||||
} `json:"weather"`
|
||||
Main struct {
|
||||
Temp float64 `json:"temp"`
|
||||
} `json:"main"`
|
||||
Code int `json:"cod"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type WeatherData struct {
|
||||
Temperature float64
|
||||
WeatherText string
|
||||
Icon string
|
||||
}
|
||||
|
||||
type WeatherManager struct {
|
||||
data *WeatherData
|
||||
lastUpdate time.Time
|
||||
mutex sync.RWMutex
|
||||
updateChan chan struct{}
|
||||
config *WeatherConfig
|
||||
}
|
||||
|
||||
func NewWeatherManager(config *WeatherConfig) *WeatherManager {
|
||||
if config.Provider != OpenWeatherMap {
|
||||
log.Fatalln("Only OpenWeatherMap is supported!")
|
||||
return nil
|
||||
}
|
||||
|
||||
if config.APIKey == "" {
|
||||
log.Fatalln("An API Key required for OpenWeather!")
|
||||
return nil
|
||||
}
|
||||
|
||||
updateInterval := config.UpdateInterval
|
||||
if updateInterval < 1 {
|
||||
updateInterval = 15
|
||||
}
|
||||
|
||||
units := config.Units
|
||||
if units == "" {
|
||||
units = "metric"
|
||||
}
|
||||
|
||||
cache := &WeatherManager{
|
||||
data: &WeatherData{},
|
||||
updateChan: make(chan struct{}),
|
||||
config: config,
|
||||
}
|
||||
|
||||
go cache.weatherWorker()
|
||||
|
||||
cache.updateChan <- struct{}{}
|
||||
|
||||
return cache
|
||||
}
|
||||
|
||||
func (c *WeatherManager) GetWeather() WeatherData {
|
||||
c.mutex.RLock()
|
||||
defer c.mutex.RUnlock()
|
||||
return *c.data
|
||||
}
|
||||
|
||||
func (c *WeatherManager) weatherWorker() {
|
||||
ticker := time.NewTicker(time.Duration(c.config.UpdateInterval) * time.Minute)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-c.updateChan:
|
||||
c.updateWeather()
|
||||
case <-ticker.C:
|
||||
c.updateWeather()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *WeatherManager) updateWeather() {
|
||||
url := fmt.Sprintf("https://api.openweathermap.org/data/2.5/weather?lat=%f&lon=%f&appid=%s&units=%s",
|
||||
c.config.Lat, c.config.Lon, c.config.APIKey, c.config.Units)
|
||||
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
fmt.Printf("Error fetching weather: %v\n", err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
fmt.Printf("Error reading response: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
var weatherResp OpenWeatherResponse
|
||||
if err := json.Unmarshal(body, &weatherResp); err != nil {
|
||||
fmt.Printf("Error parsing weather data: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// if the request failed
|
||||
if weatherResp.Code != 200 {
|
||||
// if there is no pre-existing data in the cache
|
||||
if c.data.WeatherText == "" {
|
||||
log.Fatalf("Fetching the weather data failed!\n%s\n", weatherResp.Message)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c.mutex.Lock()
|
||||
c.data.Temperature = weatherResp.Main.Temp
|
||||
c.data.WeatherText = weatherResp.Weather[0].Name
|
||||
c.data.Icon = weatherResp.Weather[0].IconId
|
||||
c.lastUpdate = time.Now()
|
||||
c.mutex.Unlock()
|
||||
}
|
||||
@@ -1,238 +0,0 @@
|
||||
@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 {
|
||||
background-color: var(--color-accent);
|
||||
color: #fff;
|
||||
border-radius: calc(var(--spacing) * 1.5);
|
||||
padding-inline: calc(var(--spacing) * 4);
|
||||
padding-block: calc(var(--spacing) * 2);
|
||||
}
|
||||
}
|
||||
|
||||
.delete-modal {
|
||||
text-align: center;
|
||||
|
||||
& > p {
|
||||
margin-bottom: calc(var(--spacing) * 3);
|
||||
}
|
||||
|
||||
& > div {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
flex-direction: column;
|
||||
row-gap: calc(var(--spacing) * 2);
|
||||
|
||||
& > button {
|
||||
padding-inline: calc(var(--spacing) * 4);
|
||||
padding-block: calc(var(--spacing) * 2);
|
||||
border-radius: calc(var(--spacing) * 1.5);
|
||||
width: 100%;
|
||||
color: #fff;
|
||||
transition: filter 300ms cubic-bezier(0.45, 0, 0.55, 1);
|
||||
|
||||
&:nth-child(1) {
|
||||
background-color: var(--color-error);
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
border: 1px solid var(--color-highlight);
|
||||
background-color: #0000;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
filter: brightness(125%);
|
||||
}
|
||||
|
||||
&:active {
|
||||
filter: brightness(95%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input:invalid.invalid {
|
||||
border: 1px solid var(--color-error);
|
||||
}
|
||||
|
||||
.new-link-card {
|
||||
border: calc(var(--spacing) * 0.5) dashed var(--color-subtle);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.action-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: calc(var(--spacing) * 2);
|
||||
}
|
||||
|
||||
.link-grid > div > div:nth-child(3) {
|
||||
position: absolute;
|
||||
right: var(--spacing);
|
||||
top: var(--spacing);
|
||||
}
|
||||
|
||||
.category-header > div:nth-child(2) {
|
||||
padding-left: calc(var(--spacing) * 2);
|
||||
}
|
||||
|
||||
.add-category-button {
|
||||
width: fit-content;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--color-subtle);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.add-category-button > h2 {
|
||||
text-decoration: underline;
|
||||
text-decoration-style: dashed;
|
||||
text-decoration-thickness: calc(var(--spacing) * 0.5);
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.action-button {
|
||||
display: flex;
|
||||
width: fit-content;
|
||||
height: fit-content;
|
||||
padding: 0.25rem;
|
||||
background-color: var(--color-highlight-sm);
|
||||
border: 1px solid color-mix(in srgb, var(--color-highlight) 70%, #0000);
|
||||
border-radius: 9999px;
|
||||
box-shadow: var(--shadow-sm);
|
||||
cursor: pointer;
|
||||
transition: filter 0.15s cubic-bezier(0.45, 0, 0.55, 1);
|
||||
contain: layout style paint;
|
||||
|
||||
&:hover {
|
||||
filter: brightness(125%);
|
||||
}
|
||||
|
||||
&:active {
|
||||
filter: brightness(95%);
|
||||
}
|
||||
}
|
||||
|
||||
.select-icon-button {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: color-mix(in srgb, var(--color-highlight) 70%, #0000);
|
||||
color: var(--color-base);
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
header {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
padding: calc(var(--spacing) * 3);
|
||||
|
||||
& > a {
|
||||
text-decoration: none;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: calc(var(--spacing) * 2);
|
||||
color: var(--color-text);
|
||||
border-bottom: 1px solid var(--color-text);
|
||||
justify-content: center;
|
||||
line-height: var(--leading-condensed);
|
||||
|
||||
&:hover {
|
||||
border-bottom-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,199 +0,0 @@
|
||||
@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);
|
||||
}
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
/* All css related to the card and category stuff */
|
||||
|
||||
@layer components {
|
||||
.link-grid > :is(a, div) {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border-radius: calc(var(--spacing) * 4);
|
||||
padding: calc(var(--spacing) * 2.5);
|
||||
align-items: center;
|
||||
transition-property: box-shadow, transform, translate;
|
||||
transition-duration: 150ms;
|
||||
transition-timing-function: cubic-bezier(0.45, 0, 0.55, 1);
|
||||
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1),
|
||||
0 2px 4px -2px rgb(0 0 0 / 0.1);
|
||||
contain: layout style paint;
|
||||
|
||||
&:not(:is(div)) {
|
||||
&:hover {
|
||||
box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1),
|
||||
0 4px 6px -4px rgb(0 0 0 / 0.1);
|
||||
transform: translateY(-4px);
|
||||
}
|
||||
|
||||
&:active {
|
||||
box-shadow: 0 2px 4px -2px rgb(0 0 0 / 0.1);
|
||||
transform: translateY(2px);
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.new-link-card) {
|
||||
text-decoration: none;
|
||||
background: var(--color-overlay);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.link-grid > :is(a, div) {
|
||||
transition: none;
|
||||
|
||||
&:hover {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Div that holds the image */
|
||||
.link-grid > :is(a, div) > div:first-child {
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
margin-right: calc(var(--spacing) * 2);
|
||||
border-radius: calc(var(--spacing) * 2.5);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.link-grid > :is(a, div) > div:first-child img {
|
||||
user-select: none;
|
||||
aspect-ratio: 1/1;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
/* Div that holds the text */
|
||||
.link-grid > :is(a, div) > div:nth-child(2) {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
row-gap: 1px;
|
||||
overflow: hidden;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.link-grid > :is(a, div) > div:nth-child(2) h3 {
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.link-grid > :is(a, div) > div:nth-child(2) p {
|
||||
color: var(--color-subtle);
|
||||
white-space: pre-wrap;
|
||||
border: 1px solid #0000;
|
||||
min-height: calc(1em + 0.5rem);
|
||||
border: 1px solid transparent;
|
||||
min-height: 26px;
|
||||
}
|
||||
|
||||
.category-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.category-header > div:first-child {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
margin-right: calc(var(--spacing) * 2);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: calc(var(--spacing) * 8);
|
||||
height: calc(var(--spacing) * 8);
|
||||
border-radius: calc(var(--spacing) * 1.5);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.categoy-header > div:first-child img {
|
||||
user-select: none;
|
||||
object-fit: cover;
|
||||
aspect-ratio: 1/1;
|
||||
}
|
||||
|
||||
.category-header h2 {
|
||||
text-transform: capitalize;
|
||||
word-break: break-all;
|
||||
border-width: 1px;
|
||||
border-color: #0000;
|
||||
}
|
||||
|
||||
.link-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(min(330px, 100%), 1fr));
|
||||
gap: calc(var(--spacing) * 2);
|
||||
padding: calc(var(--spacing) * 2.5);
|
||||
contain: layout style paint;
|
||||
}
|
||||
|
||||
/* Empty state */
|
||||
.link-grid > p {
|
||||
color: var(--color-subtle);
|
||||
}
|
||||
|
||||
.card-section {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.card-section > div {
|
||||
width: 100%;
|
||||
padding: calc(var(--spacing) * 2.5);
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.card-section > div {
|
||||
width: 80%;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
@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;
|
||||
}
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
@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,111 +0,0 @@
|
||||
<section class="card-section">
|
||||
<div>
|
||||
{{#each Categories}}
|
||||
<div class="category-header" id="{{this.ID}}_category">
|
||||
<div>
|
||||
<img width="32" height="32" draggable="false" alt="{{this.Name}}" src="{{this.Icon}}" />
|
||||
</div>
|
||||
<h2 class="capitalize break-all">{{this.Name}}</h2>
|
||||
{{#if IsAdmin}}
|
||||
<div>
|
||||
<div class="action-container">
|
||||
<button aria-label="Edit category" onclick="editCategory(this)" class="action-button">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<use href="#edit-icon" />
|
||||
</svg>
|
||||
</button>
|
||||
<button aria-label="Delete category" onclick="deleteCategory(this)"
|
||||
class="text-error action-button">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<use href="#trash-icon" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="link-grid">
|
||||
{{#each this.Links}}
|
||||
|
||||
{{#if IsAdmin}}<div data-card id="{{this.ID}}_link" {{else}} <a href="{{this.URL}}" draggable="false"
|
||||
target="_blank" rel="noreferrer" {{/if}}>
|
||||
|
||||
<div>
|
||||
<img width="64" height="64" draggable="false" src="{{this.Icon}}" alt="{{this.Name}}" />
|
||||
</div>
|
||||
<div>
|
||||
<h3>{{this.Name}}</h3>
|
||||
<p>{{this.Description}}</p>
|
||||
</div>
|
||||
{{#if IsAdmin}}
|
||||
<div>
|
||||
<div class="action-container">
|
||||
<button aria-label="Edit link" onclick="editLink(this)" class="action-button">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<use href="#edit-icon" />
|
||||
</svg>
|
||||
</button>
|
||||
<button aria-label="Delete link" onclick="deleteLink(this)" class="text-error action-button">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<use href="#trash-icon" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if IsAdmin}}
|
||||
</div {{else}} </a {{/if}}>
|
||||
|
||||
{{else}}
|
||||
{{#unless IsAdmin}}
|
||||
<p class="text-subtle">No links here, add one!</p>
|
||||
{{/unless}}
|
||||
{{/each}}
|
||||
|
||||
{{#if IsAdmin}}
|
||||
<div onclick="openModal('link', {{this.ID}})" class="new-link-card link-card admin">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 24 24">
|
||||
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="2" d="M12 5v14m-7-7h14" />
|
||||
</svg>
|
||||
<div>
|
||||
<h3>Add a link</h3>
|
||||
</div>
|
||||
</div>
|
||||
{{/if }}
|
||||
</div>
|
||||
{{/each}}
|
||||
|
||||
{{#if IsAdmin}}
|
||||
<div class="add-category-button" id="add-category-button">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24">
|
||||
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M12 5v14m-7-7h14" />
|
||||
</svg>
|
||||
<h2 onclick="openModal('category')">
|
||||
Add a new category
|
||||
</h2>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- store the svg icons here so that they can be reused -->
|
||||
{{#if IsAdmin}}
|
||||
<div class="hidden">
|
||||
<svg id="edit-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24"
|
||||
viewBox="0 0 24 24"><!-- Icon from Tabler Icons by Paweł Kuna - https://github.com/tabler/tabler-icons/blob/master/LICENSE -->
|
||||
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
|
||||
<path d="M7 7H6a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2-2v-1" />
|
||||
<path d="M20.385 6.585a2.1 2.1 0 0 0-2.97-2.97L9 12v3h3zM16 5l3 3" />
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
<svg id="trash-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24"
|
||||
viewBox="0 0 24 24"><!-- Icon from Tabler Icons by Paweł Kuna - https://github.com/tabler/tabler-icons/blob/master/LICENSE -->
|
||||
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M4 7h16m-10 4v6m4-6v6M5 7l1 12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2l1-12M9 7V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v3" />
|
||||
</svg>
|
||||
</div>
|
||||
{{/if}}
|
||||
@@ -1,16 +0,0 @@
|
||||
<div id="category-contents" class="hidden">
|
||||
<h3>Create A category</h3>
|
||||
<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" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="linkIcon">Icon</label>
|
||||
<input type="file" name="icon" id="linkIcon" accept=".svg" required />
|
||||
</div>
|
||||
<button type="submit">Create
|
||||
category</button>
|
||||
</form>
|
||||
<span id="category-message"></span>
|
||||
</div>
|
||||
@@ -1,11 +0,0 @@
|
||||
<div id="category-delete-contents" class="hidden delete-modal">
|
||||
<h3>Are you sure you want to delete this category?</h3>
|
||||
<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>
|
||||
<button onclick="confirmDeleteCategory()">Delete
|
||||
category</button>
|
||||
<button onclick="closeModal()">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,11 +0,0 @@
|
||||
<div id="link-delete-contents" class="hidden delete-modal">
|
||||
<h3>Are you sure you want to delete this link?</h3>
|
||||
<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>
|
||||
<button onclick="confirmDeleteLink()">Delete
|
||||
link</button>
|
||||
<button onclick="closeModal()">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,24 +0,0 @@
|
||||
<div id="link-contents" class="hidden">
|
||||
<h3>Add A link</h3>
|
||||
<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" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="linkDesc">Description (optional)</label>
|
||||
<input type="text" name="description" id="linkDesc" maxlength="150" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="linkURL">URL</label>
|
||||
<input required type="url" name="url" id="linkURL" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="linkIcon">Icon</label>
|
||||
<input required type="file" name="icon" id="linkIcon" accept="image/*" />
|
||||
</div>
|
||||
<button type="submit">Add
|
||||
link</button>
|
||||
</form>
|
||||
<span id="link-message"></span>
|
||||
</div>
|
||||
@@ -1,132 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Passport</title>
|
||||
<link rel="favicon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="preload" as="font" type="font/woff2" crossorigin="anonymous"
|
||||
href="/assets/fonts/InstrumentSans-VariableFont_wdth,wght.woff2" />
|
||||
{{{embedFile "assets/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>
|
||||
|
||||
<!-- store a blank link card so that if we add a new link we can clone it to make the editing experience easier -->
|
||||
<div id="template-link-card" class="hidden">
|
||||
<div>
|
||||
<img width="64" height="64" draggable="false" />
|
||||
</div>
|
||||
<div>
|
||||
<h3></h3>
|
||||
<!-- add 2 to the height to account for the border -->
|
||||
<p></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="template-category" class="hidden">
|
||||
<div class="category-header">
|
||||
<div>
|
||||
<img width="32" height="32" draggable="false" />
|
||||
</div>
|
||||
<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" />
|
||||
</svg>
|
||||
<div>
|
||||
<h3>Add a link</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="template-edit-actions" class="hidden">
|
||||
<div class="flex flex-row gap-2">
|
||||
<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>
|
||||
|
||||
<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()">
|
||||
<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>
|
||||
|
||||
{{{embedFile "scripts/admin.js"}}}
|
||||
</body>
|
||||
|
||||
{{{devContent}}}
|
||||
|
||||
</html>
|
||||
@@ -1,88 +0,0 @@
|
||||
<!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="weather-data">
|
||||
<span>
|
||||
{{{WeatherData.Icon}}}
|
||||
</span>
|
||||
<div class="font-semibold">
|
||||
<p class="leading-condensed">{{WeatherData.Temp}}°C</p>
|
||||
<p class="leading-condensed">{{WeatherData.Desc}}</p>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if UptimeData}}
|
||||
<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>
|
||||
<span>
|
||||
{{this.FriendlyName}}
|
||||
</span>
|
||||
<div class="uptime-status">
|
||||
<svg viewBox="0 0 10 10">
|
||||
<use href="#status-dot"
|
||||
class="{{#if (eq this.Status 2)}}text-success{{else}}text-error{{/if}}">
|
||||
</use>
|
||||
</svg>
|
||||
<svg viewBox="0 0 10 10">
|
||||
<use href="#status-dot"
|
||||
class="{{#if (eq this.Status 2)}}text-success{{else}}text-error{{/if}}">
|
||||
</use>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
<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>
|
||||
</main>
|
||||
{{> 'partials/category-grid' }}
|
||||
</body>
|
||||
|
||||
{{{devContent}}}
|
||||
|
||||
</html>
|
||||
49
styles/main.css
Normal file
49
styles/main.css
Normal file
@@ -0,0 +1,49 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
input:not(.search) {
|
||||
@apply px-4 py-2 rounded-md w-full bg-[#1C1C21] border border-[#2E2E32] placeholder:text-[#8A8E90] text-white 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-[hsl(240,6%,18%)] text-white cursor-pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
16
templates/layouts/main.hbs
Normal file
16
templates/layouts/main.hbs
Normal file
@@ -0,0 +1,16 @@
|
||||
<!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-[#151316] text-white">
|
||||
{{embed}}
|
||||
</body>
|
||||
{{{devContent}}}
|
||||
</html>
|
||||
272
templates/views/admin/index.hbs
Normal file
272
templates/views/admin/index.hbs
Normal file
@@ -0,0 +1,272 @@
|
||||
<div id="blur-target" class="transition-[filter] 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>
|
||||
|
||||
<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}}">
|
||||
<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>
|
||||
<button onclick="deleteCategory({{this.ID}})"
|
||||
class="w-fit h-fit flex p-0.5 bg-[#1C1C21] border-solid border-[#211F23] rounded-md hover:bg-[#29292e] cursor-pointer"><svg
|
||||
xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24">
|
||||
<path fill="none" stroke="#ff1919" 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="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="rounded-2xl bg-[#211F23] p-2.5 flex flex-row items-center shadow-md hover:shadow-xl transition-[shadow,transform,translate] ease-[cubic-bezier(0.16,1,0.3,1)] hover:-translate-y-1 relative">
|
||||
<img class="mr-2 select-none rounded-md aspect-square object-cover" width="64" height="64"
|
||||
draggable="false" src="{{this.Icon}}" alt="{{this.Name}}" />
|
||||
<div class="break-all">
|
||||
<h3>{{this.Name}}</h3>
|
||||
<p class="text-[#D7D7D7]">{{this.Description}}</p>
|
||||
</div>
|
||||
<button onclick="deleteLink({{this.ID}}, {{this.CategoryID}})"
|
||||
class="w-fit h-fit flex p-0.5 bg-[#1C1C21] border-solid border-[#211F23] rounded-md hover:bg-[#29292e] cursor-pointer absolute right-1 top-1"><svg
|
||||
xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24">
|
||||
<path fill="none" stroke="#ff1919" 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>
|
||||
{{/each}}
|
||||
<div onclick="openModal('link', {{this.ID}})"
|
||||
class="rounded-2xl border border-dashed border-[#656565] p-2.5 flex flex-row items-center shadow-md hover:shadow-xl transition-[shadow,transform] ease-[cubic-bezier(0.16,1,0.3,1)] pointer-cursor select-none cursor-pointer">
|
||||
<svg class="mr-2" xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 24 24">
|
||||
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="2" d="M12 5v14m-7-7h14" />
|
||||
</svg>
|
||||
<div>
|
||||
<h3>Add a link</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
<div class="flex items-center">
|
||||
<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-[#656565] underline decoration-dashed cursor-pointer">
|
||||
Add a new category
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div id="modal-container"
|
||||
class="flex modal-bg fixed top-0 left-0 bottom-0 right-0 bg-[#00000070] justify-center items-center">
|
||||
<div class="bg-[#151316] rounded-xl overflow-hidden w-fit p-4 modal">
|
||||
<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">
|
||||
<div>
|
||||
<label for="categoryName">Name</label>
|
||||
<input required type="text" name="name" id="categoryName" />
|
||||
</div>
|
||||
<div>
|
||||
<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-[#8A42FF] text-white border-0" type="submit">Create
|
||||
category</button>
|
||||
</form>
|
||||
<span id="category-message"></span>
|
||||
</div>
|
||||
|
||||
<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">
|
||||
<div>
|
||||
<label for="linkName">Name</label>
|
||||
<input required type="text" name="name" id="linkName" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="linkDesc">Description (optional)</label>
|
||||
<input type="text" name="description" id="linkDesc" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="linkURL">URL</label>
|
||||
<input required type="url" name="url" id="linkURL" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="linkIcon">Icon</label>
|
||||
<input required type="file" name="icon" id="linkIcon" accept="image/*" />
|
||||
</div>
|
||||
<button class="px-4 py-2 rounded-md w-full bg-[#8A42FF] text-white border-0" type="submit">Add
|
||||
link</button>
|
||||
</form>
|
||||
<span id="link-message"></span>
|
||||
</div>
|
||||
</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 targetCategoryID = null;
|
||||
let activeModal = null;
|
||||
|
||||
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");
|
||||
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)
|
||||
|
||||
document.getElementById(activeModal + "-form").querySelectorAll("[required]").forEach((el) => {
|
||||
el.classList.remove("invalid:border-[#861024]!");
|
||||
});
|
||||
|
||||
targetCategoryID = null;
|
||||
}
|
||||
|
||||
modalContainer.addEventListener("click", (event) => {
|
||||
if (event.target === modalContainer) {
|
||||
closeModal();
|
||||
}
|
||||
});
|
||||
|
||||
async function deleteLink(linkID, categoryID) {
|
||||
let res = await fetch(`/api/category/${categoryID}/link/${linkID}`, {
|
||||
method: "DELETE"
|
||||
});
|
||||
|
||||
if (res.status === 200) {
|
||||
let linkEl = document.querySelector(`[key="link-${linkID}"]`);
|
||||
linkEl.remove();
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteCategory(categoryID) {
|
||||
let res = await fetch(`/api/category/${categoryID}`, {
|
||||
method: "DELETE"
|
||||
});
|
||||
|
||||
if (res.status === 200) {
|
||||
let categoryEl = document.querySelector(`[key="category-${categoryID}"]`);
|
||||
// get the next element and remove it (its the link grid)
|
||||
let nextEl = categoryEl.nextElementSibling;
|
||||
nextEl.remove();
|
||||
categoryEl.remove();
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById("link-form").querySelector("button").addEventListener("click", (event) => {
|
||||
document.getElementById("link-form").querySelectorAll("[required]").forEach((el) => {
|
||||
el.classList.add("invalid:border-[#861024]!");
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById("link-form").addEventListener("submit", async (event) => {
|
||||
event.preventDefault();
|
||||
let data = new FormData(event.target);
|
||||
|
||||
let res = await fetch(`/api/category/${targetCategoryID}/link`, {
|
||||
method: "POST",
|
||||
body: data
|
||||
});
|
||||
|
||||
if (res.status === 201) {
|
||||
closeModal('link');
|
||||
document.getElementById("link-form").reset();
|
||||
location.reload();
|
||||
} else {
|
||||
let json = await res.json();
|
||||
document.getElementById("category-message").innerText = json.message;
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("category-form").querySelector("button").addEventListener("click", (event) => {
|
||||
document.getElementById("category-form").querySelectorAll("[required]").forEach((el) => {
|
||||
el.classList.add("invalid:border-[#861024]!");
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById("category-form").addEventListener("submit", async (event) => {
|
||||
event.preventDefault();
|
||||
let data = new FormData(event.target);
|
||||
|
||||
let res = await fetch(`/api/category`, {
|
||||
method: "POST",
|
||||
body: data
|
||||
});
|
||||
|
||||
if (res.status === 201) {
|
||||
closeModal('category');
|
||||
document.getElementById("category-form").reset();
|
||||
location.reload();
|
||||
} else {
|
||||
let json = await res.json();
|
||||
document.getElementById("link-message").innerText = json.message;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.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>
|
||||
@@ -1,31 +1,21 @@
|
||||
<!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>
|
||||
<main class="flex justify-center items-center h-screen relative bg-[#0E0A0E]">
|
||||
<div class="flex bg-[#151316] 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">
|
||||
Login
|
||||
</h2>
|
||||
<form action="/admin/login" method="post" class="login-form">
|
||||
<form action="/admin/login" method="post" class="flex flex-col gap-y-3 my-2">
|
||||
<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>
|
||||
<button class="px-4 py-2 rounded-md w-full bg-[#8A42FF] text-white border-0"
|
||||
type="submit">Login</button>
|
||||
</form>
|
||||
<span id="message"></span>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
let message = document.getElementById("message");
|
||||
let form = document.querySelector("form");
|
||||
@@ -54,6 +44,3 @@
|
||||
message.innerText = (await res.json()).message;
|
||||
});
|
||||
</script>
|
||||
{{{devContent}}}
|
||||
|
||||
</html>
|
||||
95
templates/views/index.hbs
Normal file
95
templates/views/index.hbs
Normal file
@@ -0,0 +1,95 @@
|
||||
<main class="grid grid-rows-3 grid-cols-[1fr] justify-center items-center h-screen bg-[#0E0A0E]">
|
||||
<div class="flex h-full p-2.5 justify-between">
|
||||
<div>
|
||||
{{#if WeatherData}}
|
||||
<div class="text-[#BABABA] flex items-center">
|
||||
<span class="mr-2 flex items-center">
|
||||
{{{WeatherData.Icon}}}
|
||||
</span>
|
||||
<div class="font-semibold">
|
||||
<p>{{WeatherData.Temp}}°C</p>
|
||||
<p>{{WeatherData.Desc}}</p>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div>
|
||||
{{#if UptimeData}}
|
||||
<div class="text-[#BABABA] flex items-end flex-col">
|
||||
{{#each UptimeData}}
|
||||
<div class="flex items-center">
|
||||
<span class="mr-2 flex items-center">
|
||||
{{{this.FriendlyName}}}
|
||||
</span>
|
||||
<div class="relative my-auto">
|
||||
{{#if (eq this.Status 2)}}
|
||||
<span class="absolute w-2 h-2 rounded-full bg-emerald-400 animate-ping block"></span>
|
||||
<span class="relative w-2 h-2 rounded-full bg-emerald-500 block"></span>
|
||||
{{else}}
|
||||
<span class="absolute w-2 h-2 rounded-full bg-rose-400 animate-ping block"></span>
|
||||
<span class="relative w-2 h-2 rounded-full bg-rose-500 block"></span>
|
||||
{{/if}}
|
||||
</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>
|
||||
<form class="w-full max-w-3xl" action="{{ SearchProviderURL }}" method="GET">
|
||||
<input name="{{ SearchParam }}" aria-label="Search bar"
|
||||
class="w-full bg-[#1C1C21] border border-[#2E2E32] rounded-full px-3 py-1 text-white h-7 focus-visible:outline-none placeholder:italic placeholder:text-[#434343] search"
|
||||
placeholder="Search..." />
|
||||
</form>
|
||||
</div>
|
||||
</main>
|
||||
<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 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="underline-none text-unset rounded-2xl bg-[#211F23] p-2.5 flex flex-row items-center shadow-md hover:shadow-xl transition-[shadow,transform,translate] ease-[cubic-bezier(0.16,1,0.3,1)] hover:-translate-y-1"
|
||||
draggable="false" target="_blank" rel="noopener noreferrer">
|
||||
<img class="mr-2 select-none rounded-md aspect-square object-cover" width="64" height="64"
|
||||
draggable="false" src="{{this.Icon}}" alt="{{this.Name}}" />
|
||||
<div class="break-all">
|
||||
<h3>{{this.Name}}</h3>
|
||||
<p class="text-[#D7D7D7]">{{this.Description}}</p>
|
||||
</div>
|
||||
</a>
|
||||
{{else}}
|
||||
<p class="text-[#D7D7D7]">No links here, add one!</p>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "passport",
|
||||
"version": "0.3.3",
|
||||
"version": "0.2.0",
|
||||
"description": "Passport is a simple, lightweight, and fast dashboard/new tab page for your browser.",
|
||||
"author": "juls0730",
|
||||
"license": "BSL-1.0",
|
||||
@@ -10,14 +10,9 @@
|
||||
"url": "https://github.com/juls0730/passport.git"
|
||||
},
|
||||
"scripts": {
|
||||
"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",
|
||||
"build:amd64": "zqdgr generate && GOOS=linux GOARCH=amd64 go build -tags netgo,prod -ldflags=\"-w -s\" -o passport src/main.go",
|
||||
"build:arm64": "zqdgr generate && GOOS=linux GOARCH=arm64 go build -tags netgo,prod -ldflags=\"-w -s\" -o passport src/main.go"
|
||||
"dev": "go generate; PASSPORT_DEV_MODE=true go run main.go",
|
||||
"build": "go generate && go build -tags netgo,prod -ldflags=\"-w -s\" -o passport"
|
||||
},
|
||||
"pattern": "src/**/*.{go,hbs,css,js,svg,png,jpg,jpeg,webp,woff2,ico,webp}",
|
||||
"excluded_files": ["src/assets/styles"],
|
||||
"pattern": "**/*.go,templates/views/**/*.hbs,styles/**/*.css,assets/**/*.{svg,png,jpg,jpeg,webp,woff2,ttf,otf,eot,ico,gif,webp}",
|
||||
"shutdown_signal": "SIGINT"
|
||||
}
|
||||
Reference in New Issue
Block a user