Compare commits
37 Commits
fix/carbon
...
refactor/c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ca5a2ec97 | ||
|
|
58dae5609b | ||
|
|
30c130e4e7 | ||
|
|
ce16ac85d4 | ||
|
|
cedbca1869 | ||
|
|
e9a4880908 | ||
|
|
2f82c53b28 | ||
|
|
920e98dc66 | ||
|
|
889db39ab3 | ||
|
|
78b1aec82c | ||
|
|
8848e0c8fe | ||
|
|
81607c5690 | ||
|
|
07c52fa9e6 | ||
|
|
b4c4fe2634 | ||
|
|
df3c53818a | ||
|
|
d6cb15aab0 | ||
|
|
293f67bcc8 | ||
|
|
2c71578fa1 | ||
|
|
a8a24ae364 | ||
|
|
73a75ae124 | ||
|
|
3a91838364 | ||
|
|
185e5fa075 | ||
|
|
a456697fbe | ||
|
|
5f33a8a401 | ||
|
|
699302e475 | ||
|
|
372d2bf652 | ||
|
|
97a870ca49 | ||
|
|
df229355fb | ||
|
|
0f3afe29fe | ||
|
|
6940dbeca5 | ||
|
|
0d7c599a3a | ||
|
|
b4f1a6385d | ||
|
|
ff3312bef1 | ||
|
|
a3b6c4765a | ||
|
|
da6c0b88b4 | ||
|
|
32a7c170a4 | ||
|
|
c355e76f91 |
@@ -1,5 +1,5 @@
|
|||||||
name: "Add light & dark icon"
|
name: "Add light/dark icon"
|
||||||
description: Use this template to add a new icon to the project. Monochrome icons need both light and dark versions.
|
description: Submit a new icon with light and dark versions.
|
||||||
title: "feat(icons): add [NAME]"
|
title: "feat(icons): add [NAME]"
|
||||||
labels: ["monochrome-icon"]
|
labels: ["monochrome-icon"]
|
||||||
body:
|
body:
|
||||||
|
|||||||
4
.github/ISSUE_TEMPLATE/add_normal_icon.yml
vendored
@@ -1,5 +1,5 @@
|
|||||||
name: "Add normal icon"
|
name: "Add standard icon"
|
||||||
description: Use this template to add a new icon to the project. Normal icons work for both light and dark themes.
|
description: Submit a new icon for both light and dark themes.
|
||||||
title: "feat(icons): add [NAME]"
|
title: "feat(icons): add [NAME]"
|
||||||
labels: ["normal-icon"]
|
labels: ["normal-icon"]
|
||||||
body:
|
body:
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
name: "Update light & dark icon"
|
name: "Update light/dark icon"
|
||||||
description: Use this template to update an existing icon. Monochrome icons need both light and dark versions.
|
description: Improve or update an existing light/dark icon.
|
||||||
title: "feat(icons): update [NAME]"
|
title: "feat(icons): update [NAME]"
|
||||||
labels: ["monochrome-icon"]
|
labels: ["monochrome-icon"]
|
||||||
body:
|
body:
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
name: "Update normal icon"
|
name: "Update standard icon"
|
||||||
description: Use this template to update an existing icon. Normal icons work for both light and dark themes.
|
description: Improve or update an existing standard icon.
|
||||||
title: "feat(icons): update [NAME]"
|
title: "feat(icons): update [NAME]"
|
||||||
labels: ["normal-icon"]
|
labels: ["normal-icon"]
|
||||||
body:
|
body:
|
||||||
|
|||||||
14
meta/buildium.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"base": "svg",
|
||||||
|
"aliases": [],
|
||||||
|
"categories": [
|
||||||
|
"Finance"
|
||||||
|
],
|
||||||
|
"update": {
|
||||||
|
"timestamp": "2025-04-22T18:36:00.262663",
|
||||||
|
"author": {
|
||||||
|
"id": 41155244,
|
||||||
|
"login": "giovannicalabro"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
meta/calibre-web-automated-book-downloader.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"base": "png",
|
||||||
|
"aliases": [],
|
||||||
|
"categories": [
|
||||||
|
"Media"
|
||||||
|
],
|
||||||
|
"update": {
|
||||||
|
"timestamp": "2025-04-22T17:31:36.311538",
|
||||||
|
"author": {
|
||||||
|
"id": 2319445,
|
||||||
|
"login": "samcro1967"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
meta/doplarr.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"base": "svg",
|
||||||
|
"aliases": [],
|
||||||
|
"categories": [
|
||||||
|
"Media"
|
||||||
|
],
|
||||||
|
"update": {
|
||||||
|
"timestamp": "2025-04-22T17:34:50.612136",
|
||||||
|
"author": {
|
||||||
|
"id": 2319445,
|
||||||
|
"login": "samcro1967"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
meta/entergy.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"base": "svg",
|
||||||
|
"aliases": [],
|
||||||
|
"categories": [
|
||||||
|
"Finance"
|
||||||
|
],
|
||||||
|
"update": {
|
||||||
|
"timestamp": "2025-04-22T18:33:50.980132",
|
||||||
|
"author": {
|
||||||
|
"id": 41155244,
|
||||||
|
"login": "giovannicalabro"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
meta/gone-man-switch.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"base": "png",
|
||||||
|
"aliases": [],
|
||||||
|
"categories": [],
|
||||||
|
"update": {
|
||||||
|
"timestamp": "2025-04-22T17:37:06.429465",
|
||||||
|
"author": {
|
||||||
|
"id": 2319445,
|
||||||
|
"login": "samcro1967"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
meta/image-maid.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"base": "png",
|
||||||
|
"aliases": [],
|
||||||
|
"categories": [
|
||||||
|
"Media"
|
||||||
|
],
|
||||||
|
"update": {
|
||||||
|
"timestamp": "2025-04-22T18:22:01.834809",
|
||||||
|
"author": {
|
||||||
|
"id": 2319445,
|
||||||
|
"login": "samcro1967"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
meta/karaoke-eternal.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"base": "png",
|
||||||
|
"aliases": [],
|
||||||
|
"categories": [
|
||||||
|
"Media"
|
||||||
|
],
|
||||||
|
"update": {
|
||||||
|
"timestamp": "2025-04-22T17:43:20.146082",
|
||||||
|
"author": {
|
||||||
|
"id": 2319445,
|
||||||
|
"login": "samcro1967"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
16
meta/librechat.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"base": "svg",
|
||||||
|
"aliases": [
|
||||||
|
"libre-chat"
|
||||||
|
],
|
||||||
|
"categories": [
|
||||||
|
"Development"
|
||||||
|
],
|
||||||
|
"update": {
|
||||||
|
"timestamp": "2025-04-22T19:10:20.864971",
|
||||||
|
"author": {
|
||||||
|
"id": 1473979,
|
||||||
|
"login": "lukacat10"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
meta/loxone-full.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"base": "svg",
|
||||||
|
"aliases": [],
|
||||||
|
"categories": [
|
||||||
|
"Hardware"
|
||||||
|
],
|
||||||
|
"update": {
|
||||||
|
"timestamp": "2025-04-22T18:55:13.785687",
|
||||||
|
"author": {
|
||||||
|
"id": 63781622,
|
||||||
|
"login": "Meierschlumpf"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
meta/loxone.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"base": "svg",
|
||||||
|
"aliases": [],
|
||||||
|
"categories": [
|
||||||
|
"Hardware"
|
||||||
|
],
|
||||||
|
"update": {
|
||||||
|
"timestamp": "2025-04-22T18:51:42.311628",
|
||||||
|
"author": {
|
||||||
|
"id": 39389502,
|
||||||
|
"login": "TeHtloTs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
meta/marzban.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"base": "png",
|
||||||
|
"aliases": [],
|
||||||
|
"categories": [
|
||||||
|
"Security"
|
||||||
|
],
|
||||||
|
"update": {
|
||||||
|
"timestamp": "2025-04-22T17:17:13.363083",
|
||||||
|
"author": {
|
||||||
|
"id": 27157792,
|
||||||
|
"login": "nazarukroman"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
meta/noisedash.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"base": "svg",
|
||||||
|
"aliases": [],
|
||||||
|
"categories": [
|
||||||
|
"Media"
|
||||||
|
],
|
||||||
|
"update": {
|
||||||
|
"timestamp": "2025-04-22T17:48:13.427885",
|
||||||
|
"author": {
|
||||||
|
"id": 2319445,
|
||||||
|
"login": "samcro1967"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
meta/pangolin.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"base": "svg",
|
||||||
|
"aliases": [
|
||||||
|
"pangolin",
|
||||||
|
"pangolin-newt"
|
||||||
|
],
|
||||||
|
"categories": [
|
||||||
|
"Cloud"
|
||||||
|
],
|
||||||
|
"update": {
|
||||||
|
"timestamp": "2025-04-22T18:57:05.718417",
|
||||||
|
"author": {
|
||||||
|
"id": 7034912,
|
||||||
|
"login": "leodr99"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
meta/pretix.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"base": "svg",
|
||||||
|
"aliases": [],
|
||||||
|
"categories": [
|
||||||
|
"E-Commerce"
|
||||||
|
],
|
||||||
|
"update": {
|
||||||
|
"timestamp": "2025-04-22T18:58:38.751308",
|
||||||
|
"author": {
|
||||||
|
"id": 1832823,
|
||||||
|
"login": "alemairebe"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
meta/recyclarr.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"base": "svg",
|
||||||
|
"aliases": [],
|
||||||
|
"categories": [],
|
||||||
|
"update": {
|
||||||
|
"timestamp": "2025-04-22T19:06:08.669890",
|
||||||
|
"author": {
|
||||||
|
"id": 100090983,
|
||||||
|
"login": "Dan-Ev"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
meta/release-argus.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"base": "svg",
|
||||||
|
"aliases": [],
|
||||||
|
"categories": [],
|
||||||
|
"update": {
|
||||||
|
"timestamp": "2025-04-22T17:25:54.525668",
|
||||||
|
"author": {
|
||||||
|
"id": 2319445,
|
||||||
|
"login": "samcro1967"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
meta/series-troxide.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"base": "svg",
|
||||||
|
"aliases": [],
|
||||||
|
"categories": [
|
||||||
|
"Media"
|
||||||
|
],
|
||||||
|
"update": {
|
||||||
|
"timestamp": "2025-04-22T18:25:38.709896",
|
||||||
|
"author": {
|
||||||
|
"id": 2319445,
|
||||||
|
"login": "samcro1967"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
meta/stream-harvestarr.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"base": "png",
|
||||||
|
"aliases": [],
|
||||||
|
"categories": [
|
||||||
|
"Media"
|
||||||
|
],
|
||||||
|
"update": {
|
||||||
|
"timestamp": "2025-04-22T18:28:27.157439",
|
||||||
|
"author": {
|
||||||
|
"id": 2319445,
|
||||||
|
"login": "samcro1967"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"base": "svg",
|
"base": "svg",
|
||||||
"aliases": [
|
"aliases": [
|
||||||
"vodacom"
|
"vodafone"
|
||||||
],
|
],
|
||||||
"categories": [],
|
"categories": [],
|
||||||
"update": {
|
"update": {
|
||||||
|
|||||||
14
meta/watchlistarr.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"base": "png",
|
||||||
|
"aliases": [],
|
||||||
|
"categories": [
|
||||||
|
"Media"
|
||||||
|
],
|
||||||
|
"update": {
|
||||||
|
"timestamp": "2025-04-22T18:31:18.100212",
|
||||||
|
"author": {
|
||||||
|
"id": 2319445,
|
||||||
|
"login": "samcro1967"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
267
metadata.json
@@ -466,6 +466,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"loxone": {
|
||||||
|
"base": "svg",
|
||||||
|
"aliases": [],
|
||||||
|
"categories": [
|
||||||
|
"Hardware"
|
||||||
|
],
|
||||||
|
"update": {
|
||||||
|
"timestamp": "2025-04-22T18:51:42.311628",
|
||||||
|
"author": {
|
||||||
|
"id": 39389502,
|
||||||
|
"login": "TeHtloTs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"nginx": {
|
"nginx": {
|
||||||
"base": "svg",
|
"base": "svg",
|
||||||
"aliases": [
|
"aliases": [
|
||||||
@@ -3988,6 +4002,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"image-maid": {
|
||||||
|
"base": "png",
|
||||||
|
"aliases": [],
|
||||||
|
"categories": [
|
||||||
|
"Media"
|
||||||
|
],
|
||||||
|
"update": {
|
||||||
|
"timestamp": "2025-04-22T18:22:01.834809",
|
||||||
|
"author": {
|
||||||
|
"id": 2319445,
|
||||||
|
"login": "samcro1967"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"infisical": {
|
"infisical": {
|
||||||
"base": "svg",
|
"base": "svg",
|
||||||
"aliases": [],
|
"aliases": [],
|
||||||
@@ -13906,6 +13934,20 @@
|
|||||||
"light": "libreddit-light"
|
"light": "libreddit-light"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"karaoke-eternal": {
|
||||||
|
"base": "png",
|
||||||
|
"aliases": [],
|
||||||
|
"categories": [
|
||||||
|
"Media"
|
||||||
|
],
|
||||||
|
"update": {
|
||||||
|
"timestamp": "2025-04-22T17:43:20.146082",
|
||||||
|
"author": {
|
||||||
|
"id": 2319445,
|
||||||
|
"login": "samcro1967"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"google-compute-engine": {
|
"google-compute-engine": {
|
||||||
"base": "svg",
|
"base": "svg",
|
||||||
"aliases": [],
|
"aliases": [],
|
||||||
@@ -14801,6 +14843,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"recyclarr": {
|
||||||
|
"base": "svg",
|
||||||
|
"aliases": [],
|
||||||
|
"categories": [],
|
||||||
|
"update": {
|
||||||
|
"timestamp": "2025-04-22T19:06:08.669890",
|
||||||
|
"author": {
|
||||||
|
"id": 100090983,
|
||||||
|
"login": "Dan-Ev"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"synology-surveillance-station": {
|
"synology-surveillance-station": {
|
||||||
"base": "png",
|
"base": "png",
|
||||||
"aliases": [],
|
"aliases": [],
|
||||||
@@ -15469,6 +15523,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"loxone-full": {
|
||||||
|
"base": "svg",
|
||||||
|
"aliases": [],
|
||||||
|
"categories": [
|
||||||
|
"Hardware"
|
||||||
|
],
|
||||||
|
"update": {
|
||||||
|
"timestamp": "2025-04-22T18:55:13.785687",
|
||||||
|
"author": {
|
||||||
|
"id": 63781622,
|
||||||
|
"login": "Meierschlumpf"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"microsoft-access": {
|
"microsoft-access": {
|
||||||
"base": "svg",
|
"base": "svg",
|
||||||
"aliases": [],
|
"aliases": [],
|
||||||
@@ -16213,6 +16281,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"calibre-web-automated-book-downloader": {
|
||||||
|
"base": "png",
|
||||||
|
"aliases": [],
|
||||||
|
"categories": [
|
||||||
|
"Media"
|
||||||
|
],
|
||||||
|
"update": {
|
||||||
|
"timestamp": "2025-04-22T17:31:36.311538",
|
||||||
|
"author": {
|
||||||
|
"id": 2319445,
|
||||||
|
"login": "samcro1967"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"rstudio": {
|
"rstudio": {
|
||||||
"base": "svg",
|
"base": "svg",
|
||||||
"aliases": [],
|
"aliases": [],
|
||||||
@@ -18715,6 +18797,20 @@
|
|||||||
"light": "5etools"
|
"light": "5etools"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"buildium": {
|
||||||
|
"base": "svg",
|
||||||
|
"aliases": [],
|
||||||
|
"categories": [
|
||||||
|
"Finance"
|
||||||
|
],
|
||||||
|
"update": {
|
||||||
|
"timestamp": "2025-04-22T18:36:00.262663",
|
||||||
|
"author": {
|
||||||
|
"id": 41155244,
|
||||||
|
"login": "giovannicalabro"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"element": {
|
"element": {
|
||||||
"base": "svg",
|
"base": "svg",
|
||||||
"aliases": [],
|
"aliases": [],
|
||||||
@@ -18739,6 +18835,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"pretix": {
|
||||||
|
"base": "svg",
|
||||||
|
"aliases": [],
|
||||||
|
"categories": [
|
||||||
|
"E-Commerce"
|
||||||
|
],
|
||||||
|
"update": {
|
||||||
|
"timestamp": "2025-04-22T18:58:38.751308",
|
||||||
|
"author": {
|
||||||
|
"id": 1832823,
|
||||||
|
"login": "alemairebe"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"azure-dns": {
|
"azure-dns": {
|
||||||
"base": "svg",
|
"base": "svg",
|
||||||
"aliases": [],
|
"aliases": [],
|
||||||
@@ -19219,6 +19329,23 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"pangolin": {
|
||||||
|
"base": "svg",
|
||||||
|
"aliases": [
|
||||||
|
"pangolin",
|
||||||
|
"pangolin-newt"
|
||||||
|
],
|
||||||
|
"categories": [
|
||||||
|
"Cloud"
|
||||||
|
],
|
||||||
|
"update": {
|
||||||
|
"timestamp": "2025-04-22T18:57:05.718417",
|
||||||
|
"author": {
|
||||||
|
"id": 7034912,
|
||||||
|
"login": "leodr99"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"plume": {
|
"plume": {
|
||||||
"base": "svg",
|
"base": "svg",
|
||||||
"aliases": [],
|
"aliases": [],
|
||||||
@@ -19477,6 +19604,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"watchlistarr": {
|
||||||
|
"base": "png",
|
||||||
|
"aliases": [],
|
||||||
|
"categories": [
|
||||||
|
"Media"
|
||||||
|
],
|
||||||
|
"update": {
|
||||||
|
"timestamp": "2025-04-22T18:31:18.100212",
|
||||||
|
"author": {
|
||||||
|
"id": 2319445,
|
||||||
|
"login": "samcro1967"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"xbrowsersync": {
|
"xbrowsersync": {
|
||||||
"base": "svg",
|
"base": "svg",
|
||||||
"aliases": [],
|
"aliases": [],
|
||||||
@@ -19868,6 +20009,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"series-troxide": {
|
||||||
|
"base": "svg",
|
||||||
|
"aliases": [],
|
||||||
|
"categories": [
|
||||||
|
"Media"
|
||||||
|
],
|
||||||
|
"update": {
|
||||||
|
"timestamp": "2025-04-22T18:25:38.709896",
|
||||||
|
"author": {
|
||||||
|
"id": 2319445,
|
||||||
|
"login": "samcro1967"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"reddit": {
|
"reddit": {
|
||||||
"base": "svg",
|
"base": "svg",
|
||||||
"aliases": [],
|
"aliases": [],
|
||||||
@@ -20754,6 +20909,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"stream-harvestarr": {
|
||||||
|
"base": "png",
|
||||||
|
"aliases": [],
|
||||||
|
"categories": [
|
||||||
|
"Media"
|
||||||
|
],
|
||||||
|
"update": {
|
||||||
|
"timestamp": "2025-04-22T18:28:27.157439",
|
||||||
|
"author": {
|
||||||
|
"id": 2319445,
|
||||||
|
"login": "samcro1967"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"sogo": {
|
"sogo": {
|
||||||
"base": "svg",
|
"base": "svg",
|
||||||
"aliases": [],
|
"aliases": [],
|
||||||
@@ -20909,7 +21078,7 @@
|
|||||||
"vodafone": {
|
"vodafone": {
|
||||||
"base": "svg",
|
"base": "svg",
|
||||||
"aliases": [
|
"aliases": [
|
||||||
"vodacom"
|
"vodafone"
|
||||||
],
|
],
|
||||||
"categories": [],
|
"categories": [],
|
||||||
"update": {
|
"update": {
|
||||||
@@ -22110,6 +22279,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"doplarr": {
|
||||||
|
"base": "svg",
|
||||||
|
"aliases": [],
|
||||||
|
"categories": [
|
||||||
|
"Media"
|
||||||
|
],
|
||||||
|
"update": {
|
||||||
|
"timestamp": "2025-04-22T17:34:50.612136",
|
||||||
|
"author": {
|
||||||
|
"id": 2319445,
|
||||||
|
"login": "samcro1967"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"nextcloud-ncdownloader": {
|
"nextcloud-ncdownloader": {
|
||||||
"base": "svg",
|
"base": "svg",
|
||||||
"aliases": [],
|
"aliases": [],
|
||||||
@@ -22434,6 +22617,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"librechat": {
|
||||||
|
"base": "svg",
|
||||||
|
"aliases": [
|
||||||
|
"libre-chat"
|
||||||
|
],
|
||||||
|
"categories": [
|
||||||
|
"Development"
|
||||||
|
],
|
||||||
|
"update": {
|
||||||
|
"timestamp": "2025-04-22T19:10:20.864971",
|
||||||
|
"author": {
|
||||||
|
"id": 1473979,
|
||||||
|
"login": "lukacat10"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"huawei": {
|
"huawei": {
|
||||||
"base": "svg",
|
"base": "svg",
|
||||||
"aliases": [],
|
"aliases": [],
|
||||||
@@ -24968,6 +25167,20 @@
|
|||||||
"light": "javascript-light"
|
"light": "javascript-light"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"marzban": {
|
||||||
|
"base": "png",
|
||||||
|
"aliases": [],
|
||||||
|
"categories": [
|
||||||
|
"Security"
|
||||||
|
],
|
||||||
|
"update": {
|
||||||
|
"timestamp": "2025-04-22T17:17:13.363083",
|
||||||
|
"author": {
|
||||||
|
"id": 27157792,
|
||||||
|
"login": "nazarukroman"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"oracle": {
|
"oracle": {
|
||||||
"base": "svg",
|
"base": "svg",
|
||||||
"aliases": [],
|
"aliases": [],
|
||||||
@@ -25072,6 +25285,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"entergy": {
|
||||||
|
"base": "svg",
|
||||||
|
"aliases": [],
|
||||||
|
"categories": [
|
||||||
|
"Finance"
|
||||||
|
],
|
||||||
|
"update": {
|
||||||
|
"timestamp": "2025-04-22T18:33:50.980132",
|
||||||
|
"author": {
|
||||||
|
"id": 41155244,
|
||||||
|
"login": "giovannicalabro"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"graylog": {
|
"graylog": {
|
||||||
"base": "svg",
|
"base": "svg",
|
||||||
"aliases": [],
|
"aliases": [],
|
||||||
@@ -25638,6 +25865,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"release-argus": {
|
||||||
|
"base": "svg",
|
||||||
|
"aliases": [],
|
||||||
|
"categories": [],
|
||||||
|
"update": {
|
||||||
|
"timestamp": "2025-04-22T17:25:54.525668",
|
||||||
|
"author": {
|
||||||
|
"id": 2319445,
|
||||||
|
"login": "samcro1967"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"synology-calendar": {
|
"synology-calendar": {
|
||||||
"base": "png",
|
"base": "png",
|
||||||
"aliases": [],
|
"aliases": [],
|
||||||
@@ -26490,6 +26729,20 @@
|
|||||||
"light": "windows-95-light"
|
"light": "windows-95-light"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"noisedash": {
|
||||||
|
"base": "svg",
|
||||||
|
"aliases": [],
|
||||||
|
"categories": [
|
||||||
|
"Media"
|
||||||
|
],
|
||||||
|
"update": {
|
||||||
|
"timestamp": "2025-04-22T17:48:13.427885",
|
||||||
|
"author": {
|
||||||
|
"id": 2319445,
|
||||||
|
"login": "samcro1967"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"pgbackweb": {
|
"pgbackweb": {
|
||||||
"base": "svg",
|
"base": "svg",
|
||||||
"aliases": [],
|
"aliases": [],
|
||||||
@@ -26518,6 +26771,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"gone-man-switch": {
|
||||||
|
"base": "png",
|
||||||
|
"aliases": [],
|
||||||
|
"categories": [],
|
||||||
|
"update": {
|
||||||
|
"timestamp": "2025-04-22T17:37:06.429465",
|
||||||
|
"author": {
|
||||||
|
"id": 2319445,
|
||||||
|
"login": "samcro1967"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"gentoo-linux": {
|
"gentoo-linux": {
|
||||||
"base": "svg",
|
"base": "svg",
|
||||||
"aliases": [],
|
"aliases": [],
|
||||||
|
|||||||
BIN
png/buildium.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
png/calibre-web-automated-book-downloader.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
png/doplarr.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
png/entergy.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
png/gone-man-switch.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
png/image-maid.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
png/karaoke-eternal.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
png/librechat.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
png/loxone-full.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
png/loxone.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
png/marzban.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
png/noisedash.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
png/pangolin.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
png/pretix.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
png/recyclarr.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
png/release-argus.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
png/series-troxide.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
png/stream-harvestarr.png
Normal file
|
After Width: | Height: | Size: 169 KiB |
BIN
png/watchlistarr.png
Normal file
|
After Width: | Height: | Size: 199 KiB |
21
svg/buildium.svg
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<svg xmlns:x="ns_extend;" xmlns:i="ns_ai;" xmlns:graph="ns_graphs;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Layer_1" x="0px" y="0px" style="enable-background:new 0 0 100 100;" xml:space="preserve" viewBox="0 0 100 100">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:#70B37F;}
|
||||||
|
</style>
|
||||||
|
<metadata>
|
||||||
|
<sfw xmlns="ns_sfw;">
|
||||||
|
<slices>
|
||||||
|
</slices>
|
||||||
|
<sliceSourceBounds bottomLeftOrigin="true" height="100" width="100" x="-125.6" y="67.8">
|
||||||
|
</sliceSourceBounds>
|
||||||
|
</sfw>
|
||||||
|
</metadata>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M0,0v100h100V0H0z M54.4,49v34l-16.6-5c-1.1-0.3-2-1.6-2-2.7V49h-7l20.8-26.2c0.7-0.9,2.3-1.8,3.4-2.1 l18.2-4.4L45.4,49H54.4z">
|
||||||
|
</path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 743 B |
21
svg/doplarr.svg
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<svg xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" version="1.1" id="svg5" inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20, custom)" sodipodi:docname="logo.svg" inkscape:export-filename="/home/kiran/Projects/Clojure/Doplarr/logos/logo.png" inkscape:export-xdpi="300" inkscape:export-ydpi="300" viewBox="0 0 100 100">
|
||||||
|
<sodipodi:namedview id="namedview7" pagecolor="#ffffff" bordercolor="#111111" borderopacity="1" inkscape:pageshadow="0" inkscape:pageopacity="0" inkscape:pagecheckerboard="true" inkscape:document-units="mm" showgrid="false" height="100mm" scale-x="1" inkscape:zoom="0.97213405" inkscape:cx="261.79517" inkscape:cy="523.07601" inkscape:window-width="1758" inkscape:window-height="1350" inkscape:window-x="3338" inkscape:window-y="66" inkscape:window-maximized="1" inkscape:current-layer="text1822"/>
|
||||||
|
<defs id="defs2">
|
||||||
|
<inkscape:path-effect effect="mirror_symmetry" start_point="28.682452,9.1988852" end_point="28.682452,90.801115" center_point="28.682452,50" id="path-effect20182" is_visible="true" lpeversion="1.1" mode="vertical" discard_orig_path="false" fuse_paths="false" oposite_fuse="false" split_items="false" split_open="false"/>
|
||||||
|
</defs>
|
||||||
|
<g inkscape:label="Layer 1" inkscape:groupmode="layer" id="layer1">
|
||||||
|
<circle style="fill:#7289da;fill-opacity:1;stroke:none;stroke-width:0.262264;stroke-linecap:square;stroke-dasharray:1.5736, 0.262264" id="path866" cx="50" cy="50" r="50"/>
|
||||||
|
<g aria-label="D" id="text1956" style="font-size:83.3059px;line-height:1.25;font-family:Impact;-inkscape-font-specification:'Impact, Normal';fill:#ffffff;stroke-width:2.08265" transform="matrix(0.92504678,0,0,0.92504678,71.71317,13.992208)">
|
||||||
|
<path d="m -43.302405,5.9975786 h 12.813163 c 8.270931,0 13.857199,0.3796489 16.758804,1.1389467 2.928723,0.7592987 5.1523828,2.0067177 6.6709798,3.7422577 1.518597,1.735539 2.46772,3.674462 2.847369,5.816769 0.379649,2.115189 0.569474,6.291331 0.569474,12.528426 v 23.063695 c 0,5.911681 -0.284737,9.870881 -0.854211,11.877599 -0.542356,1.9796 -1.505038,3.538874 -2.888046,4.677821 -1.383008,1.11183 -3.0914288,1.898246 -5.1252648,2.359249 -2.033835,0.433885 -5.098147,0.650828 -9.192936,0.650828 h -21.599332 z" id="path23416" sodipodi:nodetypes="cscscsscccscc"/>
|
||||||
|
</g>
|
||||||
|
<path id="path21610" style="fill:#fffffe;stroke:#000000;stroke-width:0.264583" d="M 50.0178,51.760883 A 0.01779476,0.01779476 0 0 1 50,51.778673 0.01779476,0.01779476 0 0 1 49.9822,51.760883 0.01779476,0.01779476 0 0 1 50,51.743093 a 0.01779476,0.01779476 0 0 1 0.0178,0.01779 z"/>
|
||||||
|
<path id="path21612" style="fill:#fffffe;stroke:#000000;stroke-width:0.264583" d="M 50.0178,51.760883 A 0.01779476,0.01779476 0 0 1 50,51.778673 0.01779476,0.01779476 0 0 1 49.9822,51.760883 0.01779476,0.01779476 0 0 1 50,51.743093 a 0.01779476,0.01779476 0 0 1 0.0178,0.01779 z"/>
|
||||||
|
<g aria-label="λ" id="text24934" style="font-size:47.894px;line-height:1.25;font-family:Impact;-inkscape-font-specification:Impact;fill:#7289da;stroke-width:1.19735">
|
||||||
|
<path d="m 38.342206,68.802139 5.168249,-28.50722 q 0.3274,-1.824088 0.3274,-3.016761 0,-1.473302 -1.941017,-1.473302 h -1.26283 v -4.88762 h 5.752893 q 4.770692,0 6.852023,1.052359 2.081331,1.052358 2.806289,3.507861 0.748344,2.432117 1.356373,8.325324 l 1.870859,17.773165 q 0.140315,1.520073 0.420944,1.870859 0.304014,0.327401 0.724958,0.327401 0.350786,0 1.239444,-0.04677 v 5.027935 q -2.736132,0.3274 -5.402107,0.3274 -4.560219,0 -4.934391,-5.355335 L 50.035077,45.322854 46.924773,68.802139 Z" id="path2772"/>
|
||||||
|
</g>
|
||||||
|
<g aria-label="(" id="text16135" style="font-size:89.8502px;line-height:1.25;font-family:'Times New Roman';-inkscape-font-specification:'Times New Roman, Normal';fill:#ffffff;stroke-width:2.24626" inkscape:path-effect="#path-effect20182" transform="translate(4.5800001,-0.28635574)">
|
||||||
|
<path d="m 28.682452,89.464201 v 1.62327 C 24.265987,88.864614 20.580725,86.261532 17.626666,83.278225 13.414938,79.037249 10.168398,74.035822 7.8870448,68.273944 5.6056921,62.512066 4.4650158,56.530827 4.4650158,50.330228 c 0,-9.066914 2.2374804,-17.329505 6.7124412,-24.787774 4.474961,-7.487516 10.30996,-12.83992 17.504995,-16.0572134 v 1.8426314 c -3.597518,1.988872 -6.551577,4.708946 -8.862178,8.160223 -2.310601,3.451277 -4.036239,7.82387 -5.176916,13.117778 -1.140676,5.293908 -1.711014,10.821801 -1.711014,16.583679 0,6.259095 0.482594,11.947853 1.447781,17.066273 0.760451,4.036239 1.681767,7.268155 2.763947,9.695748 1.08218,2.456841 2.529961,4.811314 4.343344,7.063419 1.842631,2.252105 4.240977,4.401841 7.195036,6.449209 z m 33.475096,0 v 1.62327 c 4.416465,-2.222857 8.101727,-4.825939 11.055786,-7.809246 4.211728,-4.240976 7.458268,-9.242403 9.739621,-15.004281 2.281353,-5.761878 3.422029,-11.743117 3.422029,-17.943716 0,-9.066914 -2.23748,-17.329505 -6.712441,-24.787774 C 75.187582,18.054938 69.352583,12.702534 62.157548,9.4852406 v 1.8426314 c 3.597518,1.988872 6.551577,4.708946 8.862178,8.160223 2.310601,3.451277 4.036239,7.82387 5.176916,13.117778 1.140676,5.293908 1.711014,10.821801 1.711014,16.583679 0,6.259095 -0.482594,11.947853 -1.447781,17.066273 -0.760451,4.036239 -1.681767,7.268155 -2.763947,9.695748 -1.08218,2.456841 -2.529961,4.811314 -4.343344,7.063419 -1.842631,2.252105 -4.240977,4.401841 -7.195036,6.449209 z" id="path20161" inkscape:original-d="m 28.682452,89.464201 v 1.62327 Q 22.057755,87.753186 17.626666,83.278225 11.309074,76.916761 7.8870448,68.273944 4.4650158,59.631127 4.4650158,50.330228 q 0,-13.600371 6.7124412,-24.787774 Q 17.889899,14.31118 28.682452,9.4852406 v 1.8426314 q -5.396277,2.983308 -8.862178,8.160223 -3.465901,5.176916 -5.176916,13.117778 -1.711014,7.940862 -1.711014,16.583679 0,9.388643 1.447781,17.066273 1.140677,6.054359 2.763947,9.695748 1.62327,3.685262 4.343344,7.063419 2.763947,3.378157 7.195036,6.449209 z"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 5.9 KiB |
14
svg/entergy.svg
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0.01 115.52 143.76">
|
||||||
|
<title>horizLogo</title>
|
||||||
|
<defs>
|
||||||
|
<linearGradient x1="76.6452875%" y1="106.733429%" x2="28.7462587%" y2="3.49216114%" id="linearGradient-1">
|
||||||
|
<stop stop-color="#8026FF" offset="25%"/>
|
||||||
|
<stop stop-color="#FF1A58" offset="75%"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="horizLogo" transform="translate(0.000000, 0.009657)" fill-rule="nonzero">
|
||||||
|
<path d="M3.22,65.9903428 C7.36,48.9903428 20.85,39.4403428 38.69,39.4403428 C56.1,39.4403428 70.25,48.8003428 74.38,65.9903428 L3.22,65.9903428 Z M105.22,38.5203428 C95.0100528,20.8238516 78.1856989,7.91046115 58.4510992,2.62326895 C38.7164996,-2.66392325 17.6896641,0.108571645 1.42108547e-14,10.3303428 L9.71,27.1503428 C18.6760705,22.5434556 28.6302113,20.1922189 38.71,20.3003428 C72.66,20.3003428 96.16,44.2403428 96.16,76.8803428 L96.16,84.5003428 L2.57,84.5003428 C5.62,103.650343 19.98,114.310343 38.69,114.310343 C52.62,114.310343 62.41,109.960343 68.69,100.600343 L91.54,100.600343 C86.8570334,112.00133 78.2280851,121.336638 67.23,126.900343 L77,143.760343 C94.6992057,133.550362 107.614545,116.724187 112.901881,96.9871749 C118.189217,77.2501625 115.414946,56.220907 105.19,38.5303428 L105.22,38.5203428 Z" id="Shape" fill="url(#linearGradient-1)"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
32
svg/librechat.svg
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="39.31 0 433.4 512.01">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="linearGradient22708">
|
||||||
|
<stop stop-color="#21facf" offset="0"/>
|
||||||
|
<stop stop-color="#0970ef" offset="1"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="linearGradient6949" x1="68.454" x2="198.59" y1="246.73" y2="96.35" gradientTransform="translate(-5.754,-56.594)" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#72004e" offset="0"/>
|
||||||
|
<stop stop-color="#0015b1" offset="1"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="linearGradient22718" x1="56.735" x2="155.2" y1="246.96" y2="58.575" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#4f00da" offset="0"/>
|
||||||
|
<stop stop-color="#e5311b" offset="1"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="linearGradient23463" x1="68.454" x2="198.59" y1="246.73" y2="96.35" gradientUnits="userSpaceOnUse" xlink:href="#linearGradient22708"/>
|
||||||
|
<linearGradient id="linearGradient903" x1="54.478" x2="192.1" y1="247.56" y2="9.8095" gradientTransform="matrix(.87923 0 0 .87923 -9.551 48.787)" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#dc180d" offset="0"/>
|
||||||
|
<stop stop-color="#f96e20" offset=".5"/>
|
||||||
|
<stop stop-color="#f4ce41" offset="1"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="linearGradient918" x1="39.468" x2="154.99" y1="204.22" y2="124.47" gradientUnits="userSpaceOnUse" xlink:href="#linearGradient22708"/>
|
||||||
|
</defs>
|
||||||
|
<g transform="matrix(2.473 0 0 2.473 -4.8978 -4.8812)">
|
||||||
|
<path transform="translate(-5.5496,-57.412)" d="m148.16 59.393c-7.7098 9.3985-19.951 42.888-20.696 49.204-0.16994 4.6737 1.3731 14.231 0.67182 15.805-0.71909 1.6134-5.117-9.4461-7.2151-6.3266-12.219 18.168-10.7 17.731-15.582 31.378-1.8357 5.1315-0.42447 21.99-1.5666 23.773-1.273 1.9866-3.962-12.31-6.8063-9.236-11.603 12.54-16.279 20.379-22.336 30.607-3.3589 5.6725-2.1817 23.33-3.506 24.674-1.3023 1.3215-3.8566-18.326-7.6437-14.309-8.5193 9.038-14.054 13.441-18.946 19.252-5.1981 6.1739-0.78251 17.584-5.0672 35.383l0.1448 0.22073c77.447-50.308 101.52-127.16 107.61-181.19-0.68051 63.93-29.41 142.78-105.33 184.65l0.1127 0.17141c20.241-2.181 22.307 10.458 44.562-4.2837 55.792-48.277 81.856-124.29 61.593-199.78z" display="none" fill="url(#linearGradient903)"/>
|
||||||
|
<path transform="translate(-5.5498,-57.412)" d="m148.16 59.393c-7.7098 9.3985-19.951 42.888-20.696 49.204-0.16994 4.6737 1.3731 14.231 0.67182 15.805-0.71909 1.6134-5.117-9.4461-7.2151-6.3266-12.219 18.168-10.7 17.731-15.582 31.378-1.8357 5.1315-0.42447 21.99-1.5666 23.773-1.273 1.9866-3.962-12.31-6.8063-9.236-11.603 12.54-16.279 20.379-22.336 30.607-3.3589 5.6725-2.1817 23.33-3.506 24.674-1.3023 1.3215-3.8566-18.326-7.6437-14.309-8.5193 9.038-14.054 13.441-18.946 19.252-5.1981 6.1739-0.78251 17.584-5.0672 35.383l0.1448 0.22073c77.447-50.308 101.52-127.16 107.61-181.19-0.68051 63.93-29.41 142.78-105.33 184.65l0.1127 0.17141c20.241-2.181 22.307 10.458 44.562-4.2837 55.792-48.277 81.856-124.29 61.593-199.78z" fill="url(#linearGradient918)"/>
|
||||||
|
<g transform="translate(0 2.0218e-5)">
|
||||||
|
<path transform="translate(-5.7543,-56.594)" d="m111.25 81.024c-48.394-1.5e-5 -87.625 39.231-87.625 87.625 0.0174 20.443 7.1818 40.236 20.253 55.954 0.2523-0.42224 0.53629-0.82423 0.85783-1.2061 4.892-5.8104 10.427-10.214 18.946-19.252 3.7871-4.0176 6.3412 15.63 7.6435 14.309 1.3243-1.3439 0.1473-19.001 3.5062-24.674 6.0563-10.228 10.733-18.067 22.336-30.607 2.8443-3.0741 5.5333 11.223 6.8063 9.2361 1.1421-1.7823-0.26941-18.641 1.5663-23.773 4.8819-13.647 3.3631-13.21 15.582-31.378 2.098-3.1195 6.496 7.9402 7.2151 6.3268 0.70126-1.5734-0.84173-11.131-0.67179-15.805 0.37161-3.1498 3.6036-13.059 7.7055-23.367-7.8432-2.2472-15.962-3.3881-24.12-3.3895zm43.142 11.356c5.5662 61.595-18.426 120.7-62.796 161.65 6.446 1.4857 13.04 2.2367 19.655 2.2386 48.394 1e-5 87.625-39.231 87.625-87.625-3.1e-4 -31.581-16.995-60.719-44.484-76.268z" display="none" fill="url(#linearGradient22718)"/>
|
||||||
|
<path transform="translate(-5.754,-56.594)" d="m111.25 81.024c-48.394-1.5e-5 -87.625 39.231-87.625 87.625 0.0174 20.443 7.1818 40.236 20.253 55.954 0.2523-0.42224 0.53629-0.82423 0.85783-1.2061 4.892-5.8104 10.427-10.214 18.946-19.252 3.7871-4.0176 6.3412 15.63 7.6435 14.309 1.3243-1.3439 0.1473-19.001 3.5062-24.674 6.0563-10.228 10.733-18.067 22.336-30.607 2.8443-3.0741 5.5333 11.223 6.8063 9.2361 1.1421-1.7823-0.26941-18.641 1.5663-23.773 4.8819-13.647 3.3631-13.21 15.582-31.378 2.098-3.1195 6.496 7.9402 7.2151 6.3268 0.70126-1.5734-0.84173-11.131-0.67179-15.805 0.37161-3.1498 3.6036-13.059 7.7055-23.367-7.8432-2.2472-15.962-3.3881-24.12-3.3895zm43.142 11.356c5.5662 61.595-18.426 120.7-62.796 161.65 6.446 1.4857 13.04 2.2367 19.655 2.2386 48.394 1e-5 87.625-39.231 87.625-87.625-3.1e-4 -31.581-16.995-60.719-44.484-76.268z" display="none" fill="url(#linearGradient23463)"/>
|
||||||
|
<path d="m105.5 24.43c-48.394-1.5e-5 -87.625 39.231-87.625 87.625 0.0174 20.443 7.1818 40.236 20.253 55.954 0.2523-0.42224 0.53629-0.82423 0.85783-1.2061 4.892-5.8104 10.427-10.214 18.946-19.252 3.7871-4.0176 6.3412 15.63 7.6435 14.309 1.3243-1.3439 0.1473-19.001 3.5062-24.674 6.0563-10.228 10.733-18.067 22.336-30.607 2.8443-3.0741 5.5333 11.223 6.8063 9.2361 1.1421-1.7823-0.26941-18.641 1.5663-23.773 4.8819-13.647 3.3631-13.21 15.582-31.378 2.098-3.1195 6.496 7.9402 7.2151 6.3268 0.70126-1.5734-0.84173-11.131-0.67179-15.805 0.37161-3.1498 3.6036-13.059 7.7055-23.367-7.8432-2.2472-15.962-3.3881-24.12-3.3895zm43.142 11.356c5.5662 61.595-18.426 120.7-62.796 161.65 6.446 1.4857 13.04 2.2367 19.655 2.2386 48.394 1e-5 87.625-39.231 87.625-87.625-3.1e-4 -31.581-16.995-60.719-44.484-76.268z" fill="url(#linearGradient6949)"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 5.6 KiB |
3
svg/loxone-full.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 100 22.09">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M91.4149 18.1044V12.2562H96.9392V8.69613H91.4149V5.93341L90.1667 4.01636H92.8082H100V0.456299H87.6289V0.471142V4.01636V18.1044V19.7067L88.9035 21.6644H100V18.1044H91.4149ZM83.4455 21.6508H83.8794V0.471191H80.4177V15.7242L72.6247 0.471191H71.489H68.8387H68.0273V21.6508H71.489V5.65867L79.6594 21.6508H80.4177H83.4455ZM63.5224 18.7948C64.4444 17.1971 64.544 15.5118 64.544 14.8618V7.21299C64.5051 3.63618 62.7241 1.86947 61.2373 1.01732C59.6341 0.0989541 57.9447 0 57.2933 0H56.0908C52.5047 0.0384399 50.7329 1.81467 49.8784 3.29784C48.958 4.89632 48.8584 6.58083 48.8584 7.23126L48.8587 14.8804C48.8973 18.4572 50.6783 20.2236 52.1651 21.0761C53.7683 21.9941 55.4573 22.093 56.1091 22.093H57.2967H57.3062C60.8942 22.0534 62.6672 20.2776 63.5224 18.7948ZM61.1099 14.8617C61.1099 16.2661 60.6111 18.6265 57.2773 18.6676H56.1095C54.6998 18.6676 52.3292 18.171 52.293 14.8617V7.23122C52.293 5.82531 52.7917 3.46069 56.1095 3.42529H57.2937C58.703 3.42529 61.0744 3.92197 61.1099 7.23122V14.8617ZM44.0577 21.6861H48.2971L42.3396 12.5366L40.2197 15.7922L44.0577 21.6861ZM42.3396 9.62064L48.2971 0.471191H44.0577L40.2197 6.36505L42.3396 9.62064ZM41.3893 11.0787L34.482 0.471191H30.2422L37.1495 11.0787L30.2422 21.6862H34.482L41.3893 11.0787ZM28.6586 18.7948C29.5806 17.1971 29.6798 15.5118 29.6798 14.8618V7.21299C29.6416 3.63618 27.8602 1.86947 26.3731 1.01732C24.7703 0.0989541 23.0809 0 22.429 0H21.227C17.6405 0.0384399 15.869 1.81467 15.0146 3.29784C14.0937 4.89632 13.9941 6.58083 13.9941 7.23126V14.8804C14.0331 18.4572 15.8141 20.2236 17.3016 21.0761C18.904 21.9941 20.5931 22.093 21.2453 22.093H22.4329H22.442C26.0308 22.0534 27.8034 20.2776 28.6586 18.7948ZM26.2443 14.8617C26.2443 16.2661 25.7462 18.6265 22.4121 18.6676H21.2443C19.8346 18.6676 17.464 18.171 17.4277 14.8617V7.23122C17.4277 5.82531 17.9261 3.46069 21.2443 3.42529H22.4281C23.8382 3.42529 26.2092 3.92197 26.2443 7.23122V14.8617ZM1.25362 21.6644H12.3717V18.1044H3.78565V0.471133H0V18.1044V19.7398L1.25362 21.6644Z" fill="#69C350"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.1 KiB |
3
svg/loxone.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 128 128">
|
||||||
|
<rect fill="#69C350" width="128" height="128"/>
|
||||||
|
<path xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" d="M91.4149 18.1044V12.2562H96.9392V8.69613H91.4149V5.93341L90.1667 4.01636H92.8082H100V0.456299H87.6289V0.471142V4.01636V18.1044V19.7067L88.9035 21.6644H100V18.1044H91.4149ZM83.4455 21.6508H83.8794V0.471191H80.4177V15.7242L72.6247 0.471191H71.489H68.8387H68.0273V21.6508H71.489V5.65867L79.6594 21.6508H80.4177H83.4455ZM63.5224 18.7948C64.4444 17.1971 64.544 15.5118 64.544 14.8618V7.21299C64.5051 3.63618 62.7241 1.86947 61.2373 1.01732C59.6341 0.0989541 57.9447 0 57.2933 0H56.0908C52.5047 0.0384399 50.7329 1.81467 49.8784 3.29784C48.958 4.89632 48.8584 6.58083 48.8584 7.23126L48.8587 14.8804C48.8973 18.4572 50.6783 20.2236 52.1651 21.0761C53.7683 21.9941 55.4573 22.093 56.1091 22.093H57.2967H57.3062C60.8942 22.0534 62.6672 20.2776 63.5224 18.7948ZM61.1099 14.8617C61.1099 16.2661 60.6111 18.6265 57.2773 18.6676H56.1095C54.6998 18.6676 52.3292 18.171 52.293 14.8617V7.23122C52.293 5.82531 52.7917 3.46069 56.1095 3.42529H57.2937C58.703 3.42529 61.0744 3.92197 61.1099 7.23122V14.8617ZM44.0577 21.6861H48.2971L42.3396 12.5366L40.2197 15.7922L44.0577 21.6861ZM42.3396 9.62064L48.2971 0.471191H44.0577L40.2197 6.36505L42.3396 9.62064ZM41.3893 11.0787L34.482 0.471191H30.2422L37.1495 11.0787L30.2422 21.6862H34.482L41.3893 11.0787ZM28.6586 18.7948C29.5806 17.1971 29.6798 15.5118 29.6798 14.8618V7.21299C29.6416 3.63618 27.8602 1.86947 26.3731 1.01732C24.7703 0.0989541 23.0809 0 22.429 0H21.227C17.6405 0.0384399 15.869 1.81467 15.0146 3.29784C14.0937 4.89632 13.9941 6.58083 13.9941 7.23126V14.8804C14.0331 18.4572 15.8141 20.2236 17.3016 21.0761C18.904 21.9941 20.5931 22.093 21.2453 22.093H22.4329H22.442C26.0308 22.0534 27.8034 20.2776 28.6586 18.7948ZM26.2443 14.8617C26.2443 16.2661 25.7462 18.6265 22.4121 18.6676H21.2443C19.8346 18.6676 17.464 18.171 17.4277 14.8617V7.23122C17.4277 5.82531 17.9261 3.46069 21.2443 3.42529H22.4281C23.8382 3.42529 26.2092 3.92197 26.2443 7.23122V14.8617ZM1.25362 21.6644H12.3717V18.1044H3.78565V0.471133H0V18.1044V19.7398L1.25362 21.6644Z" fill="#ffffff" style="transform: translate(14px, 53px);"/> </svg>
|
||||||
|
After Width: | Height: | Size: 2.2 KiB |
1
svg/noisedash.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 87.5 100"><defs><style>.cls-1{fill:#1697f6;}.cls-2{fill:#7bc6ff;}.cls-3{fill:#1867c0;}.cls-4{fill:#aeddff;}</style></defs><title>Artboard 46</title><polyline class="cls-1" points="43.75 0 23.31 0 43.75 48.32"/><polygon class="cls-2" points="43.75 62.5 43.75 100 0 14.58 22.92 14.58 43.75 62.5"/><polyline class="cls-3" points="43.75 0 64.19 0 43.75 48.32"/><polygon class="cls-4" points="64.58 14.58 87.5 14.58 43.75 100 43.75 62.5 64.58 14.58"/></svg>
|
||||||
|
After Width: | Height: | Size: 539 B |
1
svg/pangolin.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" version="1.1" x="0px" y="0px" enable-background="new 0 0 419.528 419.528" xml:space="preserve" id="svg52" sodipodi:docname="pangolin_orange.svg" inkscape:version="1.2.2 (b0a8486541, 2022-12-01)" viewBox="18.18 30 360.87 338.3"><defs id="defs56"/><sodipodi:namedview id="namedview54" pagecolor="#ffffff" bordercolor="#666666" borderopacity="1.0" inkscape:showpageshadow="2" inkscape:pageopacity="0.0" inkscape:pagecheckerboard="0" inkscape:deskcolor="#d1d1d1" showgrid="false" inkscape:zoom="1.9583914" inkscape:cx="127.40048" inkscape:cy="262.71561" inkscape:window-width="1436" inkscape:window-height="1236" inkscape:window-x="2208" inkscape:window-y="511" inkscape:window-maximized="0" inkscape:current-layer="svg52"/><path d="m 62.232921,184.91974 c 0,2.431 -1.97,4.402 -4.399,4.402 -2.429,0 -4.399,-1.972 -4.399,-4.402 0,-2.429 1.97,-4.399 4.399,-4.399 2.429,-10e-4 4.399,1.97 4.399,4.399 z m 58.993999,-4.821 c -25.943999,-2.826 -38.978999,7.453 -71.181999,31.357 -27.572,20.467 -32.767,4.381 -31.748,-2.614 1.499,-10.282 25.222,-58.573 48.079,-88.461 28.273,7.34 49.869999,30.727 54.850999,59.718 z m -55.915999,4.821 c 0,-4.131 -3.349,-7.478 -7.478,-7.478 -4.129,0 -7.478,3.347 -7.478,7.478 0,4.131 3.349,7.481 7.478,7.481 4.13,0 7.478,-3.35 7.478,-7.481 z m -15.032,48.424 -0.234,14.041 20.413,22.687 -9.818,7.353 33.306,27.492 -11.759,8.124 42.631999,19.939 -10.825,9.747 48.291,8.078 -7.526,10.307 48.758,-4.531 -3.997,11.725 53.916,-18.153 -2.76,13.357 48.077,-34.345 1.479,13.562 34.087,-48.576 7.478,14.206 15.187,-58.89 10.391,8.533 -2.14,-57.884 13.814,5.13 -21.082,-51.204 13.404,0.048 -33.696,-42.131 15.312,-1.366 -47.026,-32.831002 14.255,-8.399 -54.817,-14.682 9.257,-11.695 -49.625,0.352 0.6,-13.337 -38.537,14.084 -1.597,-12.689 -29.984,21.429 -6.446,-10.852 -22.59,26.504 -7.021,-9.572 -18.923,30.294 -9.595999,-8.744 -16.754,30.138002 c 31.509999,10.197 54.979999,37.951 59.126999,71.547 0.404,0.087 -22.37,31.257 10.955,57.85 -0.576,-2.985 -6.113,-53.902 47.496,-57.61 26.668,-1.844 48.4,21.666 48.4,48.399 0,8.184 -2.05,15.883 -5.636,22.64 -15.927,29.611 -64.858,30.755 -80.429,30.596 -45.154,-0.459 -104.051999,-51.521 -104.051999,-51.521 z" id="path46" style="fill:#f97315;fill-opacity:1"/></svg>
|
||||||
|
After Width: | Height: | Size: 2.4 KiB |
1
svg/pretix.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" version="1.1" viewBox="20.41 20.41 108.77 108.77"><g transform="matrix(.9375 0 0 .9375 20.413 20.413)"><path d="M45.52 48.98c-.74 0-1.28.13-1.75.27v17.43c.4.13.94.27 1.61.27 3.63 0 5.18-3.03 5.18-8.95s-1.35-9.02-5.05-9.02z" fill="#3b1c4a"/><path d="M114.72 36.13c.74-.07 1.28-.61 1.28-1.35V1.35c0-.74-.61-1.35-1.35-1.35H75.99v5.38c0 1.15-.94 2.09-2.09 2.09s-2.09-.94-2.09-2.09V0H1.35C.61 0 0 .61 0 1.35v33.44c0 .74.54 1.28 1.28 1.35 11.51.67 20.66 10.23 20.66 21.94s-9.15 21.2-20.66 21.8c-.74.07-1.28.61-1.28 1.35v33.44c0 .74.61 1.35 1.35 1.35h70.48v-5.38c0-1.19.9-2.09 2.09-2.09s2.09.94 2.09 2.09v5.38h38.66c.74 0 1.35-.61 1.35-1.35V81.23c0-.74-.54-1.28-1.28-1.35-11.51-.67-20.66-10.16-20.66-21.87s9.15-21.26 20.66-21.87zM47.87 72.87c-1.82 0-3.36-.2-4.1-.4v11.64H33.54V45.22C36.3 43.94 40 43 45.58 43c8.95 0 15.07 4.78 15.07 14.94 0 9.15-5.32 14.94-12.78 14.94zm28.12 25.3c0 1.15-.94 2.09-2.09 2.09s-2.09-.94-2.09-2.09V87.4c0-1.15.94-2.09 2.09-2.09s2.09.97 2.09 2.09zm0-23.28c0 1.11-.97 2.09-2.09 2.09-1.12 0-2.09-.97-2.09-2.09V64.12c0-1.19.9-2.09 2.09-2.09s2.09.94 2.09 2.09zm0-23.15c0 1.15-.94 2.09-2.09 2.09s-2.09-.94-2.09-2.09V40.97c0-1.15.94-2.09 2.09-2.09s2.09.97 2.09 2.09zm0-23.28c0 1.11-.97 2.09-2.09 2.09-1.12 0-2.09-.97-2.09-2.09V17.69c0-1.19.9-2.09 2.09-2.09s2.09.94 2.09 2.09z" fill="#3b1c4a"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
17
svg/recyclarr.svg
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Layer_1" x="0px" y="0px" style="enable-background:new 0 0 1920 1080;" xml:space="preserve" viewBox="762.04 306.21 401.81 459.59">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:#FEFEFE;}
|
||||||
|
.st1{fill:#6DB653;}
|
||||||
|
</style>
|
||||||
|
<g>
|
||||||
|
|
||||||
|
<path class="st1" d="M962.86,420.95c52.13,0,104.27,0,156.4,0c10.34,0,14.29,3.85,13.74,14.34c-1.15,21.76-2.65,43.5-3.95,65.25 c-2.45,41.02-4.84,82.04-7.29,123.06c-1.65,27.56-3.17,55.14-5.18,82.67c-0.66,9.09-0.13,18.37-2.71,27.25 c-4.62,15.89-15.62,25.12-31.1,30.06c-7.01,2.24-14.01,2.12-21.1,2.12c-69.12,0.02-138.25,0.31-207.37-0.18 c-19.52-0.14-38.4-11.46-43.69-32.65c-2.58-10.33-2.1-21.14-2.74-31.77c-2.07-34.04-4.33-68.06-6.44-102.09 c-1.25-20.08-2.28-40.18-3.54-60.27c-2.11-33.7-4.33-67.38-6.48-101.08c-0.3-4.66-0.56-9.23,3.16-13.08c2.82-2.91,6-3.7,9.87-3.69 C857.26,420.98,910.06,420.95,962.86,420.95z"/>
|
||||||
|
<path class="st1" d="M962.25,392.98c-61.96,0-123.92-0.01-185.88,0.02c-3.37,0-6.8,0.07-9.6-1.97c-2.88-2.1-4.81-4.8-4.73-8.82 c0.2-8.82-0.02-17.65,0.04-26.48c0.09-11.44,9.18-20.45,20.72-20.46c31.81-0.04,63.63-0.05,95.44,0.05 c3.55,0.01,5.82-0.65,7.19-4.44c1.56-4.32,4.04-8.32,6.16-12.44c4.09-7.94,10.79-12.08,19.54-12.12 c34.14-0.16,68.29-0.13,102.43-0.02c8.63,0.03,15.23,4.16,19.45,11.67c2.43,4.32,4.76,8.77,6.42,13.41 c1.24,3.47,3.27,3.94,6.41,3.93c30.98-0.08,61.96-0.05,92.94-0.05c12.57,0,20.61,5.4,24.67,16.93c0.68,1.94,0.29,4.28,0.3,6.44 c0.03,6.66,0.01,13.32,0.01,19.99c0,9.54-4.85,14.36-14.63,14.36C1086.83,392.99,1024.54,392.98,962.25,392.98z"/>
|
||||||
|
<path class="st0" d="M1043.97,491.95c-1.3,4.31-3.67,7.25-5.52,10.43c-6.34,10.87-12.72,21.72-19.06,32.59 c-1.48,2.54-3.04,4.4-6.56,4.2c-15.92-0.92-31.86-1.58-47.79-2.37c-0.23-0.01-0.45-0.39-0.95-0.85c3.67-3.41,8.24-5.36,12.51-7.6 c4.05-2.13,4.89-4.04,2.95-8.5c-7.91-18.24-17.55-35.23-32.58-48.7c1.6-1.76,3.36-1.07,4.91-1.07c15.8-0.03,31.61,0.03,47.41,0.06 c6.05,0.01,10.46,2.92,13.53,7.91c4.08,6.63,8.13,13.28,11.89,20.09c1.51,2.74,2.6,3.09,5.33,1.4 C1034.32,496.89,1038.48,493.93,1043.97,491.95z"/>
|
||||||
|
<path class="st0" d="M875.62,638.11c-8.67-16.31-16.96-31.76-25.1-47.28c-2.8-5.35-1.77-10.7,1.14-15.7 c3.76-6.45,7.51-12.91,11.63-19.14c1.81-2.73,1.19-3.99-1.31-5.44c-4.36-2.53-8.6-5.25-12.7-7.78c11.2,0,22.22-0.06,33.24,0.02 c5.83,0.04,11.65,0.44,17.48,0.44c2.31,0,3.81,0.73,4.77,2.77c7,14.8,14.01,29.59,20.98,44.39c0.11,0.22-0.14,0.61-0.37,1.54 c-4.72-2.87-9.54-5.3-13.77-8.52c-3.14-2.39-4.76-1.51-6.87,1.04c-12.62,15.24-22.96,31.71-27.95,51.12 C876.63,636.18,876.26,636.73,875.62,638.11z"/>
|
||||||
|
<path class="st0" d="M997.95,687.53c-4.01-3.79-5.57-7.62-7.73-11c-6.86-10.76-13.55-21.63-20.36-32.42 c-1.21-1.92-2.02-3.53-0.62-5.89c7.95-13.38,15.75-26.85,23.6-40.28c0.5-0.85,1.02-1.71,3.26-1.68c0,5.34-0.01,10.73,0,16.11 c0.01,2.02-0.16,4.13,2.77,4.36c17.63,1.37,35.19,1.77,52.47-2.99c0.64-0.18,1.27-0.41,1.92-0.55c2.48-0.5,5.28-3.59,7.14-1.47 c1.38,1.57-1.6,4.28-2.7,6.43c-6.48,12.66-13.09,25.26-19.63,37.9c-3.17,6.13-8.17,9.45-15.05,9.85 c-7.13,0.41-14.27,0.96-21.41,1.04c-3.55,0.04-4.77,1.33-4.52,4.72C997.47,676.54,997.64,681.43,997.95,687.53z"/>
|
||||||
|
<path class="st0" d="M928.28,468.79c5.76-0.28,10.77,1.86,15.26,5.15c5.81,4.25,10.58,9.6,15.26,15.04 c1.54,1.8,1.66,3.17,0.36,5.28c-9.99,16.24-19.92,32.52-29.65,48.91c-2.01,3.39-3.45,4.42-7.25,2.06 c-11.41-7.09-23.05-13.8-34.71-20.47c-3.44-1.97-4.2-3.57-1.79-7.24c8.46-12.9,16.66-25.98,24.67-39.17 C914.61,471.49,920.49,468.55,928.28,468.79z"/>
|
||||||
|
<path class="st0" d="M1074.28,583.98c-0.68,11.35-6.36,18.12-14.99,22.52c-6.37,3.25-13.48,4.6-20.34,6.54 c-1.8,0.51-3.04-0.08-4.06-1.86c-9.83-17.28-20.48-34.07-31.18-50.82c-1.66-2.61-0.71-3.79,1.47-5.14 c12.4-7.67,24.8-15.35,37.1-23.19c2.33-1.49,3.4-0.39,4.38,1.27c8.64,14.69,17.29,29.37,25.75,44.17 C1073.69,579.71,1074.51,582.38,1074.28,583.98z"/>
|
||||||
|
<path class="st0" d="M953.01,630.07c-0.55,11.62-1.27,23.23-1.57,34.85c-0.1,3.74-1.61,4.48-4.85,4.25 c-16.41-1.17-32.83-2.14-49.24-3.37c-8.47-0.64-16.49-9.9-17.16-19.99c-0.67-10.23,3.25-19.52,6.78-28.84 c0.77-2.03,2.57-1.65,4.26-1.48c18.2,1.86,36.47,2.33,54.73,3.06c7.28,0.29,7.28,0.16,7.16,7.52c-0.02,1.33,0,2.66,0,3.99 C953.09,630.07,953.05,630.07,953.01,630.07z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.2 KiB |
16
svg/release-argus.svg
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" version="1.1" id="svg1" viewBox="4.5 0 58.74 67.73">
|
||||||
|
<defs id="defs1">
|
||||||
|
<linearGradient id="linearGradient4">
|
||||||
|
<stop style="stop-color:#000000;stop-opacity:1;" offset="0" id="stop4"/>
|
||||||
|
<stop style="stop-color:#000000;stop-opacity:0;" offset="1" id="stop5"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<g id="layer2">
|
||||||
|
<rect style="fill:#0082c7;fill-opacity:1;stroke-width:78.4285" id="rect6" width="7.2357788" height="16.960531" x="37.661629" y="50.772774" rx="2.9794366"/>
|
||||||
|
<rect style="fill:#0082c7;fill-opacity:1;stroke-width:78.4285" id="rect6-6" width="7.2357788" height="16.960531" x="22.835808" y="50.772804" rx="2.9794366"/>
|
||||||
|
<ellipse style="fill:#0eabff;fill-opacity:1;stroke-width:63.9157" id="path6" cx="33.866619" cy="29.256916" rx="29.36875" ry="29.256916"/>
|
||||||
|
<ellipse style="fill:#0081c7;fill-opacity:1;stroke-width:96.3698" id="path7-9" cx="33.866619" cy="29.256916" rx="27.95863" ry="19.221935"/>
|
||||||
|
<ellipse style="fill:#ffffff;fill-opacity:1;stroke-width:82.2933" id="path7" cx="33.866619" cy="29.256916" rx="26.034615" ry="15.05247"/>
|
||||||
|
<ellipse style="fill:#004970;fill-opacity:1;stroke-width:124.026" id="path8" cx="33.866619" cy="29.256916" rx="4.8238525" ry="4.8054838"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
32
svg/series-troxide.svg
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<svg xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" version="1.1" id="svg1" inkscape:version="1.3 (0e150ed6c4, 2023-07-21)" sodipodi:docname="series-troxide.svg" inkscape:export-filename="series-troxide.png" inkscape:export-xdpi="179.29411" inkscape:export-ydpi="179.29411" viewBox="9.47 5.66 83.47 87.95">
|
||||||
|
<sodipodi:namedview id="namedview1" pagecolor="#ffffff" bordercolor="#000000" borderopacity="0.25" inkscape:showpageshadow="2" inkscape:pageopacity="0.0" inkscape:pagecheckerboard="0" inkscape:deskcolor="#d1d1d1" inkscape:document-units="mm" inkscape:zoom="1.0721378" inkscape:cx="200.06757" inkscape:cy="226.18362" inkscape:window-width="1920" inkscape:window-height="1055" inkscape:window-x="0" inkscape:window-y="25" inkscape:window-maximized="1" inkscape:current-layer="layer2"/>
|
||||||
|
<defs id="defs1">
|
||||||
|
<inkscape:path-effect effect="skeletal" id="path-effect11" is_visible="true" lpeversion="1" pattern="M 0,4.9921382 C 0,2.2364779 2.2364779,0 4.9921382,0 c 2.7556604,0 4.9921383,2.2364779 4.9921383,4.9921382 0,2.7556604 -2.2364779,4.9921383 -4.9921383,4.9921383 C 2.2364779,9.9842765 0,7.7477986 0,4.9921382 Z" copytype="single_stretched" prop_scale="0.1221914" scale_y_rel="false" spacing="0" normal_offset="0" tang_offset="0" prop_units="false" vertical_pattern="false" hide_knot="false" fuse_tolerance="0"/>
|
||||||
|
<inkscape:path-effect effect="skeletal" id="path-effect11-7" is_visible="true" lpeversion="1" pattern="M 0,4.9921382 C 0,2.2364779 2.2364779,0 4.9921382,0 c 2.7556604,0 4.9921383,2.2364779 4.9921383,4.9921382 0,2.7556604 -2.2364779,4.9921383 -4.9921383,4.9921383 C 2.2364779,9.9842765 0,7.7477986 0,4.9921382 Z" copytype="single_stretched" prop_scale="0.3067289" scale_y_rel="false" spacing="0" normal_offset="0" tang_offset="0" prop_units="false" vertical_pattern="false" hide_knot="false" fuse_tolerance="0"/>
|
||||||
|
</defs>
|
||||||
|
<g inkscape:groupmode="layer" id="layer7" inkscape:label="stand" style="display:inline">
|
||||||
|
<ellipse style="fill:#b3b3b3;fill-opacity:1;stroke:#000000;stroke-width:0.362231" id="path10" cx="53.734413" cy="88.932671" rx="18.540586" ry="4.4936233"/>
|
||||||
|
</g>
|
||||||
|
<g inkscape:groupmode="layer" id="layer2" inkscape:label="body">
|
||||||
|
<ellipse style="fill:#8f6593;fill-opacity:1;stroke:#000000;stroke-width:0.264583" id="path2" cx="53.173447" cy="59.926918" rx="39.631786" ry="31.20598"/>
|
||||||
|
</g>
|
||||||
|
<g inkscape:groupmode="layer" id="layer6" inkscape:label="right-antenna" transform="rotate(26.626716,63.663425,91.348082)" style="display:inline">
|
||||||
|
<rect style="fill:#b3b3b3;stroke:#000000;stroke-width:0.264583" id="rect5" width="2.0064371" height="21.225708" x="27.764742" y="25.941788"/>
|
||||||
|
<rect style="fill:#b3b3b3;stroke:#000000;stroke-width:0.264583" id="rect6" width="0.87785858" height="11.139125" x="28.320299" y="14.803336"/>
|
||||||
|
<rect style="fill:#b3b3b3;stroke:#000000;stroke-width:0.264583" id="rect7" width="1.7391598" height="1.0371615" x="27.84543" y="13.598304"/>
|
||||||
|
</g>
|
||||||
|
<g inkscape:groupmode="layer" id="g10" inkscape:label="left-antenna" transform="matrix(0.78455439,-0.39703989,0.36681053,0.84921059,-42.959282,47.424815)" style="display:inline">
|
||||||
|
<rect style="fill:#b3b3b3;stroke:#000000;stroke-width:0.264583" id="rect8" width="2.0064371" height="21.225708" x="88.477699" y="6.2007856"/>
|
||||||
|
<rect style="fill:#b3b3b3;stroke:#000000;stroke-width:0.264583" id="rect9" width="0.87785858" height="11.139125" x="89.033257" y="-4.9376626"/>
|
||||||
|
<rect style="fill:#b3b3b3;stroke:#000000;stroke-width:0.264583" id="rect10" width="1.7391598" height="1.0371615" x="88.558388" y="-6.142695"/>
|
||||||
|
</g>
|
||||||
|
<g inkscape:label="screen" inkscape:groupmode="layer" id="layer1" style="display:inline">
|
||||||
|
<ellipse style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.264583" id="path3" cx="38.638641" cy="60.920479" rx="29.03672" ry="22.062567"/>
|
||||||
|
<ellipse style="fill:#666666;stroke:#000000;stroke-width:0.247975" id="ellipse3" cx="38.034561" cy="60.978752" rx="27.778275" ry="20.257765"/>
|
||||||
|
<path style="fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:0.264583" d="m 34.697799,122.55314 c 0.01833,0.006 0.0316,0.0245 0.04135,0.0555 0,0 0,0 0,0 0.0092,0.0295 0.01472,0.0703 0.0168,0.12182 v 0 0 0 c 0.0042,0.10159 -0.005,0.24651 -0.02457,0.42892 v 0 c -0.03801,0.35347 -0.118996,0.87532 -0.215147,1.50114 v 0 c -0.185128,1.20614 -0.442822,2.88292 -0.559325,4.78641 -0.04494,0.75002 -0.07046,1.54169 -0.05525,2.34859 0.02287,1.21336 0.135161,2.31717 0.339835,3.3493 0,0 0,0 0,0 0.650332,3.24476 2.351371,5.07761 2.173869,5.2629 -0.149012,0.15556 -2.3584,-1.29697 -3.279062,-5.01429 0,0 0,0 0,0 -0.274838,-1.0998 -0.429558,-2.29119 -0.455419,-3.57407 -0.01726,-0.85628 0.02422,-1.68546 0.100139,-2.4653 0.190754,-1.9852 0.603085,-3.67762 0.988428,-4.87091 v 0 c 0.201418,-0.62408 0.395557,-1.10979 0.558632,-1.44128 v 0 c 0.08263,-0.16796 0.156338,-0.2943 0.218718,-0.37713 v 0 0 0 c 0.03135,-0.0416 0.05963,-0.0719 0.0846,-0.0907 0,0 0,0 0,0 0.02622,-0.0197 0.04803,-0.0268 0.06641,-0.0208 z" id="path11" inkscape:path-effect="#path-effect11" inkscape:original-d="m 34.697819,122.55308 c 0,0 -4.11305,12.26932 1.717536,17.85462" sodipodi:nodetypes="cc" transform="matrix(0.97122863,0.08809259,-0.09412623,0.90897134,-6.1653708,-61.757664)"/>
|
||||||
|
</g>
|
||||||
|
<g inkscape:groupmode="layer" id="layer3" inkscape:label="knob" style="display:inline">
|
||||||
|
<ellipse style="fill:#000000;stroke:#000000;stroke-width:0.264583" id="ellipse10" cx="84.35968" cy="58.413628" rx="2.9122553" ry="4.9937477"/>
|
||||||
|
<ellipse style="fill:#b3b3b3;stroke:#000000;stroke-width:0.22029" id="path4" cx="84.715714" cy="58.418968" rx="2.3879521" ry="4.2217774"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 5.6 KiB |
50
tree.json
@@ -225,6 +225,7 @@
|
|||||||
"budgetbee.png",
|
"budgetbee.png",
|
||||||
"budibase.png",
|
"budibase.png",
|
||||||
"buffalo.png",
|
"buffalo.png",
|
||||||
|
"buildium.png",
|
||||||
"bunkerweb.png",
|
"bunkerweb.png",
|
||||||
"buxfer.png",
|
"buxfer.png",
|
||||||
"bytestash.png",
|
"bytestash.png",
|
||||||
@@ -237,6 +238,7 @@
|
|||||||
"cal-com.png",
|
"cal-com.png",
|
||||||
"calckey.png",
|
"calckey.png",
|
||||||
"caldera.png",
|
"caldera.png",
|
||||||
|
"calibre-web-automated-book-downloader.png",
|
||||||
"calibre-web.png",
|
"calibre-web.png",
|
||||||
"calibre.png",
|
"calibre.png",
|
||||||
"camera-ui.png",
|
"camera-ui.png",
|
||||||
@@ -440,6 +442,7 @@
|
|||||||
"domainmod.png",
|
"domainmod.png",
|
||||||
"domoticz.png",
|
"domoticz.png",
|
||||||
"donetick.png",
|
"donetick.png",
|
||||||
|
"doplarr.png",
|
||||||
"dopplertask.png",
|
"dopplertask.png",
|
||||||
"double-commander.png",
|
"double-commander.png",
|
||||||
"double-take-dark.png",
|
"double-take-dark.png",
|
||||||
@@ -500,6 +503,7 @@
|
|||||||
"endless.png",
|
"endless.png",
|
||||||
"endurain.png",
|
"endurain.png",
|
||||||
"enhance.png",
|
"enhance.png",
|
||||||
|
"entergy.png",
|
||||||
"epic-games-light.png",
|
"epic-games-light.png",
|
||||||
"epic-games.png",
|
"epic-games.png",
|
||||||
"epson-iprint.png",
|
"epson-iprint.png",
|
||||||
@@ -676,6 +680,7 @@
|
|||||||
"gogs.png",
|
"gogs.png",
|
||||||
"gollum.png",
|
"gollum.png",
|
||||||
"gomft.png",
|
"gomft.png",
|
||||||
|
"gone-man-switch.png",
|
||||||
"gonic.png",
|
"gonic.png",
|
||||||
"goodreads.png",
|
"goodreads.png",
|
||||||
"google-admin.png",
|
"google-admin.png",
|
||||||
@@ -845,6 +850,7 @@
|
|||||||
"idrac.png",
|
"idrac.png",
|
||||||
"ihatemoney.png",
|
"ihatemoney.png",
|
||||||
"ilo.png",
|
"ilo.png",
|
||||||
|
"image-maid.png",
|
||||||
"immich-frame-light.png",
|
"immich-frame-light.png",
|
||||||
"immich-frame.png",
|
"immich-frame.png",
|
||||||
"immich-kiosk-light.png",
|
"immich-kiosk-light.png",
|
||||||
@@ -928,6 +934,7 @@
|
|||||||
"kapowarr.png",
|
"kapowarr.png",
|
||||||
"karakeep-dark.png",
|
"karakeep-dark.png",
|
||||||
"karakeep.png",
|
"karakeep.png",
|
||||||
|
"karaoke-eternal.png",
|
||||||
"kasm-workspaces.png",
|
"kasm-workspaces.png",
|
||||||
"kasm.png",
|
"kasm.png",
|
||||||
"kasten-k10.png",
|
"kasten-k10.png",
|
||||||
@@ -988,6 +995,7 @@
|
|||||||
"lemmy.png",
|
"lemmy.png",
|
||||||
"lemonldap-ng.png",
|
"lemonldap-ng.png",
|
||||||
"lets-encrypt.png",
|
"lets-encrypt.png",
|
||||||
|
"librechat.png",
|
||||||
"libreddit-light.png",
|
"libreddit-light.png",
|
||||||
"libreddit.png",
|
"libreddit.png",
|
||||||
"libremdb.png",
|
"libremdb.png",
|
||||||
@@ -1036,6 +1044,8 @@
|
|||||||
"logto.png",
|
"logto.png",
|
||||||
"loki.png",
|
"loki.png",
|
||||||
"longhorn.png",
|
"longhorn.png",
|
||||||
|
"loxone-full.png",
|
||||||
|
"loxone.png",
|
||||||
"lsio.png",
|
"lsio.png",
|
||||||
"lua.png",
|
"lua.png",
|
||||||
"lubelogger.png",
|
"lubelogger.png",
|
||||||
@@ -1068,6 +1078,7 @@
|
|||||||
"marginalia.png",
|
"marginalia.png",
|
||||||
"mariadb.png",
|
"mariadb.png",
|
||||||
"marktplaats.png",
|
"marktplaats.png",
|
||||||
|
"marzban.png",
|
||||||
"mastodon.png",
|
"mastodon.png",
|
||||||
"matomo.png",
|
"matomo.png",
|
||||||
"matrix-light.png",
|
"matrix-light.png",
|
||||||
@@ -1274,6 +1285,7 @@
|
|||||||
"nodebb.png",
|
"nodebb.png",
|
||||||
"nodejs-alt.png",
|
"nodejs-alt.png",
|
||||||
"nodejs.png",
|
"nodejs.png",
|
||||||
|
"noisedash.png",
|
||||||
"nomad.png",
|
"nomad.png",
|
||||||
"nomie.png",
|
"nomie.png",
|
||||||
"nordvpn.png",
|
"nordvpn.png",
|
||||||
@@ -1406,6 +1418,7 @@
|
|||||||
"pairdrop.png",
|
"pairdrop.png",
|
||||||
"palemoon.png",
|
"palemoon.png",
|
||||||
"palo-alto.png",
|
"palo-alto.png",
|
||||||
|
"pangolin.png",
|
||||||
"paperless-ng.png",
|
"paperless-ng.png",
|
||||||
"paperless-ngx.png",
|
"paperless-ngx.png",
|
||||||
"paperless.png",
|
"paperless.png",
|
||||||
@@ -1557,6 +1570,7 @@
|
|||||||
"powerpanel.png",
|
"powerpanel.png",
|
||||||
"premium-mobile.png",
|
"premium-mobile.png",
|
||||||
"premiumize.png",
|
"premiumize.png",
|
||||||
|
"pretix.png",
|
||||||
"prime-video-alt-dark.png",
|
"prime-video-alt-dark.png",
|
||||||
"prime-video-alt.png",
|
"prime-video-alt.png",
|
||||||
"prime-video-light.png",
|
"prime-video-light.png",
|
||||||
@@ -1634,12 +1648,14 @@
|
|||||||
"receipt-wrangler.png",
|
"receipt-wrangler.png",
|
||||||
"recipesage.png",
|
"recipesage.png",
|
||||||
"recipya.png",
|
"recipya.png",
|
||||||
|
"recyclarr.png",
|
||||||
"reddit.png",
|
"reddit.png",
|
||||||
"redict.png",
|
"redict.png",
|
||||||
"redis.png",
|
"redis.png",
|
||||||
"redlib-light.png",
|
"redlib-light.png",
|
||||||
"redlib.png",
|
"redlib.png",
|
||||||
"rekor.png",
|
"rekor.png",
|
||||||
|
"release-argus.png",
|
||||||
"remmina.png",
|
"remmina.png",
|
||||||
"remotely.png",
|
"remotely.png",
|
||||||
"renovate.png",
|
"renovate.png",
|
||||||
@@ -1728,6 +1744,7 @@
|
|||||||
"sentry-light.png",
|
"sentry-light.png",
|
||||||
"sentry.png",
|
"sentry.png",
|
||||||
"seq.png",
|
"seq.png",
|
||||||
|
"series-troxide.png",
|
||||||
"serpbear.png",
|
"serpbear.png",
|
||||||
"servarr-light.png",
|
"servarr-light.png",
|
||||||
"servarr.png",
|
"servarr.png",
|
||||||
@@ -1830,6 +1847,7 @@
|
|||||||
"storm.png",
|
"storm.png",
|
||||||
"stormkit.png",
|
"stormkit.png",
|
||||||
"strapi.png",
|
"strapi.png",
|
||||||
|
"stream-harvestarr.png",
|
||||||
"streama.png",
|
"streama.png",
|
||||||
"stremio.png",
|
"stremio.png",
|
||||||
"stump-alt.png",
|
"stump-alt.png",
|
||||||
@@ -2084,6 +2102,7 @@
|
|||||||
"watcharr-light.png",
|
"watcharr-light.png",
|
||||||
"watcharr.png",
|
"watcharr.png",
|
||||||
"watcher.png",
|
"watcher.png",
|
||||||
|
"watchlistarr.png",
|
||||||
"watchtower.png",
|
"watchtower.png",
|
||||||
"watchyourlan.png",
|
"watchyourlan.png",
|
||||||
"watchyourports.png",
|
"watchyourports.png",
|
||||||
@@ -2392,6 +2411,7 @@
|
|||||||
"budgetbee-light.svg",
|
"budgetbee-light.svg",
|
||||||
"budgetbee.svg",
|
"budgetbee.svg",
|
||||||
"budibase.svg",
|
"budibase.svg",
|
||||||
|
"buildium.svg",
|
||||||
"bunkerweb.svg",
|
"bunkerweb.svg",
|
||||||
"bytestash.svg",
|
"bytestash.svg",
|
||||||
"c.svg",
|
"c.svg",
|
||||||
@@ -2551,6 +2571,7 @@
|
|||||||
"dokemon.svg",
|
"dokemon.svg",
|
||||||
"dokuwiki.svg",
|
"dokuwiki.svg",
|
||||||
"donetick.svg",
|
"donetick.svg",
|
||||||
|
"doplarr.svg",
|
||||||
"double-commander.svg",
|
"double-commander.svg",
|
||||||
"double-take-dark.svg",
|
"double-take-dark.svg",
|
||||||
"double-take.svg",
|
"double-take.svg",
|
||||||
@@ -2605,6 +2626,7 @@
|
|||||||
"endless.svg",
|
"endless.svg",
|
||||||
"endurain.svg",
|
"endurain.svg",
|
||||||
"enhance.svg",
|
"enhance.svg",
|
||||||
|
"entergy.svg",
|
||||||
"epic-games-light.svg",
|
"epic-games-light.svg",
|
||||||
"epic-games.svg",
|
"epic-games.svg",
|
||||||
"erste-george.svg",
|
"erste-george.svg",
|
||||||
@@ -3027,6 +3049,7 @@
|
|||||||
"lemmy-light.svg",
|
"lemmy-light.svg",
|
||||||
"lemmy.svg",
|
"lemmy.svg",
|
||||||
"lets-encrypt.svg",
|
"lets-encrypt.svg",
|
||||||
|
"librechat.svg",
|
||||||
"libreddit-light.svg",
|
"libreddit-light.svg",
|
||||||
"libreddit.svg",
|
"libreddit.svg",
|
||||||
"librenms.svg",
|
"librenms.svg",
|
||||||
@@ -3067,6 +3090,8 @@
|
|||||||
"logto.svg",
|
"logto.svg",
|
||||||
"loki.svg",
|
"loki.svg",
|
||||||
"longhorn.svg",
|
"longhorn.svg",
|
||||||
|
"loxone-full.svg",
|
||||||
|
"loxone.svg",
|
||||||
"lua.svg",
|
"lua.svg",
|
||||||
"lunasea.svg",
|
"lunasea.svg",
|
||||||
"lynx-light.svg",
|
"lynx-light.svg",
|
||||||
@@ -3264,6 +3289,7 @@
|
|||||||
"nodebb.svg",
|
"nodebb.svg",
|
||||||
"nodejs-alt.svg",
|
"nodejs-alt.svg",
|
||||||
"nodejs.svg",
|
"nodejs.svg",
|
||||||
|
"noisedash.svg",
|
||||||
"nomad.svg",
|
"nomad.svg",
|
||||||
"nomie.svg",
|
"nomie.svg",
|
||||||
"nordvpn.svg",
|
"nordvpn.svg",
|
||||||
@@ -3370,6 +3396,7 @@
|
|||||||
"pagerduty.svg",
|
"pagerduty.svg",
|
||||||
"palemoon.svg",
|
"palemoon.svg",
|
||||||
"palo-alto.svg",
|
"palo-alto.svg",
|
||||||
|
"pangolin.svg",
|
||||||
"paperless-ng.svg",
|
"paperless-ng.svg",
|
||||||
"paperless-ngx.svg",
|
"paperless-ngx.svg",
|
||||||
"paperless.svg",
|
"paperless.svg",
|
||||||
@@ -3496,6 +3523,7 @@
|
|||||||
"powerbi.svg",
|
"powerbi.svg",
|
||||||
"powerdns.svg",
|
"powerdns.svg",
|
||||||
"premiumize.svg",
|
"premiumize.svg",
|
||||||
|
"pretix.svg",
|
||||||
"prime-video-alt-dark.svg",
|
"prime-video-alt-dark.svg",
|
||||||
"prime-video-alt.svg",
|
"prime-video-alt.svg",
|
||||||
"prime-video-light.svg",
|
"prime-video-light.svg",
|
||||||
@@ -3558,12 +3586,14 @@
|
|||||||
"recalbox.svg",
|
"recalbox.svg",
|
||||||
"receipt-wrangler.svg",
|
"receipt-wrangler.svg",
|
||||||
"recipesage.svg",
|
"recipesage.svg",
|
||||||
|
"recyclarr.svg",
|
||||||
"reddit.svg",
|
"reddit.svg",
|
||||||
"redict.svg",
|
"redict.svg",
|
||||||
"redis.svg",
|
"redis.svg",
|
||||||
"redlib-light.svg",
|
"redlib-light.svg",
|
||||||
"redlib.svg",
|
"redlib.svg",
|
||||||
"rekor.svg",
|
"rekor.svg",
|
||||||
|
"release-argus.svg",
|
||||||
"remmina.svg",
|
"remmina.svg",
|
||||||
"renovate.svg",
|
"renovate.svg",
|
||||||
"reolink.svg",
|
"reolink.svg",
|
||||||
@@ -3635,6 +3665,7 @@
|
|||||||
"sensu.svg",
|
"sensu.svg",
|
||||||
"sentry-light.svg",
|
"sentry-light.svg",
|
||||||
"sentry.svg",
|
"sentry.svg",
|
||||||
|
"series-troxide.svg",
|
||||||
"servarr-light.svg",
|
"servarr-light.svg",
|
||||||
"servarr.svg",
|
"servarr.svg",
|
||||||
"serviio-light.svg",
|
"serviio-light.svg",
|
||||||
@@ -4226,6 +4257,7 @@
|
|||||||
"budgetbee.webp",
|
"budgetbee.webp",
|
||||||
"budibase.webp",
|
"budibase.webp",
|
||||||
"buffalo.webp",
|
"buffalo.webp",
|
||||||
|
"buildium.webp",
|
||||||
"bunkerweb.webp",
|
"bunkerweb.webp",
|
||||||
"buxfer.webp",
|
"buxfer.webp",
|
||||||
"bytestash.webp",
|
"bytestash.webp",
|
||||||
@@ -4238,6 +4270,7 @@
|
|||||||
"cal-com.webp",
|
"cal-com.webp",
|
||||||
"calckey.webp",
|
"calckey.webp",
|
||||||
"caldera.webp",
|
"caldera.webp",
|
||||||
|
"calibre-web-automated-book-downloader.webp",
|
||||||
"calibre-web.webp",
|
"calibre-web.webp",
|
||||||
"calibre.webp",
|
"calibre.webp",
|
||||||
"camera-ui.webp",
|
"camera-ui.webp",
|
||||||
@@ -4441,6 +4474,7 @@
|
|||||||
"domainmod.webp",
|
"domainmod.webp",
|
||||||
"domoticz.webp",
|
"domoticz.webp",
|
||||||
"donetick.webp",
|
"donetick.webp",
|
||||||
|
"doplarr.webp",
|
||||||
"dopplertask.webp",
|
"dopplertask.webp",
|
||||||
"double-commander.webp",
|
"double-commander.webp",
|
||||||
"double-take-dark.webp",
|
"double-take-dark.webp",
|
||||||
@@ -4501,6 +4535,7 @@
|
|||||||
"endless.webp",
|
"endless.webp",
|
||||||
"endurain.webp",
|
"endurain.webp",
|
||||||
"enhance.webp",
|
"enhance.webp",
|
||||||
|
"entergy.webp",
|
||||||
"epic-games-light.webp",
|
"epic-games-light.webp",
|
||||||
"epic-games.webp",
|
"epic-games.webp",
|
||||||
"epson-iprint.webp",
|
"epson-iprint.webp",
|
||||||
@@ -4677,6 +4712,7 @@
|
|||||||
"gogs.webp",
|
"gogs.webp",
|
||||||
"gollum.webp",
|
"gollum.webp",
|
||||||
"gomft.webp",
|
"gomft.webp",
|
||||||
|
"gone-man-switch.webp",
|
||||||
"gonic.webp",
|
"gonic.webp",
|
||||||
"goodreads.webp",
|
"goodreads.webp",
|
||||||
"google-admin.webp",
|
"google-admin.webp",
|
||||||
@@ -4846,6 +4882,7 @@
|
|||||||
"idrac.webp",
|
"idrac.webp",
|
||||||
"ihatemoney.webp",
|
"ihatemoney.webp",
|
||||||
"ilo.webp",
|
"ilo.webp",
|
||||||
|
"image-maid.webp",
|
||||||
"immich-frame-light.webp",
|
"immich-frame-light.webp",
|
||||||
"immich-frame.webp",
|
"immich-frame.webp",
|
||||||
"immich-kiosk-light.webp",
|
"immich-kiosk-light.webp",
|
||||||
@@ -4929,6 +4966,7 @@
|
|||||||
"kapowarr.webp",
|
"kapowarr.webp",
|
||||||
"karakeep-dark.webp",
|
"karakeep-dark.webp",
|
||||||
"karakeep.webp",
|
"karakeep.webp",
|
||||||
|
"karaoke-eternal.webp",
|
||||||
"kasm-workspaces.webp",
|
"kasm-workspaces.webp",
|
||||||
"kasm.webp",
|
"kasm.webp",
|
||||||
"kasten-k10.webp",
|
"kasten-k10.webp",
|
||||||
@@ -4989,6 +5027,7 @@
|
|||||||
"lemmy.webp",
|
"lemmy.webp",
|
||||||
"lemonldap-ng.webp",
|
"lemonldap-ng.webp",
|
||||||
"lets-encrypt.webp",
|
"lets-encrypt.webp",
|
||||||
|
"librechat.webp",
|
||||||
"libreddit-light.webp",
|
"libreddit-light.webp",
|
||||||
"libreddit.webp",
|
"libreddit.webp",
|
||||||
"libremdb.webp",
|
"libremdb.webp",
|
||||||
@@ -5037,6 +5076,8 @@
|
|||||||
"logto.webp",
|
"logto.webp",
|
||||||
"loki.webp",
|
"loki.webp",
|
||||||
"longhorn.webp",
|
"longhorn.webp",
|
||||||
|
"loxone-full.webp",
|
||||||
|
"loxone.webp",
|
||||||
"lsio.webp",
|
"lsio.webp",
|
||||||
"lua.webp",
|
"lua.webp",
|
||||||
"lubelogger.webp",
|
"lubelogger.webp",
|
||||||
@@ -5069,6 +5110,7 @@
|
|||||||
"marginalia.webp",
|
"marginalia.webp",
|
||||||
"mariadb.webp",
|
"mariadb.webp",
|
||||||
"marktplaats.webp",
|
"marktplaats.webp",
|
||||||
|
"marzban.webp",
|
||||||
"mastodon.webp",
|
"mastodon.webp",
|
||||||
"matomo.webp",
|
"matomo.webp",
|
||||||
"matrix-light.webp",
|
"matrix-light.webp",
|
||||||
@@ -5275,6 +5317,7 @@
|
|||||||
"nodebb.webp",
|
"nodebb.webp",
|
||||||
"nodejs-alt.webp",
|
"nodejs-alt.webp",
|
||||||
"nodejs.webp",
|
"nodejs.webp",
|
||||||
|
"noisedash.webp",
|
||||||
"nomad.webp",
|
"nomad.webp",
|
||||||
"nomie.webp",
|
"nomie.webp",
|
||||||
"nordvpn.webp",
|
"nordvpn.webp",
|
||||||
@@ -5407,6 +5450,7 @@
|
|||||||
"pairdrop.webp",
|
"pairdrop.webp",
|
||||||
"palemoon.webp",
|
"palemoon.webp",
|
||||||
"palo-alto.webp",
|
"palo-alto.webp",
|
||||||
|
"pangolin.webp",
|
||||||
"paperless-ng.webp",
|
"paperless-ng.webp",
|
||||||
"paperless-ngx.webp",
|
"paperless-ngx.webp",
|
||||||
"paperless.webp",
|
"paperless.webp",
|
||||||
@@ -5558,6 +5602,7 @@
|
|||||||
"powerpanel.webp",
|
"powerpanel.webp",
|
||||||
"premium-mobile.webp",
|
"premium-mobile.webp",
|
||||||
"premiumize.webp",
|
"premiumize.webp",
|
||||||
|
"pretix.webp",
|
||||||
"prime-video-alt-dark.webp",
|
"prime-video-alt-dark.webp",
|
||||||
"prime-video-alt.webp",
|
"prime-video-alt.webp",
|
||||||
"prime-video-light.webp",
|
"prime-video-light.webp",
|
||||||
@@ -5635,12 +5680,14 @@
|
|||||||
"receipt-wrangler.webp",
|
"receipt-wrangler.webp",
|
||||||
"recipesage.webp",
|
"recipesage.webp",
|
||||||
"recipya.webp",
|
"recipya.webp",
|
||||||
|
"recyclarr.webp",
|
||||||
"reddit.webp",
|
"reddit.webp",
|
||||||
"redict.webp",
|
"redict.webp",
|
||||||
"redis.webp",
|
"redis.webp",
|
||||||
"redlib-light.webp",
|
"redlib-light.webp",
|
||||||
"redlib.webp",
|
"redlib.webp",
|
||||||
"rekor.webp",
|
"rekor.webp",
|
||||||
|
"release-argus.webp",
|
||||||
"remmina.webp",
|
"remmina.webp",
|
||||||
"remotely.webp",
|
"remotely.webp",
|
||||||
"renovate.webp",
|
"renovate.webp",
|
||||||
@@ -5729,6 +5776,7 @@
|
|||||||
"sentry-light.webp",
|
"sentry-light.webp",
|
||||||
"sentry.webp",
|
"sentry.webp",
|
||||||
"seq.webp",
|
"seq.webp",
|
||||||
|
"series-troxide.webp",
|
||||||
"serpbear.webp",
|
"serpbear.webp",
|
||||||
"servarr-light.webp",
|
"servarr-light.webp",
|
||||||
"servarr.webp",
|
"servarr.webp",
|
||||||
@@ -5831,6 +5879,7 @@
|
|||||||
"storm.webp",
|
"storm.webp",
|
||||||
"stormkit.webp",
|
"stormkit.webp",
|
||||||
"strapi.webp",
|
"strapi.webp",
|
||||||
|
"stream-harvestarr.webp",
|
||||||
"streama.webp",
|
"streama.webp",
|
||||||
"stremio.webp",
|
"stremio.webp",
|
||||||
"stump-alt.webp",
|
"stump-alt.webp",
|
||||||
@@ -6085,6 +6134,7 @@
|
|||||||
"watcharr-light.webp",
|
"watcharr-light.webp",
|
||||||
"watcharr.webp",
|
"watcharr.webp",
|
||||||
"watcher.webp",
|
"watcher.webp",
|
||||||
|
"watchlistarr.webp",
|
||||||
"watchtower.webp",
|
"watchtower.webp",
|
||||||
"watchyourlan.webp",
|
"watchyourlan.webp",
|
||||||
"watchyourports.webp",
|
"watchyourports.webp",
|
||||||
|
|||||||
@@ -42,7 +42,6 @@
|
|||||||
"canvas-confetti": "^1.9.3",
|
"canvas-confetti": "^1.9.3",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "^1.1.1",
|
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"embla-carousel-react": "^8.6.0",
|
"embla-carousel-react": "^8.6.0",
|
||||||
"framer-motion": "^12.7.3",
|
"framer-motion": "^12.7.3",
|
||||||
|
|||||||
249
web/pnpm-lock.yaml
generated
@@ -101,9 +101,6 @@ importers:
|
|||||||
clsx:
|
clsx:
|
||||||
specifier: ^2.1.1
|
specifier: ^2.1.1
|
||||||
version: 2.1.1
|
version: 2.1.1
|
||||||
cmdk:
|
|
||||||
specifier: ^1.1.1
|
|
||||||
version: 1.1.1(@types/react-dom@19.1.2(@types/react@19.1.0))(@types/react@19.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
|
||||||
date-fns:
|
date-fns:
|
||||||
specifier: ^4.1.0
|
specifier: ^4.1.0
|
||||||
version: 4.1.0
|
version: 4.1.0
|
||||||
@@ -160,7 +157,7 @@ importers:
|
|||||||
version: 3.2.0
|
version: 3.2.0
|
||||||
tailwindcss-motion:
|
tailwindcss-motion:
|
||||||
specifier: ^1.1.0
|
specifier: ^1.1.0
|
||||||
version: 1.1.0(tailwindcss@4.1.3)
|
version: 1.1.0(tailwindcss@4.1.4)
|
||||||
tw-animate-css:
|
tw-animate-css:
|
||||||
specifier: ^1.2.5
|
specifier: ^1.2.5
|
||||||
version: 1.2.5
|
version: 1.2.5
|
||||||
@@ -176,13 +173,13 @@ importers:
|
|||||||
version: 1.9.4
|
version: 1.9.4
|
||||||
'@tailwindcss/postcss':
|
'@tailwindcss/postcss':
|
||||||
specifier: ^4.1.3
|
specifier: ^4.1.3
|
||||||
version: 4.1.3
|
version: 4.1.4
|
||||||
'@types/canvas-confetti':
|
'@types/canvas-confetti':
|
||||||
specifier: ^1.9.0
|
specifier: ^1.9.0
|
||||||
version: 1.9.0
|
version: 1.9.0
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^22.14.0
|
specifier: ^22.14.0
|
||||||
version: 22.14.0
|
version: 22.14.1
|
||||||
'@types/react':
|
'@types/react':
|
||||||
specifier: ^19.1.0
|
specifier: ^19.1.0
|
||||||
version: 19.1.0
|
version: 19.1.0
|
||||||
@@ -191,13 +188,13 @@ importers:
|
|||||||
version: 19.1.2(@types/react@19.1.0)
|
version: 19.1.2(@types/react@19.1.0)
|
||||||
tailwindcss:
|
tailwindcss:
|
||||||
specifier: ^4.1.3
|
specifier: ^4.1.3
|
||||||
version: 4.1.3
|
version: 4.1.4
|
||||||
typescript:
|
typescript:
|
||||||
specifier: ^5.8.3
|
specifier: ^5.8.3
|
||||||
version: 5.8.3
|
version: 5.8.3
|
||||||
wrangler:
|
wrangler:
|
||||||
specifier: ^4.12.0
|
specifier: ^4.12.0
|
||||||
version: 4.12.0
|
version: 4.12.1
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
@@ -275,32 +272,32 @@ packages:
|
|||||||
workerd:
|
workerd:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@cloudflare/workerd-darwin-64@1.20250416.0':
|
'@cloudflare/workerd-darwin-64@1.20250417.0':
|
||||||
resolution: {integrity: sha512-aZgF8Swp9eVYxJPWOoZbAgAaYjWuYqGmEA+QJ2ecRGDBqm87rT4GEw7/mmLpxrpllny3VfEEhkk9iYCGv8nlFw==}
|
resolution: {integrity: sha512-4Adfl92aKepjxb8e6af2d+xpD2sBOADgHqvkyXsFmoLb80weMEDDRGJi1p1m5q1M78/oVnGcpdmuRCAathanRg==}
|
||||||
engines: {node: '>=16'}
|
engines: {node: '>=16'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@cloudflare/workerd-darwin-arm64@1.20250416.0':
|
'@cloudflare/workerd-darwin-arm64@1.20250417.0':
|
||||||
resolution: {integrity: sha512-FhswG1QYRfaTZ4FAlUkfVWaoM2lrlqumiBTrhbo9czMJdGR/oBXS4SGynuI6zyhApHeBf3/fZpA/SBAe4cXdgg==}
|
resolution: {integrity: sha512-dSlk18F4i3T1OTzFBxx3pKpXRMP6w2xZ26+oIV32BFWrCi/HxGzUd6gVA0q37oLGqITRt8xU693J4Gl1CwC/Ag==}
|
||||||
engines: {node: '>=16'}
|
engines: {node: '>=16'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@cloudflare/workerd-linux-64@1.20250416.0':
|
'@cloudflare/workerd-linux-64@1.20250417.0':
|
||||||
resolution: {integrity: sha512-G+nXEAJ/9y+A857XShwxKeRdfxok6UcjiQe6G+wQeCn/Ofkp/EWydacKdyeVU6QIm1oHS78DwJ7AzbCYywf9aw==}
|
resolution: {integrity: sha512-27MVzOa/lENcqewC2L9EcqstXW843UhjBMcwV1umDfsjwLyZOEv6Gtm/6j5r0L0gASvkRTam3fAmtPk/gt48TA==}
|
||||||
engines: {node: '>=16'}
|
engines: {node: '>=16'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@cloudflare/workerd-linux-arm64@1.20250416.0':
|
'@cloudflare/workerd-linux-arm64@1.20250417.0':
|
||||||
resolution: {integrity: sha512-U6oVW0d9w1fpnDYNrjPJ9SFkDlGJWJWbXHlTBObXl6vccP16WewvuxyHkKqyUhUc8hyBaph7sxeKzKmuCFQ4SA==}
|
resolution: {integrity: sha512-34qBk0htAXmUneOTQxW6/g6pjNVR91r0vJzz2FID84cAIOYVl4hZLijkjmVl+MMDU6boXUs+yDwhItdg06YvAg==}
|
||||||
engines: {node: '>=16'}
|
engines: {node: '>=16'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@cloudflare/workerd-windows-64@1.20250416.0':
|
'@cloudflare/workerd-windows-64@1.20250417.0':
|
||||||
resolution: {integrity: sha512-YAjjTzL1z9YYeN4sqYfj1dtQXd2Bblj+B+hl4Rz2aOhblpZEZAdhapZlOCRvLLkOJshKJUnRD3mDlytAdgwybQ==}
|
resolution: {integrity: sha512-PDwATFioff+geVHfgTzSWsxgwjgotrdXStb0EL0lMyMT5zNmHArAnOx83CbDtud63Uv9rVX1BAfPP4tyD1O+5A==}
|
||||||
engines: {node: '>=16'}
|
engines: {node: '>=16'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
@@ -1365,81 +1362,93 @@ packages:
|
|||||||
'@swc/helpers@0.5.15':
|
'@swc/helpers@0.5.15':
|
||||||
resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
|
resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
|
||||||
|
|
||||||
'@tailwindcss/node@4.1.3':
|
'@tailwindcss/node@4.1.4':
|
||||||
resolution: {integrity: sha512-H/6r6IPFJkCfBJZ2dKZiPJ7Ueb2wbL592+9bQEl2r73qbX6yGnmQVIfiUvDRB2YI0a3PWDrzUwkvQx1XW1bNkA==}
|
resolution: {integrity: sha512-MT5118zaiO6x6hNA04OWInuAiP1YISXql8Z+/Y8iisV5nuhM8VXlyhRuqc2PEviPszcXI66W44bCIk500Oolhw==}
|
||||||
|
|
||||||
'@tailwindcss/oxide-android-arm64@4.1.3':
|
'@tailwindcss/oxide-android-arm64@4.1.4':
|
||||||
resolution: {integrity: sha512-cxklKjtNLwFl3mDYw4XpEfBY+G8ssSg9ADL4Wm6//5woi3XGqlxFsnV5Zb6v07dxw1NvEX2uoqsxO/zWQsgR+g==}
|
resolution: {integrity: sha512-xMMAe/SaCN/vHfQYui3fqaBDEXMu22BVwQ33veLc8ep+DNy7CWN52L+TTG9y1K397w9nkzv+Mw+mZWISiqhmlA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [android]
|
os: [android]
|
||||||
|
|
||||||
'@tailwindcss/oxide-darwin-arm64@4.1.3':
|
'@tailwindcss/oxide-darwin-arm64@4.1.4':
|
||||||
resolution: {integrity: sha512-mqkf2tLR5VCrjBvuRDwzKNShRu99gCAVMkVsaEOFvv6cCjlEKXRecPu9DEnxp6STk5z+Vlbh1M5zY3nQCXMXhw==}
|
resolution: {integrity: sha512-JGRj0SYFuDuAGilWFBlshcexev2hOKfNkoX+0QTksKYq2zgF9VY/vVMq9m8IObYnLna0Xlg+ytCi2FN2rOL0Sg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@tailwindcss/oxide-darwin-x64@4.1.3':
|
'@tailwindcss/oxide-darwin-x64@4.1.4':
|
||||||
resolution: {integrity: sha512-7sGraGaWzXvCLyxrc7d+CCpUN3fYnkkcso3rCzwUmo/LteAl2ZGCDlGvDD8Y/1D3ngxT8KgDj1DSwOnNewKhmg==}
|
resolution: {integrity: sha512-sdDeLNvs3cYeWsEJ4H1DvjOzaGios4QbBTNLVLVs0XQ0V95bffT3+scptzYGPMjm7xv4+qMhCDrkHwhnUySEzA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@tailwindcss/oxide-freebsd-x64@4.1.3':
|
'@tailwindcss/oxide-freebsd-x64@4.1.4':
|
||||||
resolution: {integrity: sha512-E2+PbcbzIReaAYZe997wb9rId246yDkCwAakllAWSGqe6VTg9hHle67hfH6ExjpV2LSK/siRzBUs5wVff3RW9w==}
|
resolution: {integrity: sha512-VHxAqxqdghM83HslPhRsNhHo91McsxRJaEnShJOMu8mHmEj9Ig7ToHJtDukkuLWLzLboh2XSjq/0zO6wgvykNA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [freebsd]
|
os: [freebsd]
|
||||||
|
|
||||||
'@tailwindcss/oxide-linux-arm-gnueabihf@4.1.3':
|
'@tailwindcss/oxide-linux-arm-gnueabihf@4.1.4':
|
||||||
resolution: {integrity: sha512-GvfbJ8wjSSjbLFFE3UYz4Eh8i4L6GiEYqCtA8j2Zd2oXriPuom/Ah/64pg/szWycQpzRnbDiJozoxFU2oJZyfg==}
|
resolution: {integrity: sha512-OTU/m/eV4gQKxy9r5acuesqaymyeSCnsx1cFto/I1WhPmi5HDxX1nkzb8KYBiwkHIGg7CTfo/AcGzoXAJBxLfg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@tailwindcss/oxide-linux-arm64-gnu@4.1.3':
|
'@tailwindcss/oxide-linux-arm64-gnu@4.1.4':
|
||||||
resolution: {integrity: sha512-35UkuCWQTeG9BHcBQXndDOrpsnt3Pj9NVIB4CgNiKmpG8GnCNXeMczkUpOoqcOhO6Cc/mM2W7kaQ/MTEENDDXg==}
|
resolution: {integrity: sha512-hKlLNvbmUC6z5g/J4H+Zx7f7w15whSVImokLPmP6ff1QqTVE+TxUM9PGuNsjHvkvlHUtGTdDnOvGNSEUiXI1Ww==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@tailwindcss/oxide-linux-arm64-musl@4.1.3':
|
'@tailwindcss/oxide-linux-arm64-musl@4.1.4':
|
||||||
resolution: {integrity: sha512-dm18aQiML5QCj9DQo7wMbt1Z2tl3Giht54uVR87a84X8qRtuXxUqnKQkRDK5B4bCOmcZ580lF9YcoMkbDYTXHQ==}
|
resolution: {integrity: sha512-X3As2xhtgPTY/m5edUtddmZ8rCruvBvtxYLMw9OsZdH01L2gS2icsHRwxdU0dMItNfVmrBezueXZCHxVeeb7Aw==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@tailwindcss/oxide-linux-x64-gnu@4.1.3':
|
'@tailwindcss/oxide-linux-x64-gnu@4.1.4':
|
||||||
resolution: {integrity: sha512-LMdTmGe/NPtGOaOfV2HuO7w07jI3cflPrVq5CXl+2O93DCewADK0uW1ORNAcfu2YxDUS035eY2W38TxrsqngxA==}
|
resolution: {integrity: sha512-2VG4DqhGaDSmYIu6C4ua2vSLXnJsb/C9liej7TuSO04NK+JJJgJucDUgmX6sn7Gw3Cs5ZJ9ZLrnI0QRDOjLfNQ==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@tailwindcss/oxide-linux-x64-musl@4.1.3':
|
'@tailwindcss/oxide-linux-x64-musl@4.1.4':
|
||||||
resolution: {integrity: sha512-aalNWwIi54bbFEizwl1/XpmdDrOaCjRFQRgtbv9slWjmNPuJJTIKPHf5/XXDARc9CneW9FkSTqTbyvNecYAEGw==}
|
resolution: {integrity: sha512-v+mxVgH2kmur/X5Mdrz9m7TsoVjbdYQT0b4Z+dr+I4RvreCNXyCFELZL/DO0M1RsidZTrm6O1eMnV6zlgEzTMQ==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@tailwindcss/oxide-win32-arm64-msvc@4.1.3':
|
'@tailwindcss/oxide-wasm32-wasi@4.1.4':
|
||||||
resolution: {integrity: sha512-PEj7XR4OGTGoboTIAdXicKuWl4EQIjKHKuR+bFy9oYN7CFZo0eu74+70O4XuERX4yjqVZGAkCdglBODlgqcCXg==}
|
resolution: {integrity: sha512-2TLe9ir+9esCf6Wm+lLWTMbgklIjiF0pbmDnwmhR9MksVOq+e8aP3TSsXySnBDDvTTVd/vKu1aNttEGj3P6l8Q==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
cpu: [wasm32]
|
||||||
|
bundledDependencies:
|
||||||
|
- '@napi-rs/wasm-runtime'
|
||||||
|
- '@emnapi/core'
|
||||||
|
- '@emnapi/runtime'
|
||||||
|
- '@tybys/wasm-util'
|
||||||
|
- '@emnapi/wasi-threads'
|
||||||
|
- tslib
|
||||||
|
|
||||||
|
'@tailwindcss/oxide-win32-arm64-msvc@4.1.4':
|
||||||
|
resolution: {integrity: sha512-VlnhfilPlO0ltxW9/BgfLI5547PYzqBMPIzRrk4W7uupgCt8z6Trw/tAj6QUtF2om+1MH281Pg+HHUJoLesmng==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
'@tailwindcss/oxide-win32-x64-msvc@4.1.3':
|
'@tailwindcss/oxide-win32-x64-msvc@4.1.4':
|
||||||
resolution: {integrity: sha512-T8gfxECWDBENotpw3HR9SmNiHC9AOJdxs+woasRZ8Q/J4VHN0OMs7F+4yVNZ9EVN26Wv6mZbK0jv7eHYuLJLwA==}
|
resolution: {integrity: sha512-+7S63t5zhYjslUGb8NcgLpFXD+Kq1F/zt5Xv5qTv7HaFTG/DHyHD9GA6ieNAxhgyA4IcKa/zy7Xx4Oad2/wuhw==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
'@tailwindcss/oxide@4.1.3':
|
'@tailwindcss/oxide@4.1.4':
|
||||||
resolution: {integrity: sha512-t16lpHCU7LBxDe/8dCj9ntyNpXaSTAgxWm1u2XQP5NiIu4KGSyrDJJRlK9hJ4U9yJxx0UKCVI67MJWFNll5mOQ==}
|
resolution: {integrity: sha512-p5wOpXyOJx7mKh5MXh5oKk+kqcz8T+bA3z/5VWWeQwFrmuBItGwz8Y2CHk/sJ+dNb9B0nYFfn0rj/cKHZyjahQ==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
|
|
||||||
'@tailwindcss/postcss@4.1.3':
|
'@tailwindcss/postcss@4.1.4':
|
||||||
resolution: {integrity: sha512-6s5nJODm98F++QT49qn8xJKHQRamhYHfMi3X7/ltxiSQ9dyRsaFSfFkfaMsanWzf+TMYQtbk8mt5f6cCVXJwfg==}
|
resolution: {integrity: sha512-bjV6sqycCEa+AQSt2Kr7wpGF1bOZJ5wsqnLEkqSbM/JEHxx/yhMH8wHmdkPyApF9xhHeMSwnnkDUUMMM/hYnXw==}
|
||||||
|
|
||||||
'@tanstack/react-virtual@3.13.6':
|
'@tanstack/react-virtual@3.13.6':
|
||||||
resolution: {integrity: sha512-WT7nWs8ximoQ0CDx/ngoFP7HbQF9Q2wQe4nh2NB+u2486eX3nZRE40P9g6ccCVq7ZfTSH5gFOuCoVH5DLNS/aA==}
|
resolution: {integrity: sha512-WT7nWs8ximoQ0CDx/ngoFP7HbQF9Q2wQe4nh2NB+u2486eX3nZRE40P9g6ccCVq7ZfTSH5gFOuCoVH5DLNS/aA==}
|
||||||
@@ -1480,8 +1489,8 @@ packages:
|
|||||||
'@types/d3-timer@3.0.2':
|
'@types/d3-timer@3.0.2':
|
||||||
resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==}
|
resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==}
|
||||||
|
|
||||||
'@types/node@22.14.0':
|
'@types/node@22.14.1':
|
||||||
resolution: {integrity: sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA==}
|
resolution: {integrity: sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw==}
|
||||||
|
|
||||||
'@types/react-dom@19.1.2':
|
'@types/react-dom@19.1.2':
|
||||||
resolution: {integrity: sha512-XGJkWF41Qq305SKWEILa1O8vzhb3aOo3ogBlSmiqNko/WmRb6QIaweuZCXjKygVDXpzXb5wyxKTSOsmkuqj+Qw==}
|
resolution: {integrity: sha512-XGJkWF41Qq305SKWEILa1O8vzhb3aOo3ogBlSmiqNko/WmRb6QIaweuZCXjKygVDXpzXb5wyxKTSOsmkuqj+Qw==}
|
||||||
@@ -1540,12 +1549,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
|
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
cmdk@1.1.1:
|
|
||||||
resolution: {integrity: sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==}
|
|
||||||
peerDependencies:
|
|
||||||
react: ^18 || ^19 || ^19.0.0-rc
|
|
||||||
react-dom: ^18 || ^19 || ^19.0.0-rc
|
|
||||||
|
|
||||||
color-convert@2.0.1:
|
color-convert@2.0.1:
|
||||||
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
||||||
engines: {node: '>=7.0.0'}
|
engines: {node: '>=7.0.0'}
|
||||||
@@ -1888,8 +1891,8 @@ packages:
|
|||||||
engines: {node: '>=10.0.0'}
|
engines: {node: '>=10.0.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
miniflare@4.20250416.0:
|
miniflare@4.20250417.0:
|
||||||
resolution: {integrity: sha512-261PhPgD9zs5/BTdbWqwiaXtWxb+Av5zKCwTU+HXrA5E4tf3qnULwh3u6SVUOAEArEroFuKJzawsQ9COtNBurQ==}
|
resolution: {integrity: sha512-bROKLQKr4CoS93tnGuw5e08VaNwM3VowTL3Z2Cps1HzY6a4Bq8uNtggQ7WogriMq77jcHn6kbz64bvWyF//Jkw==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
@@ -2153,8 +2156,8 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
tailwindcss: '>=3.0.0 || insiders'
|
tailwindcss: '>=3.0.0 || insiders'
|
||||||
|
|
||||||
tailwindcss@4.1.3:
|
tailwindcss@4.1.4:
|
||||||
resolution: {integrity: sha512-2Q+rw9vy1WFXu5cIxlvsabCwhU2qUwodGq03ODhLJ0jW4ek5BUtoCsnLB0qG+m8AHgEsSJcJGDSDe06FXlP74g==}
|
resolution: {integrity: sha512-1ZIUqtPITFbv/DxRmDr5/agPqJwF69d24m9qmM1939TJehgY539CtzeZRjbLt5G6fSy/7YqqYsfvoTEw9xUI2A==}
|
||||||
|
|
||||||
tapable@2.2.1:
|
tapable@2.2.1:
|
||||||
resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==}
|
resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==}
|
||||||
@@ -2219,17 +2222,17 @@ packages:
|
|||||||
web-vitals@4.2.4:
|
web-vitals@4.2.4:
|
||||||
resolution: {integrity: sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==}
|
resolution: {integrity: sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==}
|
||||||
|
|
||||||
workerd@1.20250416.0:
|
workerd@1.20250417.0:
|
||||||
resolution: {integrity: sha512-Yrx/bZAKbmSvomdTAzzIpOHwpYhs0ldr2wqed22UEhQ0mIplAHY4xmY+SjAJhP/TydZrciOVzBxwM1+4T40KNA==}
|
resolution: {integrity: sha512-naz6oJiVODd3/Lkp9l3vtc56HKOOvx+AWDvEsTa5eSfi5SI9V0HYpLYSPblAwrfazbQ4ff1Vl3jkTl/5JxqCAA==}
|
||||||
engines: {node: '>=16'}
|
engines: {node: '>=16'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
wrangler@4.12.0:
|
wrangler@4.12.1:
|
||||||
resolution: {integrity: sha512-4rfAXOi5KqM3ECvOrZJ97k3zEqxVwtdt4bijd8jcRBZ6iJYvEtjgjVi4TsfkVa/eXGhpfHTUnKu2uk8UHa8M2w==}
|
resolution: {integrity: sha512-jYrz8y2ffhsRqvQLO2dXFi9HLvPUJk3jn7U71GWfBBCHm0I6r2ik7Vs9ajpRcTGlbNw1RY0uIHVJBVR/7bEN5A==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@cloudflare/workers-types': ^4.20250415.0
|
'@cloudflare/workers-types': ^4.20250417.0
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
'@cloudflare/workers-types':
|
'@cloudflare/workers-types':
|
||||||
optional: true
|
optional: true
|
||||||
@@ -2302,25 +2305,25 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
mime: 3.0.0
|
mime: 3.0.0
|
||||||
|
|
||||||
'@cloudflare/unenv-preset@2.3.1(unenv@2.0.0-rc.15)(workerd@1.20250416.0)':
|
'@cloudflare/unenv-preset@2.3.1(unenv@2.0.0-rc.15)(workerd@1.20250417.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
unenv: 2.0.0-rc.15
|
unenv: 2.0.0-rc.15
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
workerd: 1.20250416.0
|
workerd: 1.20250417.0
|
||||||
|
|
||||||
'@cloudflare/workerd-darwin-64@1.20250416.0':
|
'@cloudflare/workerd-darwin-64@1.20250417.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@cloudflare/workerd-darwin-arm64@1.20250416.0':
|
'@cloudflare/workerd-darwin-arm64@1.20250417.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@cloudflare/workerd-linux-64@1.20250416.0':
|
'@cloudflare/workerd-linux-64@1.20250417.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@cloudflare/workerd-linux-arm64@1.20250416.0':
|
'@cloudflare/workerd-linux-arm64@1.20250417.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@cloudflare/workerd-windows-64@1.20250416.0':
|
'@cloudflare/workerd-windows-64@1.20250417.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@cspotcode/source-map-support@0.8.1':
|
'@cspotcode/source-map-support@0.8.1':
|
||||||
@@ -3264,67 +3267,71 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@tailwindcss/node@4.1.3':
|
'@tailwindcss/node@4.1.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
enhanced-resolve: 5.18.1
|
enhanced-resolve: 5.18.1
|
||||||
jiti: 2.4.2
|
jiti: 2.4.2
|
||||||
lightningcss: 1.29.2
|
lightningcss: 1.29.2
|
||||||
tailwindcss: 4.1.3
|
tailwindcss: 4.1.4
|
||||||
|
|
||||||
'@tailwindcss/oxide-android-arm64@4.1.3':
|
'@tailwindcss/oxide-android-arm64@4.1.4':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@tailwindcss/oxide-darwin-arm64@4.1.3':
|
'@tailwindcss/oxide-darwin-arm64@4.1.4':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@tailwindcss/oxide-darwin-x64@4.1.3':
|
'@tailwindcss/oxide-darwin-x64@4.1.4':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@tailwindcss/oxide-freebsd-x64@4.1.3':
|
'@tailwindcss/oxide-freebsd-x64@4.1.4':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@tailwindcss/oxide-linux-arm-gnueabihf@4.1.3':
|
'@tailwindcss/oxide-linux-arm-gnueabihf@4.1.4':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@tailwindcss/oxide-linux-arm64-gnu@4.1.3':
|
'@tailwindcss/oxide-linux-arm64-gnu@4.1.4':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@tailwindcss/oxide-linux-arm64-musl@4.1.3':
|
'@tailwindcss/oxide-linux-arm64-musl@4.1.4':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@tailwindcss/oxide-linux-x64-gnu@4.1.3':
|
'@tailwindcss/oxide-linux-x64-gnu@4.1.4':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@tailwindcss/oxide-linux-x64-musl@4.1.3':
|
'@tailwindcss/oxide-linux-x64-musl@4.1.4':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@tailwindcss/oxide-win32-arm64-msvc@4.1.3':
|
'@tailwindcss/oxide-wasm32-wasi@4.1.4':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@tailwindcss/oxide-win32-x64-msvc@4.1.3':
|
'@tailwindcss/oxide-win32-arm64-msvc@4.1.4':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@tailwindcss/oxide@4.1.3':
|
'@tailwindcss/oxide-win32-x64-msvc@4.1.4':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@tailwindcss/oxide@4.1.4':
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@tailwindcss/oxide-android-arm64': 4.1.3
|
'@tailwindcss/oxide-android-arm64': 4.1.4
|
||||||
'@tailwindcss/oxide-darwin-arm64': 4.1.3
|
'@tailwindcss/oxide-darwin-arm64': 4.1.4
|
||||||
'@tailwindcss/oxide-darwin-x64': 4.1.3
|
'@tailwindcss/oxide-darwin-x64': 4.1.4
|
||||||
'@tailwindcss/oxide-freebsd-x64': 4.1.3
|
'@tailwindcss/oxide-freebsd-x64': 4.1.4
|
||||||
'@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.3
|
'@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.4
|
||||||
'@tailwindcss/oxide-linux-arm64-gnu': 4.1.3
|
'@tailwindcss/oxide-linux-arm64-gnu': 4.1.4
|
||||||
'@tailwindcss/oxide-linux-arm64-musl': 4.1.3
|
'@tailwindcss/oxide-linux-arm64-musl': 4.1.4
|
||||||
'@tailwindcss/oxide-linux-x64-gnu': 4.1.3
|
'@tailwindcss/oxide-linux-x64-gnu': 4.1.4
|
||||||
'@tailwindcss/oxide-linux-x64-musl': 4.1.3
|
'@tailwindcss/oxide-linux-x64-musl': 4.1.4
|
||||||
'@tailwindcss/oxide-win32-arm64-msvc': 4.1.3
|
'@tailwindcss/oxide-wasm32-wasi': 4.1.4
|
||||||
'@tailwindcss/oxide-win32-x64-msvc': 4.1.3
|
'@tailwindcss/oxide-win32-arm64-msvc': 4.1.4
|
||||||
|
'@tailwindcss/oxide-win32-x64-msvc': 4.1.4
|
||||||
|
|
||||||
'@tailwindcss/postcss@4.1.3':
|
'@tailwindcss/postcss@4.1.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@alloc/quick-lru': 5.2.0
|
'@alloc/quick-lru': 5.2.0
|
||||||
'@tailwindcss/node': 4.1.3
|
'@tailwindcss/node': 4.1.4
|
||||||
'@tailwindcss/oxide': 4.1.3
|
'@tailwindcss/oxide': 4.1.4
|
||||||
postcss: 8.5.3
|
postcss: 8.5.3
|
||||||
tailwindcss: 4.1.3
|
tailwindcss: 4.1.4
|
||||||
|
|
||||||
'@tanstack/react-virtual@3.13.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
'@tanstack/react-virtual@3.13.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -3360,7 +3367,7 @@ snapshots:
|
|||||||
|
|
||||||
'@types/d3-timer@3.0.2': {}
|
'@types/d3-timer@3.0.2': {}
|
||||||
|
|
||||||
'@types/node@22.14.0':
|
'@types/node@22.14.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
undici-types: 6.21.0
|
undici-types: 6.21.0
|
||||||
|
|
||||||
@@ -3417,18 +3424,6 @@ snapshots:
|
|||||||
|
|
||||||
clsx@2.1.1: {}
|
clsx@2.1.1: {}
|
||||||
|
|
||||||
cmdk@1.1.1(@types/react-dom@19.1.2(@types/react@19.1.0))(@types/react@19.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
|
|
||||||
dependencies:
|
|
||||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.0)(react@19.1.0)
|
|
||||||
'@radix-ui/react-dialog': 1.1.7(@types/react-dom@19.1.2(@types/react@19.1.0))(@types/react@19.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
|
||||||
'@radix-ui/react-id': 1.1.1(@types/react@19.1.0)(react@19.1.0)
|
|
||||||
'@radix-ui/react-primitive': 2.0.3(@types/react-dom@19.1.2(@types/react@19.1.0))(@types/react@19.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
|
||||||
react: 19.1.0
|
|
||||||
react-dom: 19.1.0(react@19.1.0)
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- '@types/react'
|
|
||||||
- '@types/react-dom'
|
|
||||||
|
|
||||||
color-convert@2.0.1:
|
color-convert@2.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
color-name: 1.1.4
|
color-name: 1.1.4
|
||||||
@@ -3735,7 +3730,7 @@ snapshots:
|
|||||||
|
|
||||||
mime@3.0.0: {}
|
mime@3.0.0: {}
|
||||||
|
|
||||||
miniflare@4.20250416.0:
|
miniflare@4.20250417.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@cspotcode/source-map-support': 0.8.1
|
'@cspotcode/source-map-support': 0.8.1
|
||||||
acorn: 8.14.0
|
acorn: 8.14.0
|
||||||
@@ -3744,7 +3739,7 @@ snapshots:
|
|||||||
glob-to-regexp: 0.4.1
|
glob-to-regexp: 0.4.1
|
||||||
stoppable: 1.1.0
|
stoppable: 1.1.0
|
||||||
undici: 5.29.0
|
undici: 5.29.0
|
||||||
workerd: 1.20250416.0
|
workerd: 1.20250417.0
|
||||||
ws: 8.18.0
|
ws: 8.18.0
|
||||||
youch: 3.3.4
|
youch: 3.3.4
|
||||||
zod: 3.22.3
|
zod: 3.22.3
|
||||||
@@ -4025,11 +4020,11 @@ snapshots:
|
|||||||
|
|
||||||
tailwind-merge@3.2.0: {}
|
tailwind-merge@3.2.0: {}
|
||||||
|
|
||||||
tailwindcss-motion@1.1.0(tailwindcss@4.1.3):
|
tailwindcss-motion@1.1.0(tailwindcss@4.1.4):
|
||||||
dependencies:
|
dependencies:
|
||||||
tailwindcss: 4.1.3
|
tailwindcss: 4.1.4
|
||||||
|
|
||||||
tailwindcss@4.1.3: {}
|
tailwindcss@4.1.4: {}
|
||||||
|
|
||||||
tapable@2.2.1: {}
|
tapable@2.2.1: {}
|
||||||
|
|
||||||
@@ -4100,24 +4095,24 @@ snapshots:
|
|||||||
|
|
||||||
web-vitals@4.2.4: {}
|
web-vitals@4.2.4: {}
|
||||||
|
|
||||||
workerd@1.20250416.0:
|
workerd@1.20250417.0:
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@cloudflare/workerd-darwin-64': 1.20250416.0
|
'@cloudflare/workerd-darwin-64': 1.20250417.0
|
||||||
'@cloudflare/workerd-darwin-arm64': 1.20250416.0
|
'@cloudflare/workerd-darwin-arm64': 1.20250417.0
|
||||||
'@cloudflare/workerd-linux-64': 1.20250416.0
|
'@cloudflare/workerd-linux-64': 1.20250417.0
|
||||||
'@cloudflare/workerd-linux-arm64': 1.20250416.0
|
'@cloudflare/workerd-linux-arm64': 1.20250417.0
|
||||||
'@cloudflare/workerd-windows-64': 1.20250416.0
|
'@cloudflare/workerd-windows-64': 1.20250417.0
|
||||||
|
|
||||||
wrangler@4.12.0:
|
wrangler@4.12.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@cloudflare/kv-asset-handler': 0.4.0
|
'@cloudflare/kv-asset-handler': 0.4.0
|
||||||
'@cloudflare/unenv-preset': 2.3.1(unenv@2.0.0-rc.15)(workerd@1.20250416.0)
|
'@cloudflare/unenv-preset': 2.3.1(unenv@2.0.0-rc.15)(workerd@1.20250417.0)
|
||||||
blake3-wasm: 2.1.5
|
blake3-wasm: 2.1.5
|
||||||
esbuild: 0.25.2
|
esbuild: 0.25.2
|
||||||
miniflare: 4.20250416.0
|
miniflare: 4.20250417.0
|
||||||
path-to-regexp: 6.3.0
|
path-to-regexp: 6.3.0
|
||||||
unenv: 2.0.0-rc.15
|
unenv: 2.0.0-rc.15
|
||||||
workerd: 1.20250416.0
|
workerd: 1.20250417.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents: 2.3.3
|
fsevents: 2.3.3
|
||||||
sharp: 0.33.5
|
sharp: 0.33.5
|
||||||
|
|||||||
@@ -32,16 +32,16 @@ export default function ErrorPage({
|
|||||||
</div>
|
</div>
|
||||||
<h1 className="text-2xl font-bold">Something went wrong</h1>
|
<h1 className="text-2xl font-bold">Something went wrong</h1>
|
||||||
<p className="text-muted-foreground">
|
<p className="text-muted-foreground">
|
||||||
An unexpected error occurred while loading this page. We've been notified and are looking into it.
|
Unable to load this page. We're looking into the issue.
|
||||||
</p>
|
</p>
|
||||||
<div className="flex flex-col sm:flex-row gap-4 justify-center pt-4">
|
<div className="flex flex-col sm:flex-row gap-4 justify-center pt-4">
|
||||||
<Button variant="outline" onClick={() => reset()} className="cursor-pointer">
|
<Button variant="outline" onClick={() => reset()} className="cursor-pointer">
|
||||||
<RefreshCcw className="mr-2 h-4 w-4" />
|
<RefreshCcw className="mr-2 h-4 w-4" />
|
||||||
Try again
|
Retry
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={handleGoBack} className="cursor-pointer">
|
<Button onClick={handleGoBack} className="cursor-pointer">
|
||||||
<ArrowLeft className="mr-2 h-4 w-4" />
|
<ArrowLeft className="mr-2 h-4 w-4" />
|
||||||
Go back
|
Back
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
{error.digest && <p className="text-xs text-muted-foreground mt-6">Error ID: {error.digest}</p>}
|
{error.digest && <p className="text-xs text-muted-foreground mt-6">Error ID: {error.digest}</p>}
|
||||||
|
|||||||
@@ -118,19 +118,6 @@
|
|||||||
transform: rotate(-5deg) scale(0.9);
|
transform: rotate(-5deg) scale(0.9);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
--animate-shiny-text: shiny-text 8s infinite;
|
|
||||||
@keyframes shiny-text {
|
|
||||||
0%,
|
|
||||||
90%,
|
|
||||||
100% {
|
|
||||||
background-position: calc(-100% - var(--shiny-width)) 0;
|
|
||||||
}
|
|
||||||
30%,
|
|
||||||
60% {
|
|
||||||
background-position: calc(100% + var(--shiny-width)) 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
@@ -199,7 +186,7 @@
|
|||||||
--secondary: oklch(0.31 0.03 266.71);
|
--secondary: oklch(0.31 0.03 266.71);
|
||||||
--secondary-foreground: oklch(0.92 0 0);
|
--secondary-foreground: oklch(0.92 0 0);
|
||||||
--muted: oklch(0.31 0.03 266.71);
|
--muted: oklch(0.31 0.03 266.71);
|
||||||
--muted-foreground: oklch(0.72 0 0);
|
--muted-foreground: oklch(0.78 0 0);
|
||||||
--accent: oklch(0.34 0.06 267.59);
|
--accent: oklch(0.34 0.06 267.59);
|
||||||
--accent-foreground: oklch(0.88 0.06 254.13);
|
--accent-foreground: oklch(0.88 0.06 254.13);
|
||||||
--destructive: oklch(0.64 0.21 25.33);
|
--destructive: oklch(0.64 0.21 25.33);
|
||||||
|
|||||||
@@ -2,6 +2,12 @@ import { readFile } from "node:fs/promises"
|
|||||||
import { join } from "node:path"
|
import { join } from "node:path"
|
||||||
import { getAllIcons } from "@/lib/api"
|
import { getAllIcons } from "@/lib/api"
|
||||||
import { ImageResponse } from "next/og"
|
import { ImageResponse } from "next/og"
|
||||||
|
import {
|
||||||
|
SITE_NAME,
|
||||||
|
SITE_TAGLINE,
|
||||||
|
getIconDescription,
|
||||||
|
WEB_URL
|
||||||
|
} from "@/constants"
|
||||||
|
|
||||||
export const dynamic = "force-static"
|
export const dynamic = "force-static"
|
||||||
|
|
||||||
@@ -52,9 +58,9 @@ export default async function Image({ params }: { params: { icon: string } }) {
|
|||||||
position: "relative",
|
position: "relative",
|
||||||
fontFamily: "Inter, system-ui, sans-serif",
|
fontFamily: "Inter, system-ui, sans-serif",
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
backgroundColor: "white",
|
backgroundColor: "#0f172a", // Dark background (slate-900)
|
||||||
backgroundImage:
|
backgroundImage:
|
||||||
"radial-gradient(circle at 25px 25px, lightgray 2%, transparent 0%), radial-gradient(circle at 75px 75px, lightgray 2%, transparent 0%)",
|
"radial-gradient(circle at 25px 25px, #1e293b 2%, transparent 0%), radial-gradient(circle at 75px 75px, #1e293b 2%, transparent 0%)",
|
||||||
backgroundSize: "100px 100px",
|
backgroundSize: "100px 100px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -67,7 +73,7 @@ export default async function Image({ params }: { params: { icon: string } }) {
|
|||||||
width: 400,
|
width: 400,
|
||||||
height: 400,
|
height: 400,
|
||||||
borderRadius: "50%",
|
borderRadius: "50%",
|
||||||
background: "linear-gradient(135deg, rgba(56, 189, 248, 0.1) 0%, rgba(59, 130, 246, 0.1) 100%)",
|
background: "linear-gradient(135deg, rgba(56, 189, 248, 0.15) 0%, rgba(59, 130, 246, 0.15) 100%)",
|
||||||
filter: "blur(80px)",
|
filter: "blur(80px)",
|
||||||
zIndex: 2,
|
zIndex: 2,
|
||||||
}}
|
}}
|
||||||
@@ -80,7 +86,7 @@ export default async function Image({ params }: { params: { icon: string } }) {
|
|||||||
width: 500,
|
width: 500,
|
||||||
height: 500,
|
height: 500,
|
||||||
borderRadius: "50%",
|
borderRadius: "50%",
|
||||||
background: "linear-gradient(135deg, rgba(249, 115, 22, 0.1) 0%, rgba(234, 88, 12, 0.1) 100%)",
|
background: "linear-gradient(135deg, rgba(249, 115, 22, 0.15) 0%, rgba(234, 88, 12, 0.15) 100%)",
|
||||||
filter: "blur(100px)",
|
filter: "blur(100px)",
|
||||||
zIndex: 2,
|
zIndex: 2,
|
||||||
}}
|
}}
|
||||||
@@ -109,8 +115,8 @@ export default async function Image({ params }: { params: { icon: string } }) {
|
|||||||
width: 320,
|
width: 320,
|
||||||
height: 320,
|
height: 320,
|
||||||
borderRadius: 32,
|
borderRadius: 32,
|
||||||
background: "white",
|
background: "#1e293b", // Dark container (slate-800)
|
||||||
boxShadow: "0 25px 50px -12px rgba(0, 0, 0, 0.15), 0 0 0 1px rgba(0, 0, 0, 0.05)",
|
boxShadow: "0 25px 50px -12px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(255, 255, 255, 0.1)",
|
||||||
padding: 30,
|
padding: 30,
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
position: "relative",
|
position: "relative",
|
||||||
@@ -121,7 +127,7 @@ export default async function Image({ params }: { params: { icon: string } }) {
|
|||||||
style={{
|
style={{
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
inset: 0,
|
inset: 0,
|
||||||
background: "linear-gradient(145deg, #ffffff 0%, #f8fafc 100%)",
|
background: "linear-gradient(145deg, #1e293b 0%, #0f172a 100%)",
|
||||||
zIndex: 0,
|
zIndex: 0,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -134,7 +140,7 @@ export default async function Image({ params }: { params: { icon: string } }) {
|
|||||||
objectFit: "contain",
|
objectFit: "contain",
|
||||||
position: "relative",
|
position: "relative",
|
||||||
zIndex: 1,
|
zIndex: 1,
|
||||||
filter: "drop-shadow(0 10px 15px rgba(0, 0, 0, 0.1))",
|
filter: "drop-shadow(0 10px 15px rgba(0, 0, 0, 0.3))",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -154,7 +160,7 @@ export default async function Image({ params }: { params: { icon: string } }) {
|
|||||||
display: "flex",
|
display: "flex",
|
||||||
fontSize: 64,
|
fontSize: 64,
|
||||||
fontWeight: 800,
|
fontWeight: 800,
|
||||||
color: "#0f172a",
|
color: "#f8fafc", // Light text for dark background (slate-50)
|
||||||
lineHeight: 1.1,
|
lineHeight: 1.1,
|
||||||
letterSpacing: "-0.02em",
|
letterSpacing: "-0.02em",
|
||||||
}}
|
}}
|
||||||
@@ -167,14 +173,14 @@ export default async function Image({ params }: { params: { icon: string } }) {
|
|||||||
display: "flex",
|
display: "flex",
|
||||||
fontSize: 32,
|
fontSize: 32,
|
||||||
fontWeight: 500,
|
fontWeight: 500,
|
||||||
color: "#64748b",
|
color: "#94a3b8", // Muted text (slate-400)
|
||||||
lineHeight: 1.4,
|
lineHeight: 1.4,
|
||||||
position: "relative",
|
position: "relative",
|
||||||
paddingLeft: 16,
|
paddingLeft: 16,
|
||||||
borderLeft: "4px solid #94a3b8",
|
borderLeft: "4px solid #64748b", // slate-500
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Amongst {totalIcons} other high-quality dashboard icons
|
{getIconDescription(formattedIconName, totalIcons)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -191,14 +197,14 @@ export default async function Image({ params }: { params: { icon: string } }) {
|
|||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
backgroundColor: "#f1f5f9",
|
backgroundColor: "#334155", // slate-700
|
||||||
color: "#475569",
|
color: "#e2e8f0", // slate-200
|
||||||
border: "2px solid #e2e8f0",
|
border: "2px solid #475569", // slate-600
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
padding: "8px 16px",
|
padding: "8px 16px",
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
boxShadow: "0 1px 2px rgba(0, 0, 0, 0.05)",
|
boxShadow: "0 1px 2px rgba(0, 0, 0, 0.2)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{format}
|
{format}
|
||||||
@@ -219,8 +225,8 @@ export default async function Image({ params }: { params: { icon: string } }) {
|
|||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
background: "#ffffff",
|
background: "#1e293b", // slate-800
|
||||||
borderTop: "2px solid rgba(0, 0, 0, 0.05)",
|
borderTop: "2px solid rgba(255, 255, 255, 0.1)",
|
||||||
zIndex: 20,
|
zIndex: 20,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -229,7 +235,7 @@ export default async function Image({ params }: { params: { icon: string } }) {
|
|||||||
display: "flex",
|
display: "flex",
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
color: "#334155",
|
color: "#e2e8f0", // slate-200
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
gap: 10,
|
gap: 10,
|
||||||
}}
|
}}
|
||||||
@@ -239,11 +245,11 @@ export default async function Image({ params }: { params: { icon: string } }) {
|
|||||||
width: 8,
|
width: 8,
|
||||||
height: 8,
|
height: 8,
|
||||||
borderRadius: "50%",
|
borderRadius: "50%",
|
||||||
backgroundColor: "#3b82f6",
|
backgroundColor: "#3b82f6", // blue-500
|
||||||
marginRight: 4,
|
marginRight: 4,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
dashboardicons.com
|
{WEB_URL.replace("https://", "")}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>,
|
</div>,
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { IconDetails } from "@/components/icon-details"
|
import { IconDetails } from "@/components/icon-details"
|
||||||
import { BASE_URL, WEB_URL } from "@/constants"
|
import { StructuredData } from "@/components/structured-data"
|
||||||
|
import { BASE_URL, GITHUB_URL, ICON_DETAIL_KEYWORDS, SITE_NAME, SITE_TAGLINE, TITLE_SEPARATOR, WEB_URL, getIconDescription, getIconSchema } from "@/constants"
|
||||||
import { getAllIcons, getAuthorData } from "@/lib/api"
|
import { getAllIcons, getAuthorData } from "@/lib/api"
|
||||||
import type { Metadata, ResolvingMetadata } from "next"
|
import type { Metadata, ResolvingMetadata } from "next"
|
||||||
|
import Script from "next/script"
|
||||||
import { notFound } from "next/navigation"
|
import { notFound } from "next/navigation"
|
||||||
|
|
||||||
export const dynamicParams = false
|
export const dynamicParams = false
|
||||||
@@ -40,43 +42,39 @@ export async function generateMetadata({ params, searchParams }: Props, parent:
|
|||||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
||||||
.join(" ")
|
.join(" ")
|
||||||
|
|
||||||
|
const title = `${formattedIconName} Icon ${TITLE_SEPARATOR} ${SITE_NAME}`
|
||||||
|
const fullTitle = `${formattedIconName} Icon ${TITLE_SEPARATOR} ${SITE_NAME} ${TITLE_SEPARATOR} ${SITE_TAGLINE}`
|
||||||
|
const description = getIconDescription(formattedIconName, totalIcons)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: `${formattedIconName} Icon | Dashboard Icons`,
|
title,
|
||||||
description: `Download the ${formattedIconName} icon in SVG, PNG, and WEBP formats for FREE. Part of a collection of ${totalIcons} curated icons for services, applications and tools, designed specifically for dashboards and app directories.`,
|
description,
|
||||||
assets: [iconImageUrl],
|
assets: [iconImageUrl],
|
||||||
category: "icons",
|
category: "Icons",
|
||||||
keywords: [
|
keywords: ICON_DETAIL_KEYWORDS(formattedIconName),
|
||||||
`${formattedIconName} icon`,
|
|
||||||
"dashboard icon",
|
|
||||||
"service icon",
|
|
||||||
"application icon",
|
|
||||||
"tool icon",
|
|
||||||
"web dashboard",
|
|
||||||
"app directory",
|
|
||||||
],
|
|
||||||
icons: {
|
icons: {
|
||||||
icon: iconImageUrl,
|
icon: iconImageUrl,
|
||||||
},
|
},
|
||||||
abstract: `Download the ${formattedIconName} icon in SVG, PNG, and WEBP formats for FREE. Part of a collection of ${totalIcons} curated icons for services, applications and tools, designed specifically for dashboards and app directories.`,
|
abstract: description,
|
||||||
robots: {
|
robots: {
|
||||||
index: true,
|
index: true,
|
||||||
follow: true,
|
follow: true,
|
||||||
},
|
},
|
||||||
openGraph: {
|
openGraph: {
|
||||||
title: `${formattedIconName} Icon | Dashboard Icons`,
|
title: fullTitle,
|
||||||
description: `Download the ${formattedIconName} icon in SVG, PNG, and WEBP formats for FREE. Part of a collection of ${totalIcons} curated icons for services, applications and tools, designed specifically for dashboards and app directories.`,
|
description,
|
||||||
type: "article",
|
type: "article",
|
||||||
url: pageUrl,
|
url: pageUrl,
|
||||||
authors: [authorName],
|
authors: [authorName],
|
||||||
publishedTime: updateDate.toISOString(),
|
publishedTime: updateDate.toISOString(),
|
||||||
modifiedTime: updateDate.toISOString(),
|
modifiedTime: updateDate.toISOString(),
|
||||||
section: "Icons",
|
section: "Icons",
|
||||||
tags: [formattedIconName, "dashboard icon", "service icon", "application icon", "tool icon", "web dashboard", "app directory"],
|
tags: [formattedIconName, ...ICON_DETAIL_KEYWORDS(formattedIconName)],
|
||||||
},
|
},
|
||||||
twitter: {
|
twitter: {
|
||||||
card: "summary_large_image",
|
card: "summary_large_image",
|
||||||
title: `${formattedIconName} Icon | Dashboard Icons`,
|
title: fullTitle,
|
||||||
description: `Download the ${formattedIconName} icon in SVG, PNG, and WEBP formats for FREE. Part of a collection of ${totalIcons} curated icons for services, applications and tools, designed specifically for dashboards and app directories.`,
|
description,
|
||||||
images: [iconImageUrl],
|
images: [iconImageUrl],
|
||||||
},
|
},
|
||||||
alternates: {
|
alternates: {
|
||||||
@@ -87,6 +85,9 @@ export async function generateMetadata({ params, searchParams }: Props, parent:
|
|||||||
webp: `${BASE_URL}/webp/${icon}.webp`,
|
webp: `${BASE_URL}/webp/${icon}.webp`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
other: {
|
||||||
|
"revisit-after": "7 days",
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,6 +101,26 @@ export default async function IconPage({ params }: { params: Promise<{ icon: str
|
|||||||
}
|
}
|
||||||
|
|
||||||
const authorData = await getAuthorData(originalIconData.update.author.id)
|
const authorData = await getAuthorData(originalIconData.update.author.id)
|
||||||
|
const updateDate = new Date(originalIconData.update.timestamp)
|
||||||
|
const authorName = authorData.name || authorData.login
|
||||||
|
const formattedIconName = icon
|
||||||
|
.split("-")
|
||||||
|
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
||||||
|
.join(" ")
|
||||||
|
|
||||||
return <IconDetails icon={icon} iconData={originalIconData} authorData={authorData} />
|
const imageSchema = getIconSchema(
|
||||||
|
formattedIconName,
|
||||||
|
icon,
|
||||||
|
authorName,
|
||||||
|
authorData.html_url,
|
||||||
|
updateDate.toISOString(),
|
||||||
|
Object.keys(iconsData).length
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<StructuredData data={imageSchema} id="image-schema" />
|
||||||
|
<IconDetails icon={icon} iconData={originalIconData} authorData={authorData} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,87 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import { Input } from "@/components/ui/input"
|
|
||||||
import { BASE_URL } from "@/constants"
|
|
||||||
import type { IconSearchProps, IconWithName } from "@/types/icons"
|
|
||||||
import { Search } from "lucide-react"
|
|
||||||
import Image from "next/image"
|
|
||||||
import Link from "next/link"
|
|
||||||
import { useState } from "react"
|
|
||||||
|
|
||||||
export function IconSearch({ icons, initialQuery = "" }: IconSearchProps) {
|
|
||||||
const [searchQuery, setSearchQuery] = useState(initialQuery)
|
|
||||||
const [filteredIcons, setFilteredIcons] = useState<IconWithName[]>(() => {
|
|
||||||
if (!initialQuery.trim()) return icons
|
|
||||||
|
|
||||||
const q = initialQuery.toLowerCase()
|
|
||||||
return icons.filter(({ name, data }) => {
|
|
||||||
if (name.toLowerCase().includes(q)) return true
|
|
||||||
if (data.aliases.some((alias) => alias.toLowerCase().includes(q))) return true
|
|
||||||
if (data.categories.some((category) => category.toLowerCase().includes(q))) return true
|
|
||||||
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const handleSearch = (query: string) => {
|
|
||||||
setSearchQuery(query)
|
|
||||||
|
|
||||||
if (!query.trim()) {
|
|
||||||
setFilteredIcons(icons)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const q = query.toLowerCase()
|
|
||||||
const filtered = icons.filter(({ name, data }) => {
|
|
||||||
if (name.toLowerCase().includes(q)) return true
|
|
||||||
if (data.aliases.some((alias) => alias.toLowerCase().includes(q))) return true
|
|
||||||
if (data.categories.some((category) => category.toLowerCase().includes(q))) return true
|
|
||||||
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
|
|
||||||
setFilteredIcons(filtered)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="relative w-full max-w-md">
|
|
||||||
<Search className="absolute left-2.5 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground transition-all duration-300" />
|
|
||||||
<Input
|
|
||||||
type="search"
|
|
||||||
placeholder="Search icons by name, aliases, or categories..."
|
|
||||||
className="w-full pl-8 transition-all duration-300 text-sm md:text-base"
|
|
||||||
value={searchQuery}
|
|
||||||
onChange={(e) => handleSearch(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{filteredIcons.length === 0 ? (
|
|
||||||
<div className="text-center py-12">
|
|
||||||
<h2 className="text-xl font-semibold">No icons found</h2>
|
|
||||||
<p className="text-muted-foreground mt-2">Try a different search term.</p>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 xl:grid-cols-8 gap-4 mt-8">
|
|
||||||
{filteredIcons.map(({ name, data }) => (
|
|
||||||
<Link
|
|
||||||
key={name}
|
|
||||||
href={`/icons/${name}`}
|
|
||||||
className="group flex flex-col items-center p-4 rounded-lg border border-border hover:border-primary hover:bg-accent transition-colors"
|
|
||||||
>
|
|
||||||
<div className="relative h-16 w-16 mb-2">
|
|
||||||
<Image
|
|
||||||
src={`${BASE_URL}/${data.base}/${name}.${data.base}`}
|
|
||||||
alt={`${name} icon`}
|
|
||||||
fill
|
|
||||||
className="object-contain p-1 group-hover:scale-110 transition-transform"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<span className="text-sm text-center truncate w-full">{name.replace(/-/g, " ")}</span>
|
|
||||||
</Link>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -19,7 +19,7 @@ import { Input } from "@/components/ui/input"
|
|||||||
import { Separator } from "@/components/ui/separator"
|
import { Separator } from "@/components/ui/separator"
|
||||||
import { BASE_URL } from "@/constants"
|
import { BASE_URL } from "@/constants"
|
||||||
import type { Icon, IconSearchProps } from "@/types/icons"
|
import type { Icon, IconSearchProps } from "@/types/icons"
|
||||||
import { ArrowDownAZ, ArrowUpZA, Calendar, Filter, Search, SortAsc, X } from "lucide-react"
|
import { ArrowDownAZ, ArrowUpZA, Calendar, ChevronLeft, ChevronRight, Filter, Search, SortAsc, X } from "lucide-react"
|
||||||
import { useTheme } from "next-themes"
|
import { useTheme } from "next-themes"
|
||||||
import Image from "next/image"
|
import Image from "next/image"
|
||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
@@ -27,24 +27,82 @@ import { usePathname, useRouter, useSearchParams } from "next/navigation"
|
|||||||
import posthog from "posthog-js"
|
import posthog from "posthog-js"
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
|
||||||
import { toast } from "sonner"
|
import { toast } from "sonner"
|
||||||
|
import { motion, AnimatePresence } from "framer-motion"
|
||||||
|
|
||||||
type SortOption = "relevance" | "alphabetical-asc" | "alphabetical-desc" | "newest"
|
type SortOption = "relevance" | "alphabetical-asc" | "alphabetical-desc" | "newest"
|
||||||
|
|
||||||
|
// Get the display rows count based on viewport size
|
||||||
|
function getDefaultRowsPerPage() {
|
||||||
|
if (typeof window === "undefined") return 3; // Default for SSR
|
||||||
|
|
||||||
|
// Calculate based on viewport height and width
|
||||||
|
const vh = window.innerHeight;
|
||||||
|
const vw = window.innerWidth;
|
||||||
|
|
||||||
|
// Determine number of columns based on viewport width
|
||||||
|
let columns = 2; // Default for small screens (sm)
|
||||||
|
if (vw >= 1280) columns = 8; // xl breakpoint
|
||||||
|
else if (vw >= 1024) columns = 6; // lg breakpoint
|
||||||
|
else if (vw >= 768) columns = 4; // md breakpoint
|
||||||
|
else if (vw >= 640) columns = 3; // sm breakpoint
|
||||||
|
|
||||||
|
// Calculate rows (accounting for pagination UI space)
|
||||||
|
const rowHeight = 130; // Approximate height of each row in pixels
|
||||||
|
const availableHeight = vh * 0.6; // 60% of viewport height
|
||||||
|
|
||||||
|
// Ensure at least 1 row, maximum 5 rows
|
||||||
|
return Math.max(1, Math.min(5, Math.floor(availableHeight / rowHeight)));
|
||||||
|
}
|
||||||
|
|
||||||
export function IconSearch({ icons }: IconSearchProps) {
|
export function IconSearch({ icons }: IconSearchProps) {
|
||||||
const searchParams = useSearchParams()
|
const searchParams = useSearchParams()
|
||||||
const initialQuery = searchParams.get("q")
|
const initialQuery = searchParams.get("q")
|
||||||
const initialCategories = searchParams.getAll("category")
|
const initialCategories = searchParams.getAll("category")
|
||||||
const initialSort = (searchParams.get("sort") as SortOption) || "relevance"
|
const initialSort = (searchParams.get("sort") as SortOption) || "relevance"
|
||||||
|
const initialPage = Number(searchParams.get("page") || "1")
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
const [searchQuery, setSearchQuery] = useState(initialQuery ?? "")
|
const [searchQuery, setSearchQuery] = useState(initialQuery ?? "")
|
||||||
const [debouncedQuery, setDebouncedQuery] = useState(initialQuery ?? "")
|
const [debouncedQuery, setDebouncedQuery] = useState(initialQuery ?? "")
|
||||||
const [selectedCategories, setSelectedCategories] = useState<string[]>(initialCategories ?? [])
|
const [selectedCategories, setSelectedCategories] = useState<string[]>(initialCategories ?? [])
|
||||||
const [sortOption, setSortOption] = useState<SortOption>(initialSort)
|
const [sortOption, setSortOption] = useState<SortOption>(initialSort)
|
||||||
|
const [currentPage, setCurrentPage] = useState(initialPage)
|
||||||
|
const [iconsPerPage, setIconsPerPage] = useState(getDefaultRowsPerPage() * 8) // Default cols is 8 for xl screens
|
||||||
const timeoutRef = useRef<NodeJS.Timeout | null>(null)
|
const timeoutRef = useRef<NodeJS.Timeout | null>(null)
|
||||||
const { resolvedTheme } = useTheme()
|
const { resolvedTheme } = useTheme()
|
||||||
const [isLazyRequestSubmitted, setIsLazyRequestSubmitted] = useState(false)
|
const [isLazyRequestSubmitted, setIsLazyRequestSubmitted] = useState(false)
|
||||||
|
|
||||||
|
// Add resize observer to update iconsPerPage when window size changes
|
||||||
|
useEffect(() => {
|
||||||
|
const updateIconsPerPage = () => {
|
||||||
|
const rows = getDefaultRowsPerPage();
|
||||||
|
|
||||||
|
// Determine columns based on current viewport
|
||||||
|
const vw = window.innerWidth;
|
||||||
|
let columns = 2; // Default for small screens
|
||||||
|
if (vw >= 1280) columns = 8; // xl breakpoint
|
||||||
|
else if (vw >= 1024) columns = 6; // lg breakpoint
|
||||||
|
else if (vw >= 768) columns = 4; // md breakpoint
|
||||||
|
else if (vw >= 640) columns = 3; // sm breakpoint
|
||||||
|
|
||||||
|
setIconsPerPage(rows * columns);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initial setup
|
||||||
|
updateIconsPerPage();
|
||||||
|
|
||||||
|
// Add resize listener
|
||||||
|
window.addEventListener('resize', updateIconsPerPage);
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
return () => window.removeEventListener('resize', updateIconsPerPage);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Reset page when search parameters change
|
||||||
|
useEffect(() => {
|
||||||
|
setCurrentPage(1);
|
||||||
|
}, [debouncedQuery, selectedCategories, sortOption]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const timer = setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
setDebouncedQuery(searchQuery)
|
setDebouncedQuery(searchQuery)
|
||||||
@@ -138,7 +196,7 @@ export function IconSearch({ icons }: IconSearchProps) {
|
|||||||
}, [filterIcons, debouncedQuery, selectedCategories, sortOption])
|
}, [filterIcons, debouncedQuery, selectedCategories, sortOption])
|
||||||
|
|
||||||
const updateResults = useCallback(
|
const updateResults = useCallback(
|
||||||
(query: string, categories: string[], sort: SortOption) => {
|
(query: string, categories: string[], sort: SortOption, page = 1) => {
|
||||||
const params = new URLSearchParams()
|
const params = new URLSearchParams()
|
||||||
if (query) params.set("q", query)
|
if (query) params.set("q", query)
|
||||||
|
|
||||||
@@ -152,6 +210,11 @@ export function IconSearch({ icons }: IconSearchProps) {
|
|||||||
params.set("sort", sort)
|
params.set("sort", sort)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add page parameter if not the first page
|
||||||
|
if (page > 1) {
|
||||||
|
params.set("page", page.toString())
|
||||||
|
}
|
||||||
|
|
||||||
const newUrl = params.toString() ? `${pathname}?${params.toString()}` : pathname
|
const newUrl = params.toString() ? `${pathname}?${params.toString()}` : pathname
|
||||||
router.push(newUrl, { scroll: false })
|
router.push(newUrl, { scroll: false })
|
||||||
},
|
},
|
||||||
@@ -197,11 +260,20 @@ export function IconSearch({ icons }: IconSearchProps) {
|
|||||||
[updateResults, searchQuery, selectedCategories],
|
[updateResults, searchQuery, selectedCategories],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const handlePageChange = useCallback(
|
||||||
|
(page: number) => {
|
||||||
|
setCurrentPage(page);
|
||||||
|
updateResults(searchQuery, selectedCategories, sortOption, page);
|
||||||
|
},
|
||||||
|
[updateResults, searchQuery, selectedCategories, sortOption],
|
||||||
|
)
|
||||||
|
|
||||||
const clearFilters = useCallback(() => {
|
const clearFilters = useCallback(() => {
|
||||||
setSearchQuery("")
|
setSearchQuery("")
|
||||||
setSelectedCategories([])
|
setSelectedCategories([])
|
||||||
setSortOption("relevance")
|
setSortOption("relevance")
|
||||||
updateResults("", [], "relevance")
|
setCurrentPage(1)
|
||||||
|
updateResults("", [], "relevance", 1)
|
||||||
}, [updateResults])
|
}, [updateResults])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -228,11 +300,11 @@ export function IconSearch({ icons }: IconSearchProps) {
|
|||||||
const getSortLabel = (sort: SortOption) => {
|
const getSortLabel = (sort: SortOption) => {
|
||||||
switch (sort) {
|
switch (sort) {
|
||||||
case "relevance":
|
case "relevance":
|
||||||
return "Best match"
|
return "Relevance"
|
||||||
case "alphabetical-asc":
|
case "alphabetical-asc":
|
||||||
return "A to Z"
|
return "Name (A-Z)"
|
||||||
case "alphabetical-desc":
|
case "alphabetical-desc":
|
||||||
return "Z to A"
|
return "Name (Z-A)"
|
||||||
case "newest":
|
case "newest":
|
||||||
return "Newest first"
|
return "Newest first"
|
||||||
default:
|
default:
|
||||||
@@ -265,7 +337,7 @@ export function IconSearch({ icons }: IconSearchProps) {
|
|||||||
</div>
|
</div>
|
||||||
<Input
|
<Input
|
||||||
type="search"
|
type="search"
|
||||||
placeholder="Search icons by name, alias, or category..."
|
placeholder="Search for icons..."
|
||||||
className="w-full h-10 pl-9 cursor-text transition-all duration-300 text-sm md:text-base border-border shadow-sm"
|
className="w-full h-10 pl-9 cursor-text transition-all duration-300 text-sm md:text-base border-border shadow-sm"
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={(e) => handleSearch(e.target.value)}
|
onChange={(e) => handleSearch(e.target.value)}
|
||||||
@@ -277,18 +349,18 @@ export function IconSearch({ icons }: IconSearchProps) {
|
|||||||
{/* Filter dropdown */}
|
{/* Filter dropdown */}
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button variant="outline" size="sm" className="flex-1 sm:flex-none cursor-pointer bg-background border-border shadow-sm ">
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
className="flex-1 sm:flex-none cursor-pointer bg-background border-border shadow-sm"
|
||||||
|
aria-label="Filter icons"
|
||||||
|
>
|
||||||
<Filter className="h-4 w-4 mr-2" />
|
<Filter className="h-4 w-4 mr-2" />
|
||||||
<span>Filter</span>
|
<span>{selectedCategories.length > 0 ? `Filters (${selectedCategories.length})` : "Filter"}</span>
|
||||||
{selectedCategories.length > 0 && (
|
|
||||||
<Badge variant="secondary" className="ml-2 px-1.5">
|
|
||||||
{selectedCategories.length}
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="start" className="w-64 sm:w-56">
|
<DropdownMenuContent align="start" className="w-64 sm:w-56">
|
||||||
<DropdownMenuLabel className="font-semibold">Categories</DropdownMenuLabel>
|
<DropdownMenuLabel className="font-semibold">Select Categories</DropdownMenuLabel>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
|
|
||||||
<div className="max-h-[40vh] overflow-y-auto p-1">
|
<div className="max-h-[40vh] overflow-y-auto p-1">
|
||||||
@@ -314,7 +386,7 @@ export function IconSearch({ icons }: IconSearchProps) {
|
|||||||
}}
|
}}
|
||||||
className="cursor-pointer focus: focus:bg-rose-50 dark:focus:bg-rose-950/20"
|
className="cursor-pointer focus: focus:bg-rose-50 dark:focus:bg-rose-950/20"
|
||||||
>
|
>
|
||||||
Clear all filters
|
Clear categories
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -330,18 +402,18 @@ export function IconSearch({ icons }: IconSearchProps) {
|
|||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="start" className="w-56">
|
<DropdownMenuContent align="start" className="w-56">
|
||||||
<DropdownMenuLabel className="font-semibold">Sort By</DropdownMenuLabel>
|
<DropdownMenuLabel className="font-semibold">Sort Icons</DropdownMenuLabel>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<DropdownMenuRadioGroup value={sortOption} onValueChange={(value) => handleSortChange(value as SortOption)}>
|
<DropdownMenuRadioGroup value={sortOption} onValueChange={(value) => handleSortChange(value as SortOption)}>
|
||||||
<DropdownMenuRadioItem value="relevance" className="cursor-pointer">
|
<DropdownMenuRadioItem value="relevance" className="cursor-pointer">
|
||||||
<Search className="h-4 w-4 mr-2" />
|
<Search className="h-4 w-4 mr-2" />
|
||||||
Best match
|
Relevance
|
||||||
</DropdownMenuRadioItem>
|
</DropdownMenuRadioItem>
|
||||||
<DropdownMenuRadioItem value="alphabetical-asc" className="cursor-pointer">
|
<DropdownMenuRadioItem value="alphabetical-asc" className="cursor-pointer">
|
||||||
<ArrowDownAZ className="h-4 w-4 mr-2" />A to Z
|
<ArrowDownAZ className="h-4 w-4 mr-2" />Name (A-Z)
|
||||||
</DropdownMenuRadioItem>
|
</DropdownMenuRadioItem>
|
||||||
<DropdownMenuRadioItem value="alphabetical-desc" className="cursor-pointer">
|
<DropdownMenuRadioItem value="alphabetical-desc" className="cursor-pointer">
|
||||||
<ArrowUpZA className="h-4 w-4 mr-2" />Z to A
|
<ArrowUpZA className="h-4 w-4 mr-2" />Name (Z-A)
|
||||||
</DropdownMenuRadioItem>
|
</DropdownMenuRadioItem>
|
||||||
<DropdownMenuRadioItem value="newest" className="cursor-pointer">
|
<DropdownMenuRadioItem value="newest" className="cursor-pointer">
|
||||||
<Calendar className="h-4 w-4 mr-2" />
|
<Calendar className="h-4 w-4 mr-2" />
|
||||||
@@ -353,9 +425,15 @@ export function IconSearch({ icons }: IconSearchProps) {
|
|||||||
|
|
||||||
{/* Clear all button */}
|
{/* Clear all button */}
|
||||||
{(searchQuery || selectedCategories.length > 0 || sortOption !== "relevance") && (
|
{(searchQuery || selectedCategories.length > 0 || sortOption !== "relevance") && (
|
||||||
<Button variant="outline" size="sm" onClick={clearFilters} className="flex-1 sm:flex-none cursor-pointer bg-background">
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={clearFilters}
|
||||||
|
className="flex-1 sm:flex-none cursor-pointer bg-background"
|
||||||
|
aria-label="Reset all filters"
|
||||||
|
>
|
||||||
<X className="h-4 w-4 mr-2" />
|
<X className="h-4 w-4 mr-2" />
|
||||||
<span>Clear all</span>
|
<span>Reset</span>
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -363,7 +441,7 @@ export function IconSearch({ icons }: IconSearchProps) {
|
|||||||
{/* Active filter badges */}
|
{/* Active filter badges */}
|
||||||
{selectedCategories.length > 0 && (
|
{selectedCategories.length > 0 && (
|
||||||
<div className="flex flex-wrap items-center gap-2 mt-2">
|
<div className="flex flex-wrap items-center gap-2 mt-2">
|
||||||
<span className="text-sm text-muted-foreground">Filters:</span>
|
<span className="text-sm text-muted-foreground">Selected:</span>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{selectedCategories.map((category) => (
|
{selectedCategories.map((category) => (
|
||||||
<Badge key={category} variant="secondary" className="flex items-center gap-1 pl-2 pr-1">
|
<Badge key={category} variant="secondary" className="flex items-center gap-1 pl-2 pr-1">
|
||||||
@@ -389,7 +467,7 @@ export function IconSearch({ icons }: IconSearchProps) {
|
|||||||
}}
|
}}
|
||||||
className="text-xs h-7 px-2 cursor-pointer"
|
className="text-xs h-7 px-2 cursor-pointer"
|
||||||
>
|
>
|
||||||
Clear all
|
Clear
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -400,27 +478,33 @@ export function IconSearch({ icons }: IconSearchProps) {
|
|||||||
{filteredIcons.length === 0 ? (
|
{filteredIcons.length === 0 ? (
|
||||||
<div className="flex flex-col gap-8 py-12 max-w-2xl mx-auto items-center">
|
<div className="flex flex-col gap-8 py-12 max-w-2xl mx-auto items-center">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<h2 className="text-3xl sm:text-5xl font-semibold">We don't have this one...yet!</h2>
|
<h2 className="text-3xl sm:text-5xl font-semibold">Icon not found</h2>
|
||||||
|
<p className="text-lg text-muted-foreground mt-2">Help us expand our collection</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-4 items-center w-full">
|
||||||
|
<IconSubmissionContent />
|
||||||
|
<div className="mt-4 flex items-center gap-2 justify-center">
|
||||||
|
<span className="text-sm text-muted-foreground">Can't submit it yourself?</span>
|
||||||
|
<Button
|
||||||
|
className="cursor-pointer"
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => {
|
||||||
|
setIsLazyRequestSubmitted(true)
|
||||||
|
toast("Request received!", {
|
||||||
|
description: `We've noted your request for "${searchQuery || "this icon"}". Thanks for your suggestion.`,
|
||||||
|
})
|
||||||
|
posthog.capture("lazy icon request", {
|
||||||
|
query: searchQuery,
|
||||||
|
categories: selectedCategories,
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
disabled={isLazyRequestSubmitted}
|
||||||
|
>
|
||||||
|
Request this icon
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
|
||||||
className="cursor-pointer motion-preset-pop"
|
|
||||||
variant="default"
|
|
||||||
size="lg"
|
|
||||||
onClick={() => {
|
|
||||||
setIsLazyRequestSubmitted(true)
|
|
||||||
toast("We hear you!", {
|
|
||||||
description: `Okay, okay... we'll consider adding "${searchQuery || "that icon"}" just for you. 😉`,
|
|
||||||
})
|
|
||||||
posthog.capture("lazy icon request", {
|
|
||||||
query: searchQuery,
|
|
||||||
categories: selectedCategories,
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
disabled={isLazyRequestSubmitted}
|
|
||||||
>
|
|
||||||
I want this icon added but I'm too lazy to add it myself
|
|
||||||
</Button>
|
|
||||||
<IconSubmissionContent />
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
@@ -435,7 +519,14 @@ export function IconSearch({ icons }: IconSearchProps) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<IconsGrid filteredIcons={filteredIcons} matchedAliases={matchedAliases} />
|
<IconsGrid
|
||||||
|
filteredIcons={filteredIcons}
|
||||||
|
matchedAliases={matchedAliases}
|
||||||
|
currentPage={currentPage}
|
||||||
|
iconsPerPage={iconsPerPage}
|
||||||
|
onPageChange={handlePageChange}
|
||||||
|
totalIcons={filteredIcons.length}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
@@ -445,15 +536,13 @@ export function IconSearch({ icons }: IconSearchProps) {
|
|||||||
function IconCard({
|
function IconCard({
|
||||||
name,
|
name,
|
||||||
data: iconData,
|
data: iconData,
|
||||||
matchedAlias,
|
|
||||||
}: {
|
}: {
|
||||||
name: string
|
name: string
|
||||||
data: Icon
|
data: Icon
|
||||||
matchedAlias?: string | null
|
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<MagicCard className="rounded-md shadow-md">
|
<MagicCard className="rounded-md shadow-md cursor-pointer">
|
||||||
<Link prefetch={false} href={`/icons/${name}`} className="group flex flex-col items-center p-3 sm:p-4 cursor-pointer">
|
<Link prefetch={false} href={`/icons/${name}`} className="group flex flex-col items-center p-3 sm:p-4">
|
||||||
<div className="relative h-12 w-12 sm:h-16 sm:w-16 mb-2">
|
<div className="relative h-12 w-12 sm:h-16 sm:w-16 mb-2">
|
||||||
<Image
|
<Image
|
||||||
src={`${BASE_URL}/${iconData.base}/${name}.${iconData.base}`}
|
src={`${BASE_URL}/${iconData.base}/${name}.${iconData.base}`}
|
||||||
@@ -462,11 +551,9 @@ function IconCard({
|
|||||||
className="object-contain p-1 group-hover:scale-110 transition-transform duration-300"
|
className="object-contain p-1 group-hover:scale-110 transition-transform duration-300"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-xs sm:text-sm text-center truncate w-full capitalize group- dark:group-hover:text-rose-400 transition-colors duration-200 font-medium">
|
<span className="text-xs sm:text-sm text-center truncate w-full capitalize group-hover:text-rose-500 dark:group-hover:text-rose-400 transition-colors duration-200 font-medium">
|
||||||
{name.replace(/-/g, " ")}
|
{name.replace(/-/g, " ")}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
{matchedAlias && <span className="text-[10px] text-center truncate w-full mt-1">Alias: {matchedAlias}</span>}
|
|
||||||
</Link>
|
</Link>
|
||||||
</MagicCard>
|
</MagicCard>
|
||||||
)
|
)
|
||||||
@@ -475,17 +562,253 @@ function IconCard({
|
|||||||
interface IconsGridProps {
|
interface IconsGridProps {
|
||||||
filteredIcons: { name: string; data: Icon }[]
|
filteredIcons: { name: string; data: Icon }[]
|
||||||
matchedAliases: Record<string, string>
|
matchedAliases: Record<string, string>
|
||||||
|
currentPage: number
|
||||||
|
iconsPerPage: number
|
||||||
|
onPageChange: (page: number) => void
|
||||||
|
totalIcons: number
|
||||||
}
|
}
|
||||||
|
|
||||||
function IconsGrid({ filteredIcons, matchedAliases }: IconsGridProps) {
|
function IconsGrid({ filteredIcons, matchedAliases, currentPage, iconsPerPage, onPageChange, totalIcons }: IconsGridProps) {
|
||||||
|
// Calculate pagination values
|
||||||
|
const totalPages = Math.ceil(totalIcons / iconsPerPage)
|
||||||
|
const indexOfLastIcon = currentPage * iconsPerPage
|
||||||
|
const indexOfFirstIcon = indexOfLastIcon - iconsPerPage
|
||||||
|
const currentIcons = filteredIcons.slice(indexOfFirstIcon, indexOfLastIcon)
|
||||||
|
|
||||||
|
// Calculate letter ranges for each page
|
||||||
|
const getLetterRange = (pageNum: number) => {
|
||||||
|
if (filteredIcons.length === 0) return '';
|
||||||
|
const start = (pageNum - 1) * iconsPerPage;
|
||||||
|
const end = Math.min(start + iconsPerPage - 1, filteredIcons.length - 1);
|
||||||
|
|
||||||
|
if (start >= filteredIcons.length) return '';
|
||||||
|
|
||||||
|
const firstLetter = filteredIcons[start].name.charAt(0).toUpperCase();
|
||||||
|
const lastLetter = filteredIcons[end].name.charAt(0).toUpperCase();
|
||||||
|
|
||||||
|
return firstLetter === lastLetter ? firstLetter : `${firstLetter} - ${lastLetter}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get current page letter range
|
||||||
|
const currentLetterRange = getLetterRange(currentPage);
|
||||||
|
|
||||||
|
// Handle direct page input
|
||||||
|
const [pageInput, setPageInput] = useState(currentPage.toString());
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setPageInput(currentPage.toString());
|
||||||
|
}, [currentPage]);
|
||||||
|
|
||||||
|
const handlePageInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setPageInput(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePageInputSubmit = (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const pageNumber = parseInt(pageInput);
|
||||||
|
if (!isNaN(pageNumber) && pageNumber >= 1 && pageNumber <= totalPages) {
|
||||||
|
onPageChange(pageNumber);
|
||||||
|
} else {
|
||||||
|
// Reset to current page if invalid
|
||||||
|
setPageInput(currentPage.toString());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 xl:grid-cols-8 gap-4 mt-2">
|
<AnimatePresence mode="wait">
|
||||||
{filteredIcons.slice(0, 120).map(({ name, data }) => (
|
<motion.div
|
||||||
<IconCard key={name} name={name} data={data} matchedAlias={matchedAliases[name] || null} />
|
key={currentPage}
|
||||||
))}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
</div>
|
animate={{ opacity: 1, y: 0 }}
|
||||||
{filteredIcons.length > 120 && <p className="text-sm text-muted-foreground">And {filteredIcons.length - 120} more...</p>}
|
exit={{ opacity: 0, y: -20 }}
|
||||||
|
transition={{ duration: 0.3 }}
|
||||||
|
className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 xl:grid-cols-8 gap-4 mt-2"
|
||||||
|
>
|
||||||
|
{currentIcons.map(({ name, data }) => (
|
||||||
|
<IconCard key={name} name={name} data={data} />
|
||||||
|
))}
|
||||||
|
</motion.div>
|
||||||
|
</AnimatePresence>
|
||||||
|
|
||||||
|
{totalPages > 1 && (
|
||||||
|
<div className="flex flex-col gap-4 mt-8">
|
||||||
|
{/* Mobile view: centered content */}
|
||||||
|
<div className="text-sm text-muted-foreground text-center md:text-left md:hidden">
|
||||||
|
Showing {indexOfFirstIcon + 1}-{Math.min(indexOfLastIcon, totalIcons)} of {totalIcons} icons
|
||||||
|
{currentLetterRange && (
|
||||||
|
<span className="ml-2 font-medium">({currentLetterRange})</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Desktop view layout */}
|
||||||
|
<div className="hidden md:flex justify-between items-center">
|
||||||
|
<div className="text-sm text-muted-foreground">
|
||||||
|
Showing {indexOfFirstIcon + 1}-{Math.min(indexOfLastIcon, totalIcons)} of {totalIcons} icons
|
||||||
|
{currentLetterRange && (
|
||||||
|
<span className="ml-2 font-medium">({currentLetterRange})</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
{/* Page input and total count */}
|
||||||
|
<form onSubmit={handlePageInputSubmit} className="flex items-center gap-2">
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
min={1}
|
||||||
|
max={totalPages}
|
||||||
|
value={pageInput}
|
||||||
|
onChange={handlePageInputChange}
|
||||||
|
className="w-16 h-8 text-center cursor-text"
|
||||||
|
aria-label="Go to page"
|
||||||
|
/>
|
||||||
|
<span className="text-sm whitespace-nowrap">of {totalPages}</span>
|
||||||
|
<Button type="submit" size="sm" variant="outline" className="h-8 cursor-pointer">Go</Button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{/* Pagination controls */}
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Button
|
||||||
|
onClick={() => onPageChange(currentPage - 1)}
|
||||||
|
disabled={currentPage === 1}
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
className="h-8 rounded-r-none cursor-pointer"
|
||||||
|
aria-label="Previous page"
|
||||||
|
>
|
||||||
|
<ChevronLeft className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<div className="flex items-center overflow-hidden">
|
||||||
|
{Array.from({ length: Math.min(5, totalPages) }, (_, i) => {
|
||||||
|
// Show pages around current page
|
||||||
|
let pageNum;
|
||||||
|
if (totalPages <= 5) {
|
||||||
|
pageNum = i + 1;
|
||||||
|
} else if (currentPage <= 3) {
|
||||||
|
pageNum = i + 1;
|
||||||
|
} else if (currentPage >= totalPages - 2) {
|
||||||
|
pageNum = totalPages - 4 + i;
|
||||||
|
} else {
|
||||||
|
pageNum = currentPage - 2 + i;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate letter range for this page
|
||||||
|
const letterRange = getLetterRange(pageNum);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
key={pageNum}
|
||||||
|
onClick={() => onPageChange(pageNum)}
|
||||||
|
variant={pageNum === currentPage ? "default" : "outline"}
|
||||||
|
size="sm"
|
||||||
|
className={`h-8 w-8 p-0 rounded-none relative group cursor-pointer transition-colors duration-200 ${
|
||||||
|
pageNum === currentPage ? "font-medium" : ""
|
||||||
|
}`}
|
||||||
|
aria-label={`Page ${pageNum}`}
|
||||||
|
aria-current={pageNum === currentPage ? "page" : undefined}
|
||||||
|
>
|
||||||
|
{pageNum}
|
||||||
|
{letterRange && (
|
||||||
|
<span className="absolute -top-8 left-1/2 transform -translate-x-1/2 bg-popover text-popover-foreground px-2 py-1 rounded text-xs opacity-0 group-hover:opacity-100 transition-opacity shadow-md whitespace-nowrap">
|
||||||
|
{letterRange}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onClick={() => onPageChange(currentPage + 1)}
|
||||||
|
disabled={currentPage === totalPages}
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
className="h-8 rounded-l-none cursor-pointer"
|
||||||
|
aria-label="Next page"
|
||||||
|
>
|
||||||
|
<ChevronRight className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Mobile-only pagination layout - centered */}
|
||||||
|
<div className="flex flex-col items-center gap-4 md:hidden">
|
||||||
|
{/* Mobile pagination controls */}
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Button
|
||||||
|
onClick={() => onPageChange(currentPage - 1)}
|
||||||
|
disabled={currentPage === 1}
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
className="h-8 rounded-r-none cursor-pointer"
|
||||||
|
aria-label="Previous page"
|
||||||
|
>
|
||||||
|
<ChevronLeft className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<div className="flex items-center overflow-hidden">
|
||||||
|
{Array.from({ length: Math.min(5, totalPages) }, (_, i) => {
|
||||||
|
// Show pages around current page - same logic as desktop
|
||||||
|
let pageNum;
|
||||||
|
if (totalPages <= 5) {
|
||||||
|
pageNum = i + 1;
|
||||||
|
} else if (currentPage <= 3) {
|
||||||
|
pageNum = i + 1;
|
||||||
|
} else if (currentPage >= totalPages - 2) {
|
||||||
|
pageNum = totalPages - 4 + i;
|
||||||
|
} else {
|
||||||
|
pageNum = currentPage - 2 + i;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
key={pageNum}
|
||||||
|
onClick={() => onPageChange(pageNum)}
|
||||||
|
variant={pageNum === currentPage ? "default" : "outline"}
|
||||||
|
size="sm"
|
||||||
|
className={`h-8 w-8 p-0 rounded-none cursor-pointer ${
|
||||||
|
pageNum === currentPage ? "font-medium" : ""
|
||||||
|
}`}
|
||||||
|
aria-label={`Page ${pageNum}`}
|
||||||
|
aria-current={pageNum === currentPage ? "page" : undefined}
|
||||||
|
>
|
||||||
|
{pageNum}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onClick={() => onPageChange(currentPage + 1)}
|
||||||
|
disabled={currentPage === totalPages}
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
className="h-8 rounded-l-none cursor-pointer"
|
||||||
|
aria-label="Next page"
|
||||||
|
>
|
||||||
|
<ChevronRight className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Mobile page input */}
|
||||||
|
<form onSubmit={handlePageInputSubmit} className="flex items-center gap-2">
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
min={1}
|
||||||
|
max={totalPages}
|
||||||
|
value={pageInput}
|
||||||
|
onChange={handlePageInputChange}
|
||||||
|
className="w-16 h-8 text-center cursor-text"
|
||||||
|
aria-label="Go to page"
|
||||||
|
/>
|
||||||
|
<span className="text-sm whitespace-nowrap">of {totalPages}</span>
|
||||||
|
<Button type="submit" size="sm" variant="outline" className="h-8 cursor-pointer">Go</Button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,49 +1,39 @@
|
|||||||
import { BASE_URL } from "@/constants"
|
import { BASE_URL, BROWSE_KEYWORDS, DEFAULT_OG_IMAGE, GITHUB_URL, ORGANIZATION_NAME, ORGANIZATION_SCHEMA, SITE_NAME, SITE_TAGLINE, TITLE_SEPARATOR, WEB_URL, getBrowseDescription } from "@/constants"
|
||||||
import { getIconsArray } from "@/lib/api"
|
import { getIconsArray } from "@/lib/api"
|
||||||
import type { Metadata } from "next"
|
import type { Metadata } from "next"
|
||||||
import { IconSearch } from "./components/icon-search"
|
import { IconSearch } from "./components/icon-search"
|
||||||
|
import { StructuredData } from "@/components/structured-data"
|
||||||
|
|
||||||
export async function generateMetadata(): Promise<Metadata> {
|
export async function generateMetadata(): Promise<Metadata> {
|
||||||
const icons = await getIconsArray()
|
const icons = await getIconsArray()
|
||||||
const totalIcons = icons.length
|
const totalIcons = icons.length
|
||||||
|
|
||||||
|
const title = `Browse Icons ${TITLE_SEPARATOR} ${SITE_NAME}`
|
||||||
|
const description = getBrowseDescription(totalIcons)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: "Browse Icons | Free Dashboard Icons",
|
title,
|
||||||
description: `Search and browse through our collection of ${totalIcons} curated icons for services, applications and tools, designed specifically for dashboards and app directories.`,
|
description,
|
||||||
keywords: [
|
keywords: BROWSE_KEYWORDS,
|
||||||
"browse icons",
|
|
||||||
"dashboard icons",
|
|
||||||
"icon search",
|
|
||||||
"service icons",
|
|
||||||
"application icons",
|
|
||||||
"tool icons",
|
|
||||||
"web dashboard",
|
|
||||||
"app directory",
|
|
||||||
],
|
|
||||||
openGraph: {
|
openGraph: {
|
||||||
title: "Browse Icons | Free Dashboard Icons",
|
title: `Browse Icons ${TITLE_SEPARATOR} ${SITE_NAME} ${TITLE_SEPARATOR} ${SITE_TAGLINE}`,
|
||||||
description: `Search and browse through our collection of ${totalIcons} curated icons for services, applications and tools, designed specifically for dashboards and app directories.`,
|
description,
|
||||||
type: "website",
|
type: "website",
|
||||||
url: `${BASE_URL}/icons`,
|
url: `${WEB_URL}/icons`,
|
||||||
images: [
|
images: [DEFAULT_OG_IMAGE],
|
||||||
{
|
|
||||||
url: "/og-image.png",
|
|
||||||
width: 1200,
|
|
||||||
height: 630,
|
|
||||||
alt: "Browse Dashboard Icons Collection",
|
|
||||||
type: "image/png",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
twitter: {
|
twitter: {
|
||||||
card: "summary_large_image",
|
card: "summary_large_image",
|
||||||
title: "Browse Icons | Free Dashboard Icons",
|
title: `Browse Icons ${TITLE_SEPARATOR} ${SITE_NAME} ${TITLE_SEPARATOR} ${SITE_TAGLINE}`,
|
||||||
description: `Search and browse through our collection of ${totalIcons} curated icons for services, applications and tools, designed specifically for dashboards and app directories.`,
|
description,
|
||||||
images: ["/og-image-browse.png"],
|
images: [DEFAULT_OG_IMAGE.url],
|
||||||
},
|
},
|
||||||
alternates: {
|
alternates: {
|
||||||
canonical: `${BASE_URL}/icons`,
|
canonical: `${WEB_URL}/icons`,
|
||||||
},
|
},
|
||||||
|
other: {
|
||||||
|
"revisit-after": "3 days",
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,20 +41,38 @@ export const dynamic = "force-static"
|
|||||||
|
|
||||||
export default async function IconsPage() {
|
export default async function IconsPage() {
|
||||||
const icons = await getIconsArray()
|
const icons = await getIconsArray()
|
||||||
return (
|
|
||||||
<div className="isolate overflow-hidden">
|
|
||||||
<div className="py-8">
|
|
||||||
<div className="space-y-4 mb-8 mx-auto max-w-7xl">
|
|
||||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
|
||||||
<div>
|
|
||||||
<h1 className="text-3xl font-bold">Browse icons</h1>
|
|
||||||
<p className="text-muted-foreground">Search through our collection of {icons.length} beautiful icons.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<IconSearch icons={icons} />
|
const gallerySchema = {
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "ImageGallery",
|
||||||
|
"name": `${SITE_NAME} - Browse ${icons.length} Icons - ${SITE_TAGLINE}`,
|
||||||
|
"description": getBrowseDescription(icons.length),
|
||||||
|
"url": `${WEB_URL}/icons`,
|
||||||
|
"numberOfItems": icons.length,
|
||||||
|
"creator": {
|
||||||
|
"@type": "Organization",
|
||||||
|
"name": ORGANIZATION_NAME,
|
||||||
|
"url": GITHUB_URL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<StructuredData data={gallerySchema} id="gallery-schema" />
|
||||||
|
<div className="isolate overflow-hidden">
|
||||||
|
<div className="py-8">
|
||||||
|
<div className="space-y-4 mb-8 mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
||||||
|
<div>
|
||||||
|
<h1 className="text-3xl font-bold">Icons</h1>
|
||||||
|
<p className="text-muted-foreground">Search our collection of {icons.length} icons - {SITE_TAGLINE}.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<IconSearch icons={icons} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,13 @@ import { PostHogProvider } from "@/components/PostHogProvider"
|
|||||||
import { Footer } from "@/components/footer"
|
import { Footer } from "@/components/footer"
|
||||||
import { HeaderWrapper } from "@/components/header-wrapper"
|
import { HeaderWrapper } from "@/components/header-wrapper"
|
||||||
import { LicenseNotice } from "@/components/license-notice"
|
import { LicenseNotice } from "@/components/license-notice"
|
||||||
|
import { WebsiteStructuredData } from "@/components/structured-data"
|
||||||
import { getTotalIcons } from "@/lib/api"
|
import { getTotalIcons } from "@/lib/api"
|
||||||
import type { Metadata, Viewport } from "next"
|
import type { Metadata, Viewport } from "next"
|
||||||
import { Inter } from "next/font/google"
|
import { Inter } from "next/font/google"
|
||||||
import { Toaster } from "sonner"
|
import { Toaster } from "sonner"
|
||||||
import "./globals.css"
|
import "./globals.css"
|
||||||
import { getDescription, websiteTitle } from "@/constants"
|
import { DEFAULT_KEYWORDS, DEFAULT_OG_IMAGE, GITHUB_URL, ORGANIZATION_NAME, ORGANIZATION_SCHEMA, SITE_NAME, SITE_TAGLINE, WEB_URL, getDescription, getWebsiteSchema, websiteFullTitle, websiteTitle } from "@/constants"
|
||||||
import { ThemeProvider } from "./theme-provider"
|
import { ThemeProvider } from "./theme-provider"
|
||||||
|
|
||||||
const inter = Inter({
|
const inter = Inter({
|
||||||
@@ -27,12 +28,13 @@ export const viewport: Viewport = {
|
|||||||
|
|
||||||
export async function generateMetadata(): Promise<Metadata> {
|
export async function generateMetadata(): Promise<Metadata> {
|
||||||
const { totalIcons } = await getTotalIcons()
|
const { totalIcons } = await getTotalIcons()
|
||||||
|
const description = getDescription(totalIcons)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
metadataBase: new URL("https://dashboardicons.com"),
|
metadataBase: new URL(WEB_URL),
|
||||||
title: websiteTitle,
|
title: websiteTitle,
|
||||||
description: getDescription(totalIcons),
|
description,
|
||||||
keywords: ["dashboard icons", "service icons", "application icons", "tool icons", "web dashboard", "app directory"],
|
keywords: DEFAULT_KEYWORDS,
|
||||||
robots: {
|
robots: {
|
||||||
index: true,
|
index: true,
|
||||||
follow: true,
|
follow: true,
|
||||||
@@ -42,33 +44,23 @@ export async function generateMetadata(): Promise<Metadata> {
|
|||||||
googleBot: "index, follow",
|
googleBot: "index, follow",
|
||||||
},
|
},
|
||||||
openGraph: {
|
openGraph: {
|
||||||
siteName: "Dashboard Icons",
|
siteName: SITE_NAME,
|
||||||
type: "website",
|
type: "website",
|
||||||
locale: "en_US",
|
locale: "en_US",
|
||||||
title: websiteTitle,
|
title: websiteFullTitle,
|
||||||
description: getDescription(totalIcons),
|
description,
|
||||||
url: "https://dashboardicons.com",
|
url: WEB_URL,
|
||||||
images: [
|
images: [DEFAULT_OG_IMAGE],
|
||||||
{
|
|
||||||
url: "/og-image.png",
|
|
||||||
width: 1200,
|
|
||||||
height: 630,
|
|
||||||
alt: "Dashboard Icons",
|
|
||||||
type: "image/png",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
twitter: {
|
twitter: {
|
||||||
card: "summary_large_image",
|
card: "summary_large_image",
|
||||||
site: "@homarr_app",
|
title: websiteFullTitle,
|
||||||
creator: "@homarr_app",
|
description,
|
||||||
title: websiteTitle,
|
images: [DEFAULT_OG_IMAGE.url],
|
||||||
description: getDescription(totalIcons),
|
|
||||||
images: ["/og-image.png"],
|
|
||||||
},
|
},
|
||||||
applicationName: "Dashboard Icons",
|
applicationName: SITE_NAME,
|
||||||
appleWebApp: {
|
appleWebApp: {
|
||||||
title: "Dashboard Icons",
|
title: SITE_NAME,
|
||||||
statusBarStyle: "default",
|
statusBarStyle: "default",
|
||||||
capable: true,
|
capable: true,
|
||||||
},
|
},
|
||||||
@@ -88,14 +80,29 @@ export async function generateMetadata(): Promise<Metadata> {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
manifest: "/site.webmanifest",
|
manifest: "/site.webmanifest",
|
||||||
|
authors: [{ name: ORGANIZATION_NAME, url: GITHUB_URL }],
|
||||||
|
creator: ORGANIZATION_NAME,
|
||||||
|
publisher: ORGANIZATION_NAME,
|
||||||
|
category: "Icons",
|
||||||
|
classification: "Dashboard Design Resources",
|
||||||
|
other: {
|
||||||
|
"revisit-after": "7 days",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
|
export default async function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
|
||||||
|
const { totalIcons } = await getTotalIcons()
|
||||||
|
const websiteSchema = getWebsiteSchema(totalIcons)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<html lang="en" suppressHydrationWarning>
|
<html lang="en" suppressHydrationWarning>
|
||||||
<body className={`${inter.variable} antialiased bg-background flex flex-col min-h-screen`}>
|
<body className={`${inter.variable} antialiased bg-background flex flex-col min-h-screen`}>
|
||||||
<PostHogProvider>
|
<PostHogProvider>
|
||||||
|
<WebsiteStructuredData
|
||||||
|
websiteSchema={websiteSchema}
|
||||||
|
organizationSchema={ORGANIZATION_SCHEMA}
|
||||||
|
/>
|
||||||
<ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>
|
<ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>
|
||||||
<HeaderWrapper />
|
<HeaderWrapper />
|
||||||
<main className="flex-grow">{children}</main>
|
<main className="flex-grow">{children}</main>
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ export default function NotFound({
|
|||||||
<div className="mx-auto w-16 h-16 bg-red-100 dark:bg-red-900/20 rounded-full flex items-center justify-center text-red-600 dark:text-red-400">
|
<div className="mx-auto w-16 h-16 bg-red-100 dark:bg-red-900/20 rounded-full flex items-center justify-center text-red-600 dark:text-red-400">
|
||||||
<AlertTriangle className="w-8 h-8" />
|
<AlertTriangle className="w-8 h-8" />
|
||||||
</div>
|
</div>
|
||||||
<h1 className="text-2xl sm:text-3xl font-bold mt-6">Icon not found</h1>
|
<h1 className="text-2xl sm:text-3xl font-bold mt-6">Not found</h1>
|
||||||
<p className="text-muted-foreground mt-3 max-w-md">
|
<p className="text-muted-foreground mt-3 max-w-md">
|
||||||
The icon you are looking for could not be found or there was an error loading it.
|
This icon does not exist or could not be loaded.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -25,16 +25,16 @@ export default function NotFound({
|
|||||||
<Button asChild variant="outline">
|
<Button asChild variant="outline">
|
||||||
<Link href="/icons">
|
<Link href="/icons">
|
||||||
<ArrowLeft className="mr-2 h-4 w-4" />
|
<ArrowLeft className="mr-2 h-4 w-4" />
|
||||||
Back to all icons
|
Back to icons
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="border-t border-border pt-8 mt-8">
|
<div className="border-t border-border pt-8 mt-8">
|
||||||
<div className="text-center mb-6">
|
<div className="text-center mb-6">
|
||||||
<h2 className="text-xl font-semibold">Can't find what you're looking for?</h2>
|
<h2 className="text-xl font-semibold">Missing an icon?</h2>
|
||||||
<p className="text-muted-foreground mt-2">
|
<p className="text-muted-foreground mt-2">
|
||||||
Contribute to our icon collection by suggesting a new icon or improving an existing one.
|
Submit a new icon or suggest improvements to our collection.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,42 +1,37 @@
|
|||||||
import { HeroSection } from "@/components/hero"
|
import { HeroSection } from "@/components/hero"
|
||||||
import { RecentlyAddedIcons } from "@/components/recently-added-icons"
|
import { RecentlyAddedIcons } from "@/components/recently-added-icons"
|
||||||
import { BASE_URL, REPO_NAME, getDescription, websiteTitle } from "@/constants"
|
import { StructuredData } from "@/components/structured-data"
|
||||||
|
import { BASE_URL, DEFAULT_KEYWORDS, DEFAULT_OG_IMAGE, GITHUB_URL, ORGANIZATION_NAME, ORGANIZATION_SCHEMA, SITE_NAME, SITE_TAGLINE, WEB_URL, REPO_NAME, getHomeDescription, websiteFullTitle, websiteTitle } from "@/constants"
|
||||||
import { getRecentlyAddedIcons, getTotalIcons } from "@/lib/api"
|
import { getRecentlyAddedIcons, getTotalIcons } from "@/lib/api"
|
||||||
import type { Metadata } from "next"
|
import type { Metadata } from "next"
|
||||||
|
|
||||||
export async function generateMetadata(): Promise<Metadata> {
|
export async function generateMetadata(): Promise<Metadata> {
|
||||||
const { totalIcons } = await getTotalIcons()
|
const { totalIcons } = await getTotalIcons()
|
||||||
|
const description = getHomeDescription(totalIcons)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: websiteTitle,
|
title: websiteTitle,
|
||||||
description: getDescription(totalIcons),
|
description,
|
||||||
keywords: ["dashboard icons", "service icons", "application icons", "tool icons", "web dashboard", "app directory"],
|
keywords: DEFAULT_KEYWORDS,
|
||||||
robots: {
|
robots: {
|
||||||
index: true,
|
index: true,
|
||||||
follow: true,
|
follow: true,
|
||||||
},
|
},
|
||||||
openGraph: {
|
openGraph: {
|
||||||
title: websiteTitle,
|
title: websiteFullTitle,
|
||||||
description: getDescription(totalIcons),
|
description,
|
||||||
type: "website",
|
type: "website",
|
||||||
url: BASE_URL,
|
url: WEB_URL,
|
||||||
images: [
|
images: [DEFAULT_OG_IMAGE],
|
||||||
{
|
|
||||||
url: "/og-image.png",
|
|
||||||
width: 1200,
|
|
||||||
height: 630,
|
|
||||||
alt: "Dashboard Icons",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
twitter: {
|
twitter: {
|
||||||
title: websiteTitle,
|
title: websiteFullTitle,
|
||||||
description: getDescription(totalIcons),
|
description,
|
||||||
card: "summary_large_image",
|
card: "summary_large_image",
|
||||||
images: ["/og-image.png"],
|
images: [DEFAULT_OG_IMAGE.url],
|
||||||
},
|
},
|
||||||
alternates: {
|
alternates: {
|
||||||
canonical: BASE_URL,
|
canonical: WEB_URL,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -54,9 +49,11 @@ export default async function Home() {
|
|||||||
const stars = await getGitHubStars()
|
const stars = await getGitHubStars()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col min-h-screen">
|
<>
|
||||||
<HeroSection totalIcons={totalIcons} stars={stars} />
|
<div className="flex flex-col min-h-screen">
|
||||||
<RecentlyAddedIcons icons={recentIcons} />
|
<HeroSection totalIcons={totalIcons} stars={stars} />
|
||||||
</div>
|
<RecentlyAddedIcons icons={recentIcons} />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,138 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import { CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command"
|
|
||||||
import { useMediaQuery } from "@/hooks/use-media-query"
|
|
||||||
import { fuzzySearch } from "@/lib/utils"
|
|
||||||
import { Icon } from "@/types/icons"
|
|
||||||
import { useRouter } from "next/navigation"
|
|
||||||
import { useCallback, useEffect, useState } from "react"
|
|
||||||
|
|
||||||
interface CommandMenuProps {
|
|
||||||
icons: {
|
|
||||||
name: string
|
|
||||||
data: {
|
|
||||||
categories: string[]
|
|
||||||
aliases: string[]
|
|
||||||
[key: string]: unknown
|
|
||||||
}
|
|
||||||
}[]
|
|
||||||
triggerButtonId?: string
|
|
||||||
open?: boolean
|
|
||||||
onOpenChange?: (open: boolean) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export function CommandMenu({ icons, open: externalOpen, onOpenChange: externalOnOpenChange }: CommandMenuProps) {
|
|
||||||
const router = useRouter()
|
|
||||||
const [internalOpen, setInternalOpen] = useState(false)
|
|
||||||
const [query, setQuery] = useState("")
|
|
||||||
const isDesktop = useMediaQuery("(min-width: 768px)")
|
|
||||||
|
|
||||||
// Use either external or internal state for controlling open state
|
|
||||||
const isOpen = externalOpen !== undefined ? externalOpen : internalOpen
|
|
||||||
|
|
||||||
// Wrap setIsOpen in useCallback to fix dependency issue
|
|
||||||
const setIsOpen = useCallback(
|
|
||||||
(value: boolean) => {
|
|
||||||
if (externalOnOpenChange) {
|
|
||||||
externalOnOpenChange(value)
|
|
||||||
} else {
|
|
||||||
setInternalOpen(value)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[externalOnOpenChange],
|
|
||||||
)
|
|
||||||
|
|
||||||
const filteredIcons = getFilteredIcons(icons, query)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handleKeyDown = (e: KeyboardEvent) => {
|
|
||||||
if (
|
|
||||||
(e.key === "k" && (e.metaKey || e.ctrlKey)) ||
|
|
||||||
(e.key === "/" && document.activeElement?.tagName !== "INPUT" && document.activeElement?.tagName !== "TEXTAREA")
|
|
||||||
) {
|
|
||||||
e.preventDefault()
|
|
||||||
setIsOpen(!isOpen)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener("keydown", handleKeyDown)
|
|
||||||
return () => document.removeEventListener("keydown", handleKeyDown)
|
|
||||||
}, [isOpen, setIsOpen])
|
|
||||||
|
|
||||||
function getFilteredIcons(iconList: CommandMenuProps["icons"], query: string) {
|
|
||||||
if (!query) {
|
|
||||||
// Return a limited number of icons when no query is provided
|
|
||||||
return iconList.slice(0, 8)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate scores for each icon
|
|
||||||
const scoredIcons = iconList.map((icon) => {
|
|
||||||
// Calculate scores for different fields
|
|
||||||
const nameScore = fuzzySearch(icon.name, query) * 2.0 // Give more weight to name matches
|
|
||||||
|
|
||||||
// Get max score from aliases
|
|
||||||
const aliasScore =
|
|
||||||
icon.data.aliases && icon.data.aliases.length > 0
|
|
||||||
? Math.max(...icon.data.aliases.map((alias) => fuzzySearch(alias, query))) * 1.8 // Increased weight for aliases
|
|
||||||
: 0
|
|
||||||
|
|
||||||
// Get max score from categories
|
|
||||||
const categoryScore =
|
|
||||||
icon.data.categories && icon.data.categories.length > 0
|
|
||||||
? Math.max(...icon.data.categories.map((category) => fuzzySearch(category, query)))
|
|
||||||
: 0
|
|
||||||
|
|
||||||
// Use the highest score
|
|
||||||
const score = Math.max(nameScore, aliasScore, categoryScore)
|
|
||||||
|
|
||||||
return { icon, score, matchedField: score === nameScore ? "name" : score === aliasScore ? "alias" : "category" }
|
|
||||||
})
|
|
||||||
|
|
||||||
// Filter icons with a minimum score and sort by highest score
|
|
||||||
return scoredIcons
|
|
||||||
.filter((item) => item.score > 0.3) // Higher threshold for more accurate results
|
|
||||||
.sort((a, b) => b.score - a.score)
|
|
||||||
.slice(0, 20) // Limit the number of results
|
|
||||||
.map((item) => item.icon)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleSelect = (name: string) => {
|
|
||||||
setIsOpen(false)
|
|
||||||
router.push(`/icons/${name}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<CommandDialog open={isOpen} onOpenChange={setIsOpen}>
|
|
||||||
<CommandInput placeholder="Search for icons by name, category, or purpose..." value={query} onValueChange={setQuery} />
|
|
||||||
<CommandList>
|
|
||||||
<CommandEmpty>No matching icons found. Try a different search term or browse all icons.</CommandEmpty>
|
|
||||||
<CommandGroup heading="Icons">
|
|
||||||
{filteredIcons.map(({ name, data }) => {
|
|
||||||
// Find matched alias for display if available
|
|
||||||
const matchedAlias =
|
|
||||||
query && data.aliases && data.aliases.length > 0
|
|
||||||
? data.aliases.find((alias) => alias.toLowerCase().includes(query.toLowerCase()))
|
|
||||||
: null
|
|
||||||
|
|
||||||
return (
|
|
||||||
<CommandItem key={name} value={name} onSelect={() => handleSelect(name)} className="flex items-center gap-2 cursor-pointer">
|
|
||||||
<div className="flex-shrink-0 h-5 w-5 relative">
|
|
||||||
<div className="h-5 w-5 bg-rose-100 dark:bg-rose-900/30 rounded-md flex items-center justify-center">
|
|
||||||
<span className="text-[10px] font-medium text-rose-800 dark:text-rose-300">{name.substring(0, 2).toUpperCase()}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<span className="flex-grow capitalize">{name.replace(/-/g, " ")}</span>
|
|
||||||
{matchedAlias && <span className="text-xs text-primary-500 truncate max-w-[100px]">alias: {matchedAlias}</span>}
|
|
||||||
{!matchedAlias && data.categories && data.categories.length > 0 && (
|
|
||||||
<span className="text-xs text-muted-foreground truncate max-w-[100px]">
|
|
||||||
{data.categories[0].replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase())}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</CommandItem>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</CommandGroup>
|
|
||||||
</CommandList>
|
|
||||||
</CommandDialog>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -37,7 +37,7 @@ export function Footer() {
|
|||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-3">
|
||||||
<h3 className="font-bold text-lg text-foreground/90">Dashboard Icons</h3>
|
<h3 className="font-bold text-lg text-foreground/90">Dashboard Icons</h3>
|
||||||
<p className="text-sm text-muted-foreground leading-relaxed">
|
<p className="text-sm text-muted-foreground leading-relaxed">
|
||||||
A collection of curated icons for services, applications and tools, designed specifically for dashboards and app directories.
|
Collection of icons for applications, services, and tools - designed for dashboards and app directories.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -3,42 +3,14 @@
|
|||||||
import { IconSubmissionForm } from "@/components/icon-submission-form"
|
import { IconSubmissionForm } from "@/components/icon-submission-form"
|
||||||
import { ThemeSwitcher } from "@/components/theme-switcher"
|
import { ThemeSwitcher } from "@/components/theme-switcher"
|
||||||
import { REPO_PATH } from "@/constants"
|
import { REPO_PATH } from "@/constants"
|
||||||
import { getIconsArray } from "@/lib/api"
|
|
||||||
import type { IconWithName } from "@/types/icons"
|
|
||||||
import { motion } from "framer-motion"
|
import { motion } from "framer-motion"
|
||||||
import { Github, Search } from "lucide-react"
|
import { Github } from "lucide-react"
|
||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
import { useEffect, useState } from "react"
|
|
||||||
import { CommandMenu } from "./command-menu"
|
|
||||||
import { HeaderNav } from "./header-nav"
|
import { HeaderNav } from "./header-nav"
|
||||||
import { Button } from "./ui/button"
|
import { Button } from "./ui/button"
|
||||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./ui/tooltip"
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./ui/tooltip"
|
||||||
|
|
||||||
export function Header() {
|
export function Header() {
|
||||||
const [iconsData, setIconsData] = useState<IconWithName[]>([])
|
|
||||||
const [isLoaded, setIsLoaded] = useState(false)
|
|
||||||
const [commandMenuOpen, setCommandMenuOpen] = useState(false)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
async function loadIcons() {
|
|
||||||
try {
|
|
||||||
const icons = await getIconsArray()
|
|
||||||
setIconsData(icons)
|
|
||||||
setIsLoaded(true)
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Failed to load icons:", error)
|
|
||||||
setIsLoaded(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
loadIcons()
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
// Function to open the command menu
|
|
||||||
const openCommandMenu = () => {
|
|
||||||
setCommandMenuOpen(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<motion.header
|
<motion.header
|
||||||
className="border-b sticky top-0 z-50 backdrop-blur-2xl bg-background/50 border-border/50"
|
className="border-b sticky top-0 z-50 backdrop-blur-2xl bg-background/50 border-border/50"
|
||||||
@@ -56,30 +28,6 @@ export function Header() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 md:gap-4">
|
<div className="flex items-center gap-2 md:gap-4">
|
||||||
{/* Desktop search button */}
|
|
||||||
<div className="hidden md:block">
|
|
||||||
<Button variant="outline" className="gap-2 cursor-pointer transition-all duration-300" onClick={openCommandMenu}>
|
|
||||||
<Search className="h-4 w-4 transition-all duration-300" />
|
|
||||||
<span>Find icons</span>
|
|
||||||
<kbd className="pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border border-border/80 bg-muted/80 px-1.5 font-mono text-[10px] font-medium opacity-100">
|
|
||||||
<span className="text-xs">⌘</span>K
|
|
||||||
</kbd>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Mobile search button */}
|
|
||||||
<div className="md:hidden">
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
className="rounded-lg cursor-pointer transition-all duration-300 hover:ring-2 "
|
|
||||||
onClick={openCommandMenu}
|
|
||||||
>
|
|
||||||
<Search className="h-5 w-5 transition-all duration-300" />
|
|
||||||
<span className="sr-only">Find icons</span>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="hidden md:flex items-center gap-2 md:gap-4">
|
<div className="hidden md:flex items-center gap-2 md:gap-4">
|
||||||
<IconSubmissionForm />
|
<IconSubmissionForm />
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
@@ -106,9 +54,6 @@ export function Header() {
|
|||||||
<ThemeSwitcher />
|
<ThemeSwitcher />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Single instance of CommandMenu */}
|
|
||||||
{isLoaded && <CommandMenu icons={iconsData} open={commandMenuOpen} onOpenChange={setCommandMenuOpen} />}
|
|
||||||
</motion.header>
|
</motion.header>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -205,13 +205,61 @@ export function HeroSection({ totalIcons, stars }: { totalIcons: number; stars:
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="relative z-10 container mx-auto px-4 md:px-6 mt-4 py-20">
|
<div className="relative z-10 container mx-auto px-4 sm:px-6 lg:px-8 mt-4 py-20">
|
||||||
<div className="max-w-4xl mx-auto text-center flex flex-col gap-4 ">
|
<div className="max-w-4xl mx-auto text-center flex flex-col gap-4 ">
|
||||||
<h1 className="relative text-3xl sm:text-5xl md:text-7xl font-bold mb-4 md:mb-8 tracking-tight motion-preset-slide-up motion-duration-500 ">
|
<h1 className="relative text-3xl sm:text-5xl md:text-7xl font-bold mb-4 md:mb-8 tracking-tight motion-preset-slide-up motion-duration-500 ">
|
||||||
Your definitive source for
|
Your definitive source for
|
||||||
<Sparkles className="absolute -right-1 -bottom-3 text-rose-500 h-8 w-8 sm:h-12 sm:w-12 md:h-16 md:w-12 motion-delay-300 motion-preset-seesaw-lg motion-scale-in-[0.5] motion-translate-x-in-[-120%] motion-translate-y-in-[-60%] motion-opacity-in-[33%] motion-rotate-in-[-1080deg] motion-blur-in-[10px] motion-duration-500 motion-delay-[0.13s]/scale motion-duration-[0.13s]/opacity motion-duration-[0.40s]/rotate motion-duration-[0.05s]/blur motion-delay-[0.20s]/blur motion-ease-spring-bouncier" />
|
<motion.span
|
||||||
|
className="absolute -right-1 -bottom-3"
|
||||||
|
initial={{ opacity: 0, scale: 0.5, x: -20, y: -10 }}
|
||||||
|
animate={{ opacity: 1, scale: 1, x: 0, y: 0 }}
|
||||||
|
transition={{
|
||||||
|
duration: 0.5,
|
||||||
|
delay: 0.3,
|
||||||
|
ease: "easeOut"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<motion.div
|
||||||
|
animate={{
|
||||||
|
y: [0, -3, 0],
|
||||||
|
rotate: [0, 5, 0]
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
duration: 3,
|
||||||
|
repeat: Infinity,
|
||||||
|
repeatType: "reverse",
|
||||||
|
ease: "easeInOut"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Sparkles className="text-rose-500 h-8 w-8 sm:h-12 sm:w-12 md:h-16 md:w-12" />
|
||||||
|
</motion.div>
|
||||||
|
</motion.span>
|
||||||
<br />
|
<br />
|
||||||
<Sparkles className="absolute -left-1 -top-3 text-rose-500 h-5 w-5 sm:h-8 sm:w-8 md:h-12 md:w-12 motion-delay-300 motion-preset-seesaw-lg motion-scale-in-[0.5] motion-translate-x-in-[159%] motion-translate-y-in-[-60%] motion-opacity-in-[33%] motion-rotate-in-[-1080deg] motion-blur-in-[10px] motion-duration-500 motion-delay-[0.13s]/scale motion-duration-[0.13s]/opacity motion-duration-[0.40s]/rotate motion-duration-[0.05s]/blur motion-delay-[0.20s]/blur motion-ease-spring-bouncier" />
|
<motion.span
|
||||||
|
className="absolute -left-1 -top-3"
|
||||||
|
initial={{ opacity: 0, scale: 0.5, x: 20, y: -10 }}
|
||||||
|
animate={{ opacity: 1, scale: 1, x: 0, y: 0 }}
|
||||||
|
transition={{
|
||||||
|
duration: 0.5,
|
||||||
|
delay: 0.3,
|
||||||
|
ease: "easeOut"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<motion.div
|
||||||
|
animate={{
|
||||||
|
y: [0, -3, 0],
|
||||||
|
rotate: [0, -5, 0]
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
duration: 4,
|
||||||
|
repeat: Infinity,
|
||||||
|
repeatType: "reverse",
|
||||||
|
ease: "easeInOut"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Sparkles className="text-rose-500 h-5 w-5 sm:h-8 sm:w-8 md:h-12 md:w-12" />
|
||||||
|
</motion.div>
|
||||||
|
</motion.span>
|
||||||
<AuroraText colors={["#FA5352", "#FA5352", "orange"]}>dashboard icons</AuroraText>
|
<AuroraText colors={["#FA5352", "#FA5352", "orange"]}>dashboard icons</AuroraText>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
@@ -224,7 +272,7 @@ export function HeroSection({ totalIcons, stars }: { totalIcons: number; stars:
|
|||||||
<SearchInput searchQuery={searchQuery} setSearchQuery={setSearchQuery} totalIcons={totalIcons} />
|
<SearchInput searchQuery={searchQuery} setSearchQuery={setSearchQuery} totalIcons={totalIcons} />
|
||||||
<div className="w-full flex gap-3 md:gap-4 flex-wrap justify-center motion-preset-slide-down motion-duration-500">
|
<div className="w-full flex gap-3 md:gap-4 flex-wrap justify-center motion-preset-slide-down motion-duration-500">
|
||||||
<Link href="/icons">
|
<Link href="/icons">
|
||||||
<InteractiveHoverButton className="rounded-md bg-input/30">Explore icons</InteractiveHoverButton>
|
<InteractiveHoverButton className="rounded-md bg-input/30">Browse icons</InteractiveHoverButton>
|
||||||
</Link>
|
</Link>
|
||||||
<GiveUsAStarButton stars={stars} />
|
<GiveUsAStarButton stars={stars} />
|
||||||
<GiveUsMoneyButton />
|
<GiveUsMoneyButton />
|
||||||
@@ -449,12 +497,12 @@ export function GiveUsMoneyButton() {
|
|||||||
<div className="flex justify-between items-center pt-2">
|
<div className="flex justify-between items-center pt-2">
|
||||||
<Link href={openCollectiveUrl} target="_blank" rel="noopener noreferrer">
|
<Link href={openCollectiveUrl} target="_blank" rel="noopener noreferrer">
|
||||||
<Button variant="default" size="sm" className="bg-primary hover:bg-primary/90">
|
<Button variant="default" size="sm" className="bg-primary hover:bg-primary/90">
|
||||||
Donate
|
Support
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
<Link href={`${openCollectiveUrl}/transactions`} target="_blank" rel="noopener noreferrer">
|
<Link href={`${openCollectiveUrl}/transactions`} target="_blank" rel="noopener noreferrer">
|
||||||
<Button variant="link" size="sm" className="flex items-center gap-1 text-xs text-secondary-foreground">
|
<Button variant="link" size="sm" className="flex items-center gap-1 text-xs text-secondary-foreground">
|
||||||
View expenses
|
View transactions
|
||||||
<ExternalLink className="h-3 w-3" />
|
<ExternalLink className="h-3 w-3" />
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
@@ -478,7 +526,7 @@ function SearchInput({ searchQuery, setSearchQuery, totalIcons }: SearchInputPro
|
|||||||
name="q"
|
name="q"
|
||||||
autoFocus
|
autoFocus
|
||||||
type="search"
|
type="search"
|
||||||
placeholder={`Find any of ${totalIcons} icons by name or category...`}
|
placeholder="Search for icons..."
|
||||||
className="pl-10 h-10 md:h-12 rounded-lg w-full border-border focus:border-primary/20 text-sm md:text-base"
|
className="pl-10 h-10 md:h-12 rounded-lg w-full border-border focus:border-primary/20 text-sm md:text-base"
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
|
|||||||
@@ -207,6 +207,7 @@ export function IconDetails({ icon, iconData, authorData }: IconDetailsProps) {
|
|||||||
size="icon"
|
size="icon"
|
||||||
className="h-8 w-8 rounded-lg cursor-pointer"
|
className="h-8 w-8 rounded-lg cursor-pointer"
|
||||||
onClick={(e) => handleDownload(e, imageUrl, `${iconName}.${format}`)}
|
onClick={(e) => handleDownload(e, imageUrl, `${iconName}.${format}`)}
|
||||||
|
aria-label={`Download ${iconName} in ${format} format${theme ? ` (${theme} theme)` : ""}`}
|
||||||
>
|
>
|
||||||
<Download className="w-4 h-4" />
|
<Download className="w-4 h-4" />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -223,6 +224,7 @@ export function IconDetails({ icon, iconData, authorData }: IconDetailsProps) {
|
|||||||
size="icon"
|
size="icon"
|
||||||
className="h-8 w-8 rounded-lg cursor-pointer"
|
className="h-8 w-8 rounded-lg cursor-pointer"
|
||||||
onClick={(e) => handleCopy(imageUrl, `btn-${variantKey}`, e)}
|
onClick={(e) => handleCopy(imageUrl, `btn-${variantKey}`, e)}
|
||||||
|
aria-label={`Copy URL for ${iconName} in ${format} format${theme ? ` (${theme} theme)` : ""}`}
|
||||||
>
|
>
|
||||||
{copiedVariants[`btn-${variantKey}`] ? <Check className="w-4 h-4 text-green-500" /> : <Copy className="w-4 h-4" />}
|
{copiedVariants[`btn-${variantKey}`] ? <Check className="w-4 h-4 text-green-500" /> : <Copy className="w-4 h-4" />}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -234,8 +236,18 @@ export function IconDetails({ icon, iconData, authorData }: IconDetailsProps) {
|
|||||||
|
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<Button variant="outline" size="icon" className="h-8 w-8 rounded-lg" asChild>
|
<Button
|
||||||
<Link href={githubUrl} target="_blank" rel="noopener noreferrer">
|
variant="outline"
|
||||||
|
size="icon"
|
||||||
|
className="h-8 w-8 rounded-lg"
|
||||||
|
asChild
|
||||||
|
>
|
||||||
|
<Link
|
||||||
|
href={githubUrl}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
aria-label={`View ${iconName} ${format} file on GitHub`}
|
||||||
|
>
|
||||||
<Github className="w-4 h-4" />
|
<Github className="w-4 h-4" />
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
@@ -252,7 +264,7 @@ export function IconDetails({ icon, iconData, authorData }: IconDetailsProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto pt-12 pb-14">
|
<div className="container mx-auto pt-12 pb-14 px-4 sm:px-6 lg:px-8">
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-4 gap-6">
|
<div className="grid grid-cols-1 lg:grid-cols-4 gap-6">
|
||||||
{/* Left Column: Icon Info and Author */}
|
{/* Left Column: Icon Info and Author */}
|
||||||
<div className="lg:col-span-1">
|
<div className="lg:col-span-1">
|
||||||
@@ -306,7 +318,7 @@ export function IconDetails({ icon, iconData, authorData }: IconDetailsProps) {
|
|||||||
|
|
||||||
{iconData.categories && iconData.categories.length > 0 && (
|
{iconData.categories && iconData.categories.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-sm font-semibold text-muted-foreground">Categories</h3>
|
<h3 className="text-sm font-semibold text-muted-foreground mb-2">Categories</h3>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{iconData.categories.map((category) => (
|
{iconData.categories.map((category) => (
|
||||||
<Link key={category} href={`/icons?category=${encodeURIComponent(category)}`} className="cursor-pointer">
|
<Link key={category} href={`/icons?category=${encodeURIComponent(category)}`} className="cursor-pointer">
|
||||||
@@ -327,7 +339,7 @@ export function IconDetails({ icon, iconData, authorData }: IconDetailsProps) {
|
|||||||
|
|
||||||
{iconData.aliases && iconData.aliases.length > 0 && (
|
{iconData.aliases && iconData.aliases.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-sm font-semibold text-muted-foreground">Aliases</h3>
|
<h3 className="text-sm font-semibold text-muted-foreground mb-2">Aliases</h3>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{iconData.aliases.map((alias) => (
|
{iconData.aliases.map((alias) => (
|
||||||
<Badge
|
<Badge
|
||||||
@@ -344,19 +356,17 @@ export function IconDetails({ icon, iconData, authorData }: IconDetailsProps) {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-sm font-semibold text-muted-foreground">About this icon</h3>
|
<h3 className="text-sm font-semibold text-muted-foreground mb-2">About this icon</h3>
|
||||||
<div className="text-xs text-muted-foreground space-y-2">
|
<div className="text-xs text-muted-foreground space-y-2">
|
||||||
<p>
|
<p>
|
||||||
Available in{" "}
|
Available in {availableFormats.length > 1
|
||||||
{availableFormats.length > 1
|
? `${availableFormats.length} formats (${availableFormats.map((f) => f.toUpperCase()).join(", ")}) `
|
||||||
? `${availableFormats.length} formats (${availableFormats.map((f) => f.toUpperCase()).join(", ")})`
|
: `${availableFormats[0].toUpperCase()} format `}
|
||||||
: `${availableFormats[0].toUpperCase()} format`}{" "}
|
|
||||||
with a base format of {iconData.base.toUpperCase()}.
|
with a base format of {iconData.base.toUpperCase()}.
|
||||||
{iconData.colors && " Includes both light and dark theme variants for better integration with different UI designs."}
|
{iconData.colors && " Includes both light and dark theme variants for better integration with different UI designs."}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Use the {icon} icon in your web applications, dashboards, or documentation to enhance visual communication and user
|
Perfect for adding to dashboards, app directories, documentation, or anywhere you need the {icon.replace(/-/g, " ")} logo.
|
||||||
experience.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -412,7 +422,7 @@ export function IconDetails({ icon, iconData, authorData }: IconDetailsProps) {
|
|||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="">
|
<div className="">
|
||||||
<h3 className="text-sm font-semibold text-muted-foreground">Base format</h3>
|
<h3 className="text-sm font-semibold text-muted-foreground mb-2">Base format</h3>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<FileType className="w-4 h-4 text-blue-500" />
|
<FileType className="w-4 h-4 text-blue-500" />
|
||||||
<div className="px-3 py-1.5 border border-border rounded-lg text-sm font-medium">{iconData.base.toUpperCase()}</div>
|
<div className="px-3 py-1.5 border border-border rounded-lg text-sm font-medium">{iconData.base.toUpperCase()}</div>
|
||||||
@@ -420,7 +430,7 @@ export function IconDetails({ icon, iconData, authorData }: IconDetailsProps) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="">
|
<div className="">
|
||||||
<h3 className="text-sm font-semibold text-muted-foreground">Available formats</h3>
|
<h3 className="text-sm font-semibold text-muted-foreground mb-2">Available formats</h3>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{availableFormats.map((format) => (
|
{availableFormats.map((format) => (
|
||||||
<div key={format} className="px-3 py-1.5 border border-border rounded-lg text-xs font-medium">
|
<div key={format} className="px-3 py-1.5 border border-border rounded-lg text-xs font-medium">
|
||||||
@@ -432,7 +442,7 @@ export function IconDetails({ icon, iconData, authorData }: IconDetailsProps) {
|
|||||||
|
|
||||||
{iconData.colors && (
|
{iconData.colors && (
|
||||||
<div className="">
|
<div className="">
|
||||||
<h3 className="text-sm font-semibold text-muted-foreground">Color variants</h3>
|
<h3 className="text-sm font-semibold text-muted-foreground mb-2">Color variants</h3>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{Object.entries(iconData.colors).map(([theme, variant]) => (
|
{Object.entries(iconData.colors).map(([theme, variant]) => (
|
||||||
<div key={theme} className="flex items-center gap-2">
|
<div key={theme} className="flex items-center gap-2">
|
||||||
@@ -446,7 +456,7 @@ export function IconDetails({ icon, iconData, authorData }: IconDetailsProps) {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="">
|
<div className="">
|
||||||
<h3 className="text-sm font-semibold text-muted-foreground">Source</h3>
|
<h3 className="text-sm font-semibold text-muted-foreground mb-2">Source</h3>
|
||||||
<Button variant="outline" className="w-full" asChild>
|
<Button variant="outline" className="w-full" asChild>
|
||||||
<Link href={`${REPO_PATH}/blob/main/meta/${icon}.json`} target="_blank" rel="noopener noreferrer">
|
<Link href={`${REPO_PATH}/blob/main/meta/${icon}.json`} target="_blank" rel="noopener noreferrer">
|
||||||
<Github className="w-4 h-4 mr-2" />
|
<Github className="w-4 h-4 mr-2" />
|
||||||
|
|||||||
@@ -11,32 +11,32 @@ import { useState } from "react"
|
|||||||
export const ISSUE_TEMPLATES = [
|
export const ISSUE_TEMPLATES = [
|
||||||
{
|
{
|
||||||
id: "add_monochrome_icon",
|
id: "add_monochrome_icon",
|
||||||
name: "Add light & dark icon",
|
name: "Add light/dark icon",
|
||||||
description: "Submit a new icon with both light and dark versions for optimal theme compatibility.",
|
description: "Submit a new icon with light and dark versions.",
|
||||||
url: `${REPO_PATH}/issues/new?template=add_monochrome_icon.yml`,
|
url: `${REPO_PATH}/issues/new?template=add_monochrome_icon.yml`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "add_normal_icon",
|
id: "add_normal_icon",
|
||||||
name: "Add normal icon",
|
name: "Add standard icon",
|
||||||
description: "Submit a new icon that works well across both light and dark themes.",
|
description: "Submit a new icon for both themes.",
|
||||||
url: `${REPO_PATH}/issues/new?template=add_normal_icon.yml`,
|
url: `${REPO_PATH}/issues/new?template=add_normal_icon.yml`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "update_monochrome_icon",
|
id: "update_monochrome_icon",
|
||||||
name: "Update light & dark icon",
|
name: "Update light/dark icon",
|
||||||
description: "Improve an existing icon by updating both light and dark versions.",
|
description: "Improve or update an existing light/dark icon.",
|
||||||
url: `${REPO_PATH}/issues/new?template=update_monochrome_icon.yml`,
|
url: `${REPO_PATH}/issues/new?template=update_monochrome_icon.yml`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "update_normal_icon",
|
id: "update_normal_icon",
|
||||||
name: "Update normal icon",
|
name: "Update standard icon",
|
||||||
description: "Improve an existing icon that works across both light and dark themes.",
|
description: "Improve or update an existing standard icon.",
|
||||||
url: `${REPO_PATH}/issues/new?template=update_normal_icon.yml`,
|
url: `${REPO_PATH}/issues/new?template=update_normal_icon.yml`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "blank_issue",
|
id: "blank_issue",
|
||||||
name: "Something else",
|
name: "Other request",
|
||||||
description: "Create a custom issue for other suggestions, bug reports, or improvements.",
|
description: "Submit another type of request.",
|
||||||
url: `${REPO_PATH}/issues/new?template=BLANK_ISSUE`,
|
url: `${REPO_PATH}/issues/new?template=BLANK_ISSUE`,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@@ -73,13 +73,13 @@ export function IconSubmissionForm() {
|
|||||||
<Dialog open={open} onOpenChange={setOpen}>
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button variant="outline" className="hidden md:inline-flex cursor-pointer transition-all duration-300">
|
<Button variant="outline" className="hidden md:inline-flex cursor-pointer transition-all duration-300">
|
||||||
<PlusCircle className="h-4 w-4 transition-all duration-300" /> Contribute new icon
|
<PlusCircle className="h-4 w-4 transition-all duration-300" /> Submit icon(s)
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="md:max-w-4xl backdrop-blur-2xl bg-background">
|
<DialogContent className="md:max-w-4xl backdrop-blur-2xl bg-background">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Contribute a new icon</DialogTitle>
|
<DialogTitle>Submit an icon</DialogTitle>
|
||||||
<DialogDescription>Choose a template below to suggest a new icon or improve an existing one.</DialogDescription>
|
<DialogDescription>Select an option below to submit or update an icon.</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<IconSubmissionContent onClose={() => setOpen(false)} />
|
<IconSubmissionContent onClose={() => setOpen(false)} />
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
import type { CSSProperties, ComponentPropsWithoutRef, FC } from "react"
|
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
|
||||||
|
|
||||||
export interface AnimatedShinyTextProps extends ComponentPropsWithoutRef<"span"> {
|
|
||||||
shimmerWidth?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export const AnimatedShinyText: FC<AnimatedShinyTextProps> = ({ children, className, shimmerWidth = 100, ...props }) => {
|
|
||||||
return (
|
|
||||||
<span
|
|
||||||
style={
|
|
||||||
{
|
|
||||||
"--shiny-width": `${shimmerWidth}px`,
|
|
||||||
} as CSSProperties
|
|
||||||
}
|
|
||||||
className={cn(
|
|
||||||
"mx-auto max-w-md text-neutral-600/70 dark:text-neutral-400/70",
|
|
||||||
|
|
||||||
// Shine effect
|
|
||||||
"animate-shiny-text bg-clip-text bg-no-repeat [background-position:0_0] [background-size:var(--shiny-width)_100%] [transition:background-position_1s_cubic-bezier(.6,.6,0,1)_infinite]",
|
|
||||||
|
|
||||||
// Shine gradient
|
|
||||||
"bg-gradient-to-r from-transparent via-black/80 via-50% to-transparent dark:via-white/80",
|
|
||||||
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { motion, useMotionTemplate, useMotionValue } from "motion/react"
|
import { motion, useMotionTemplate, useMotionValue } from "motion/react"
|
||||||
import type React from "react"
|
import type React from "react"
|
||||||
import { useCallback, useEffect, useRef } from "react"
|
import { useCallback, useEffect, useRef, useState } from "react"
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
@@ -28,6 +28,7 @@ export function MagicCard({
|
|||||||
const cardRef = useRef<HTMLDivElement>(null)
|
const cardRef = useRef<HTMLDivElement>(null)
|
||||||
const mouseX = useMotionValue(-gradientSize)
|
const mouseX = useMotionValue(-gradientSize)
|
||||||
const mouseY = useMotionValue(-gradientSize)
|
const mouseY = useMotionValue(-gradientSize)
|
||||||
|
const [isMounted, setIsMounted] = useState(false)
|
||||||
|
|
||||||
const handleMouseMove = useCallback(
|
const handleMouseMove = useCallback(
|
||||||
(e: MouseEvent) => {
|
(e: MouseEvent) => {
|
||||||
@@ -60,6 +61,14 @@ export function MagicCard({
|
|||||||
}, [handleMouseMove, mouseX, gradientSize, mouseY])
|
}, [handleMouseMove, mouseX, gradientSize, mouseY])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
setIsMounted(true)
|
||||||
|
mouseX.set(-gradientSize)
|
||||||
|
mouseY.set(-gradientSize)
|
||||||
|
}, [gradientSize, mouseX, mouseY])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isMounted) return
|
||||||
|
|
||||||
document.addEventListener("mousemove", handleMouseMove)
|
document.addEventListener("mousemove", handleMouseMove)
|
||||||
document.addEventListener("mouseout", handleMouseOut)
|
document.addEventListener("mouseout", handleMouseOut)
|
||||||
document.addEventListener("mouseenter", handleMouseEnter)
|
document.addEventListener("mouseenter", handleMouseEnter)
|
||||||
@@ -69,15 +78,10 @@ export function MagicCard({
|
|||||||
document.removeEventListener("mouseout", handleMouseOut)
|
document.removeEventListener("mouseout", handleMouseOut)
|
||||||
document.removeEventListener("mouseenter", handleMouseEnter)
|
document.removeEventListener("mouseenter", handleMouseEnter)
|
||||||
}
|
}
|
||||||
}, [handleMouseEnter, handleMouseMove, handleMouseOut])
|
}, [isMounted, handleMouseEnter, handleMouseMove, handleMouseOut])
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
mouseX.set(-gradientSize)
|
|
||||||
mouseY.set(-gradientSize)
|
|
||||||
}, [gradientSize, mouseX, mouseY])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={cardRef} className={cn("group relative rounded-[inherit]", className)}>
|
<div className={cn("group relative rounded-[inherit]", className)}>
|
||||||
<motion.div
|
<motion.div
|
||||||
className="pointer-events-none absolute inset-0 rounded-[inherit] bg-border duration-300 group-hover:opacity-100"
|
className="pointer-events-none absolute inset-0 rounded-[inherit] bg-border duration-300 group-hover:opacity-100"
|
||||||
style={{
|
style={{
|
||||||
@@ -100,7 +104,7 @@ export function MagicCard({
|
|||||||
opacity: gradientOpacity,
|
opacity: gradientOpacity,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="relative">{children}</div>
|
<div ref={cardRef} className="relative">{children}</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export function RecentlyAddedIcons({ icons }: { icons: IconWithName[] }) {
|
|||||||
{/* Background glow */}
|
{/* Background glow */}
|
||||||
<div className="absolute inset-x-0 -top-40 -z-10 transform-gpu overflow-hidden blur-3xl sm:-top-80" aria-hidden="true" />
|
<div className="absolute inset-x-0 -top-40 -z-10 transform-gpu overflow-hidden blur-3xl sm:-top-80" aria-hidden="true" />
|
||||||
|
|
||||||
<div className="mx-auto px-6 lg:px-8">
|
<div className="mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
<div className="mx-auto max-w-2xl text-center my-4">
|
<div className="mx-auto max-w-2xl text-center my-4">
|
||||||
<h2 className="text-3xl font-bold tracking-tight sm:text-4xl bg-clip-text text-transparent bg-gradient-to-r from-rose-600 to-rose-500 motion-safe:motion-preset-fade-lg motion-duration-500">
|
<h2 className="text-3xl font-bold tracking-tight sm:text-4xl bg-clip-text text-transparent bg-gradient-to-r from-rose-600 to-rose-500 motion-safe:motion-preset-fade-lg motion-duration-500">
|
||||||
Recently Added Icons
|
Recently Added Icons
|
||||||
@@ -61,7 +61,7 @@ export function RecentlyAddedIcons({ icons }: { icons: IconWithName[] }) {
|
|||||||
href="/icons"
|
href="/icons"
|
||||||
className="font-medium inline-flex items-center py-2 px-4 rounded-full border transition-all duration-200 group hover-lift soft-shadow"
|
className="font-medium inline-flex items-center py-2 px-4 rounded-full border transition-all duration-200 group hover-lift soft-shadow"
|
||||||
>
|
>
|
||||||
View complete collection
|
View all icons
|
||||||
<ArrowRight className="w-4 h-4 ml-1.5 transition-transform duration-200 group-hover:translate-x-1" />
|
<ArrowRight className="w-4 h-4 ml-1.5 transition-transform duration-200 group-hover:translate-x-1" />
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
33
web/src/components/structured-data.tsx
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
type StructuredDataProps = {
|
||||||
|
data: any
|
||||||
|
id?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const StructuredData = ({ data, id }: StructuredDataProps) => {
|
||||||
|
return (
|
||||||
|
<script
|
||||||
|
id={id}
|
||||||
|
type="application/ld+json"
|
||||||
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(data) }}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type WebsiteStructuredDataProps = {
|
||||||
|
websiteSchema: any
|
||||||
|
organizationSchema: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WebsiteStructuredData = ({
|
||||||
|
websiteSchema,
|
||||||
|
organizationSchema
|
||||||
|
}: WebsiteStructuredDataProps) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<StructuredData data={websiteSchema} id="website-schema" />
|
||||||
|
<StructuredData data={organizationSchema} id="organization-schema" />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,177 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import * as React from "react"
|
|
||||||
import { Command as CommandPrimitive } from "cmdk"
|
|
||||||
import { SearchIcon } from "lucide-react"
|
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogDescription,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
} from "@/components/ui/dialog"
|
|
||||||
|
|
||||||
function Command({
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof CommandPrimitive>) {
|
|
||||||
return (
|
|
||||||
<CommandPrimitive
|
|
||||||
data-slot="command"
|
|
||||||
className={cn(
|
|
||||||
"bg-popover text-popover-foreground flex h-full w-full flex-col overflow-hidden rounded-md",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function CommandDialog({
|
|
||||||
title = "Command Palette",
|
|
||||||
description = "Search for a command to run...",
|
|
||||||
children,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof Dialog> & {
|
|
||||||
title?: string
|
|
||||||
description?: string
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<Dialog {...props}>
|
|
||||||
<DialogHeader className="sr-only">
|
|
||||||
<DialogTitle>{title}</DialogTitle>
|
|
||||||
<DialogDescription>{description}</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
<DialogContent className="overflow-hidden p-0">
|
|
||||||
<Command className="[&_[cmdk-group-heading]]:text-muted-foreground **:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
|
|
||||||
{children}
|
|
||||||
</Command>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function CommandInput({
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof CommandPrimitive.Input>) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
data-slot="command-input-wrapper"
|
|
||||||
className="flex h-9 items-center gap-2 border-b px-3"
|
|
||||||
>
|
|
||||||
<SearchIcon className="size-4 shrink-0 opacity-50" />
|
|
||||||
<CommandPrimitive.Input
|
|
||||||
data-slot="command-input"
|
|
||||||
className={cn(
|
|
||||||
"placeholder:text-muted-foreground flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function CommandList({
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof CommandPrimitive.List>) {
|
|
||||||
return (
|
|
||||||
<CommandPrimitive.List
|
|
||||||
data-slot="command-list"
|
|
||||||
className={cn(
|
|
||||||
"max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function CommandEmpty({
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof CommandPrimitive.Empty>) {
|
|
||||||
return (
|
|
||||||
<CommandPrimitive.Empty
|
|
||||||
data-slot="command-empty"
|
|
||||||
className="py-6 text-center text-sm"
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function CommandGroup({
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof CommandPrimitive.Group>) {
|
|
||||||
return (
|
|
||||||
<CommandPrimitive.Group
|
|
||||||
data-slot="command-group"
|
|
||||||
className={cn(
|
|
||||||
"text-foreground [&_[cmdk-group-heading]]:text-muted-foreground overflow-hidden p-1 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function CommandSeparator({
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof CommandPrimitive.Separator>) {
|
|
||||||
return (
|
|
||||||
<CommandPrimitive.Separator
|
|
||||||
data-slot="command-separator"
|
|
||||||
className={cn("bg-border -mx-1 h-px", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function CommandItem({
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof CommandPrimitive.Item>) {
|
|
||||||
return (
|
|
||||||
<CommandPrimitive.Item
|
|
||||||
data-slot="command-item"
|
|
||||||
className={cn(
|
|
||||||
"data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function CommandShortcut({
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<"span">) {
|
|
||||||
return (
|
|
||||||
<span
|
|
||||||
data-slot="command-shortcut"
|
|
||||||
className={cn(
|
|
||||||
"text-muted-foreground ml-auto text-xs tracking-widest",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
|
||||||
Command,
|
|
||||||
CommandDialog,
|
|
||||||
CommandInput,
|
|
||||||
CommandList,
|
|
||||||
CommandEmpty,
|
|
||||||
CommandGroup,
|
|
||||||
CommandItem,
|
|
||||||
CommandShortcut,
|
|
||||||
CommandSeparator,
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,119 @@ export const METADATA_URL = "https://raw.githubusercontent.com/homarr-labs/dashb
|
|||||||
export const WEB_URL = "https://dashboardicons.com"
|
export const WEB_URL = "https://dashboardicons.com"
|
||||||
export const REPO_NAME = "homarr-labs/dashboard-icons"
|
export const REPO_NAME = "homarr-labs/dashboard-icons"
|
||||||
|
|
||||||
export const getDescription = (totalIcons: number) =>
|
// Site-wide metadata constants
|
||||||
`A collection of ${totalIcons} curated icons for services, applications and tools, designed specifically for dashboards and app directories.`
|
export const SITE_NAME = "Dashboard Icons"
|
||||||
|
export const TITLE_SEPARATOR = " | "
|
||||||
|
export const SITE_TAGLINE = "Your definitive source for dashboard icons"
|
||||||
|
export const ORGANIZATION_NAME = "Homarr Labs"
|
||||||
|
|
||||||
export const websiteTitle = "Free Dashboard Icons - Download High-Quality UI & App Icons"
|
export const getDescription = (totalIcons: number) =>
|
||||||
|
`A curated collection of ${totalIcons} free icons for dashboards and app directories. Available in SVG, PNG, and WEBP formats. ${SITE_TAGLINE}.`
|
||||||
|
|
||||||
|
export const getHomeDescription = (totalIcons: number) =>
|
||||||
|
`Discover our curated collection of ${totalIcons} icons designed specifically for dashboards and app directories. ${SITE_TAGLINE}.`
|
||||||
|
|
||||||
|
export const getBrowseDescription = (totalIcons: number) =>
|
||||||
|
`Browse, search and download from our collection of ${totalIcons} curated icons. All icons available in SVG, PNG, and WEBP formats. ${SITE_TAGLINE}.`
|
||||||
|
|
||||||
|
export const getIconDescription = (iconName: string, totalIcons: number) =>
|
||||||
|
`Download the ${iconName} icon in SVG, PNG, and WEBP formats. Part of our curated collection of ${totalIcons} free icons for dashboards. ${SITE_TAGLINE}.`
|
||||||
|
|
||||||
|
export const websiteTitle = `${SITE_NAME} ${TITLE_SEPARATOR} Free, Curated Icons for Apps & Services`
|
||||||
|
export const websiteFullTitle = `${SITE_NAME} ${TITLE_SEPARATOR} Free, Curated Icons for Apps & Services ${TITLE_SEPARATOR} ${SITE_TAGLINE}`
|
||||||
|
|
||||||
|
// Various keyword sets for different pages
|
||||||
|
export const DEFAULT_KEYWORDS = [
|
||||||
|
"dashboard icons",
|
||||||
|
"app icons",
|
||||||
|
"service icons",
|
||||||
|
"curated icons",
|
||||||
|
"free icons",
|
||||||
|
"SVG icons",
|
||||||
|
"web dashboard",
|
||||||
|
"app directory"
|
||||||
|
]
|
||||||
|
|
||||||
|
export const BROWSE_KEYWORDS = [
|
||||||
|
"browse icons",
|
||||||
|
"search icons",
|
||||||
|
"download icons",
|
||||||
|
"minimal icons",
|
||||||
|
"dashboard design",
|
||||||
|
"UI icons",
|
||||||
|
...DEFAULT_KEYWORDS
|
||||||
|
]
|
||||||
|
|
||||||
|
export const ICON_DETAIL_KEYWORDS = (iconName: string) => [
|
||||||
|
`${iconName} icon`,
|
||||||
|
`${iconName} logo`,
|
||||||
|
`${iconName} svg`,
|
||||||
|
`${iconName} download`,
|
||||||
|
`${iconName} dashboard icon`,
|
||||||
|
...DEFAULT_KEYWORDS
|
||||||
|
]
|
||||||
|
|
||||||
|
// Core structured data for the website (JSON-LD)
|
||||||
|
export const getWebsiteSchema = (totalIcons: number) => ({
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "WebSite",
|
||||||
|
"name": SITE_NAME,
|
||||||
|
"url": WEB_URL,
|
||||||
|
"description": getDescription(totalIcons),
|
||||||
|
"potentialAction": {
|
||||||
|
"@type": "SearchAction",
|
||||||
|
"target": {
|
||||||
|
"@type": "EntryPoint",
|
||||||
|
"urlTemplate": `${WEB_URL}/icons?q={search_term_string}`
|
||||||
|
},
|
||||||
|
"query-input": "required name=search_term_string"
|
||||||
|
},
|
||||||
|
"slogan": SITE_TAGLINE
|
||||||
|
})
|
||||||
|
|
||||||
|
// Organization schema
|
||||||
|
export const ORGANIZATION_SCHEMA = {
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "Organization",
|
||||||
|
"name": ORGANIZATION_NAME,
|
||||||
|
"url": `https://github.com/${REPO_NAME}`,
|
||||||
|
"logo": `${WEB_URL}/og-image.png`,
|
||||||
|
"sameAs": [
|
||||||
|
`https://github.com/${REPO_NAME}`,
|
||||||
|
"https://homarr.dev"
|
||||||
|
],
|
||||||
|
"slogan": SITE_TAGLINE
|
||||||
|
}
|
||||||
|
|
||||||
|
// Social media
|
||||||
|
export const GITHUB_URL = `https://github.com/${REPO_NAME}`
|
||||||
|
|
||||||
|
// Image schemas
|
||||||
|
export const getIconSchema = (iconName: string, iconId: string, authorName: string, authorUrl: string, updateDate: string, totalIcons: number) => ({
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "ImageObject",
|
||||||
|
"name": `${iconName} Icon`,
|
||||||
|
"description": getIconDescription(iconName, totalIcons),
|
||||||
|
"contentUrl": `${BASE_URL}/png/${iconId}.png`,
|
||||||
|
"thumbnailUrl": `${BASE_URL}/png/${iconId}.png`,
|
||||||
|
"uploadDate": updateDate,
|
||||||
|
"author": {
|
||||||
|
"@type": "Person",
|
||||||
|
"name": authorName,
|
||||||
|
"url": authorUrl
|
||||||
|
},
|
||||||
|
"encodingFormat": ["image/png", "image/svg+xml", "image/webp"],
|
||||||
|
"contentSize": "Variable",
|
||||||
|
"representativeOfPage": true,
|
||||||
|
"creditText": `Icon contributed by ${authorName} to the ${SITE_NAME} collection by ${ORGANIZATION_NAME}`,
|
||||||
|
"embedUrl": `${WEB_URL}/icons/${iconId}`
|
||||||
|
})
|
||||||
|
|
||||||
|
// OpenGraph defaults
|
||||||
|
export const DEFAULT_OG_IMAGE = {
|
||||||
|
url: "/og-image.png",
|
||||||
|
width: 1200,
|
||||||
|
height: 630,
|
||||||
|
alt: `${SITE_NAME} - ${SITE_TAGLINE}`,
|
||||||
|
type: "image/png"
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import { useEffect, useState } from "react"
|
|
||||||
|
|
||||||
export function useMediaQuery(query: string): boolean {
|
|
||||||
const [matches, setMatches] = useState(false)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const media = window.matchMedia(query)
|
|
||||||
|
|
||||||
// Initial check
|
|
||||||
if (media.matches !== matches) {
|
|
||||||
setMatches(media.matches)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup listener for changes
|
|
||||||
const listener = () => setMatches(media.matches)
|
|
||||||
media.addEventListener("change", listener)
|
|
||||||
|
|
||||||
// Cleanup
|
|
||||||
return () => media.removeEventListener("change", listener)
|
|
||||||
}, [query, matches])
|
|
||||||
|
|
||||||
return matches
|
|
||||||
}
|
|
||||||
BIN
webp/buildium.webp
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
webp/calibre-web-automated-book-downloader.webp
Normal file
|
After Width: | Height: | Size: 106 KiB |
BIN
webp/doplarr.webp
Normal file
|
After Width: | Height: | Size: 57 KiB |
BIN
webp/entergy.webp
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
webp/gone-man-switch.webp
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
webp/image-maid.webp
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
webp/karaoke-eternal.webp
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
webp/librechat.webp
Normal file
|
After Width: | Height: | Size: 57 KiB |
BIN
webp/loxone-full.webp
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
webp/loxone.webp
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
webp/marzban.webp
Normal file
|
After Width: | Height: | Size: 69 KiB |
BIN
webp/noisedash.webp
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
webp/pangolin.webp
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
webp/pretix.webp
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
webp/recyclarr.webp
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
webp/release-argus.webp
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
webp/series-troxide.webp
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
webp/stream-harvestarr.webp
Normal file
|
After Width: | Height: | Size: 161 KiB |