Compare commits
11 Commits
icons/add-
...
icons/add-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
09531d11d5 | ||
|
|
d0f45e6bbb | ||
|
|
4138e10265 | ||
|
|
321e969f6c | ||
|
|
ea9b96ad6d | ||
|
|
59ad9344b7 | ||
|
|
50c3a92b29 | ||
|
|
575dee0580 | ||
|
|
23462d2980 | ||
|
|
832a4b76ae | ||
|
|
267b6d4400 |
12
meta/cup.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"base": "svg",
|
||||
"aliases": [],
|
||||
"categories": [],
|
||||
"update": {
|
||||
"timestamp": "2025-04-27T15:11:25.174121",
|
||||
"author": {
|
||||
"id": 77530549,
|
||||
"login": "sergi0g"
|
||||
}
|
||||
}
|
||||
}
|
||||
14
meta/mailpit.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"base": "svg",
|
||||
"aliases": [],
|
||||
"categories": [
|
||||
"Development"
|
||||
],
|
||||
"update": {
|
||||
"timestamp": "2025-04-27T15:11:19.986638",
|
||||
"author": {
|
||||
"id": 10742906,
|
||||
"login": "zackad"
|
||||
}
|
||||
}
|
||||
}
|
||||
12
meta/nzbgeek.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"base": "png",
|
||||
"aliases": [],
|
||||
"categories": [],
|
||||
"update": {
|
||||
"timestamp": "2025-04-27T15:13:05.869706",
|
||||
"author": {
|
||||
"id": 39389502,
|
||||
"login": "TeHtloTs"
|
||||
}
|
||||
}
|
||||
}
|
||||
16
meta/reactjs.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"base": "svg",
|
||||
"aliases": [
|
||||
"react-js"
|
||||
],
|
||||
"categories": [
|
||||
"Development"
|
||||
],
|
||||
"update": {
|
||||
"timestamp": "2025-04-26T23:03:30.306984",
|
||||
"author": {
|
||||
"id": 61716607,
|
||||
"login": "lesolski"
|
||||
}
|
||||
}
|
||||
}
|
||||
16
meta/uptimerobot.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"base": "svg",
|
||||
"aliases": [
|
||||
"uptime-robot"
|
||||
],
|
||||
"categories": [
|
||||
"Cloud"
|
||||
],
|
||||
"update": {
|
||||
"timestamp": "2025-04-28T18:02:44.073299",
|
||||
"author": {
|
||||
"id": 69894187,
|
||||
"login": "bunnypranav"
|
||||
}
|
||||
}
|
||||
}
|
||||
17
meta/viber.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"base": "svg",
|
||||
"aliases": [
|
||||
"viber-app",
|
||||
"viber-messaging"
|
||||
],
|
||||
"categories": [
|
||||
"Communication"
|
||||
],
|
||||
"update": {
|
||||
"timestamp": "2025-04-27T15:12:16.037327",
|
||||
"author": {
|
||||
"id": 61716607,
|
||||
"login": "lesolski"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -198,6 +198,20 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"mailpit": {
|
||||
"base": "svg",
|
||||
"aliases": [],
|
||||
"categories": [
|
||||
"Development"
|
||||
],
|
||||
"update": {
|
||||
"timestamp": "2025-04-27T15:11:19.986638",
|
||||
"author": {
|
||||
"id": 10742906,
|
||||
"login": "zackad"
|
||||
}
|
||||
}
|
||||
},
|
||||
"phoneinfoga": {
|
||||
"base": "svg",
|
||||
"aliases": [],
|
||||
@@ -3123,6 +3137,18 @@
|
||||
"light": "libreddit-light"
|
||||
}
|
||||
},
|
||||
"nzbgeek": {
|
||||
"base": "png",
|
||||
"aliases": [],
|
||||
"categories": [],
|
||||
"update": {
|
||||
"timestamp": "2025-04-27T15:13:05.869706",
|
||||
"author": {
|
||||
"id": 39389502,
|
||||
"login": "TeHtloTs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"seelf": {
|
||||
"base": "svg",
|
||||
"aliases": [
|
||||
@@ -11031,6 +11057,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"cup": {
|
||||
"base": "svg",
|
||||
"aliases": [],
|
||||
"categories": [],
|
||||
"update": {
|
||||
"timestamp": "2025-04-27T15:11:25.174121",
|
||||
"author": {
|
||||
"id": 77530549,
|
||||
"login": "sergi0g"
|
||||
}
|
||||
}
|
||||
},
|
||||
"private-internet-access": {
|
||||
"base": "svg",
|
||||
"aliases": [
|
||||
@@ -11367,6 +11405,22 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"reactjs": {
|
||||
"base": "svg",
|
||||
"aliases": [
|
||||
"react-js"
|
||||
],
|
||||
"categories": [
|
||||
"Development"
|
||||
],
|
||||
"update": {
|
||||
"timestamp": "2025-04-26T23:03:30.306984",
|
||||
"author": {
|
||||
"id": 61716607,
|
||||
"login": "lesolski"
|
||||
}
|
||||
}
|
||||
},
|
||||
"hotio": {
|
||||
"base": "svg",
|
||||
"aliases": [],
|
||||
@@ -17528,6 +17582,22 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"uptimerobot": {
|
||||
"base": "svg",
|
||||
"aliases": [
|
||||
"uptime-robot"
|
||||
],
|
||||
"categories": [
|
||||
"Cloud"
|
||||
],
|
||||
"update": {
|
||||
"timestamp": "2025-04-28T18:02:44.073299",
|
||||
"author": {
|
||||
"id": 69894187,
|
||||
"login": "bunnypranav"
|
||||
}
|
||||
}
|
||||
},
|
||||
"infoblox": {
|
||||
"base": "svg",
|
||||
"aliases": [],
|
||||
@@ -19467,6 +19537,23 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"viber": {
|
||||
"base": "svg",
|
||||
"aliases": [
|
||||
"viber-app",
|
||||
"viber-messaging"
|
||||
],
|
||||
"categories": [
|
||||
"Communication"
|
||||
],
|
||||
"update": {
|
||||
"timestamp": "2025-04-27T15:12:16.037327",
|
||||
"author": {
|
||||
"id": 61716607,
|
||||
"login": "lesolski"
|
||||
}
|
||||
}
|
||||
},
|
||||
"docker-amd": {
|
||||
"base": "png",
|
||||
"aliases": [],
|
||||
|
||||
BIN
png/cup.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 12 KiB |
BIN
png/cups.png
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 12 KiB |
BIN
png/mailpit.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
png/nzbgeek.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
png/reactjs.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
png/uptimerobot.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
png/viber.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
29
svg/cup.svg
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.2.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_2" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 128 128" style="enable-background:new 0 0 128 128;" xml:space="preserve">
|
||||
<path style="fill:#A6CFD6;" d="M65.12,17.55c-17.6-0.53-34.75,5.6-34.83,14.36c-0.04,5.2,1.37,18.6,3.62,48.68s2.25,33.58,3.5,34.95
|
||||
c1.25,1.37,10.02,8.8,25.75,8.8s25.93-6.43,26.93-8.05c0.48-0.78,1.83-17.89,3.5-37.07c1.81-20.84,3.91-43.9,3.99-45.06
|
||||
C97.82,30.66,94.2,18.43,65.12,17.55z"/>
|
||||
<path style="fill:#DCEDF6;" d="M41.4,45.29c-0.12,0.62,1.23,24.16,2.32,27.94c1.99,6.92,9.29,7.38,10.23,4.16
|
||||
c0.9-3.07-0.38-29.29-0.38-29.29s-3.66-0.3-6.43-0.84C44,46.63,41.4,45.29,41.4,45.29z"/>
|
||||
<path style="fill:#6CA4AE;" d="M33.74,32.61c-0.26,8.83,20.02,12.28,30.19,12.22c13.56-0.09,29.48-4.29,29.8-11.7
|
||||
S79.53,21.1,63.35,21.1C49.6,21.1,33.96,25.19,33.74,32.61z"/>
|
||||
<path style="fill:#DC0D27;" d="M84.85,13.1c-0.58,0.64-9.67,30.75-9.67,30.75s2.01-0.33,4-0.79c2.63-0.61,3.76-1.06,3.76-1.06
|
||||
s7.19-22.19,7.64-23.09c0.45-0.9,21.61-7.61,22.31-7.93c0.7-0.32,1.39-0.4,1.46-0.78c0.06-0.38-2.34-6.73-3.11-6.73
|
||||
C110.47,3.47,86.08,11.74,84.85,13.1z"/>
|
||||
<path style="fill:#8A1F0F;" d="M110.55,7.79c1.04,2.73,2.8,3.09,3.55,2.77c0.45-0.19,1.25-1.84,0.01-4.47
|
||||
c-0.99-2.09-2.17-2.74-2.93-2.61C110.42,3.6,109.69,5.53,110.55,7.79z"/>
|
||||
<g>
|
||||
<path style="fill:#8A1F0F;" d="M91.94,18.34c-0.22,0-0.44-0.11-0.58-0.3l-3.99-5.77c-0.22-0.32-0.14-0.75,0.18-0.97
|
||||
c0.32-0.22,0.76-0.14,0.97,0.18l3.99,5.77c0.22,0.32,0.14,0.75-0.18,0.97C92.21,18.3,92.07,18.34,91.94,18.34z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path style="fill:#8A1F0F;" d="M90.28,19.43c-0.18,0-0.35-0.07-0.49-0.2l-5.26-5.12c-0.28-0.27-0.28-0.71-0.01-0.99
|
||||
c0.27-0.28,0.71-0.28,0.99-0.01l5.26,5.12c0.28,0.27,0.28,0.71,0.01,0.99C90.64,19.36,90.46,19.43,90.28,19.43z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path style="fill:#8A1F0F;" d="M89.35,21.22c-0.12,0-0.25-0.03-0.36-0.1l-5.6-3.39c-0.33-0.2-0.44-0.63-0.24-0.96
|
||||
c0.2-0.33,0.63-0.44,0.96-0.24l5.6,3.39c0.33,0.2,0.44,0.63,0.24,0.96C89.82,21.1,89.59,21.22,89.35,21.22z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
6
svg/mailpit.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="500" height="460" viewBox="0 0 132.292 121.708" version="1.1" id="svg6" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs id="defs10"/>
|
||||
<path d="M12.321 0l53.861 53.918L120.365 0zM5.155 9.025l60.842 59.673 61.211-59.489-.185 36.835L66.921 70.54l15.164 12.616-8.137 5.986-41.609.184c-4.838-.022-25.877-18.34-27.185-41.255z" fill-opacity=".941" fill="#2d4a5f" id="path2" style="fill:#ffffff;fill-opacity:1"/>
|
||||
<path d="M78.385 72.049l53.907-21.679-8.031 57.318-11.845-9.132c-21.727 23.171-45.255 26.289-67.997 20.837S12.281 98.39 5.155 83.8-.67 53.116 2.843 38.769c1.13 10.511-1.313 16.316 6.38 33.612 6.31 11.399 14.413 20.417 25.89 24.956 13.9 6.195 32.247 3.357 41.701-3.039l14.24-12.156z" fill="#00b786" id="path4"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 828 B |
9
svg/reactjs.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-11.5 -10.23174 23 20.46348">
|
||||
<title>React Logo</title>
|
||||
<circle cx="0" cy="0" r="2.05" fill="#61dafb"/>
|
||||
<g stroke="#61dafb" stroke-width="1" fill="none">
|
||||
<ellipse rx="11" ry="4.2"/>
|
||||
<ellipse rx="11" ry="4.2" transform="rotate(60)"/>
|
||||
<ellipse rx="11" ry="4.2" transform="rotate(120)"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 366 B |
1
svg/uptimerobot.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="298" height="298"><g fill="#3BD771" transform="translate(.9 .9)"><circle cx="148.1" cy="148.1" r="148.1" opacity=".3"/><circle cx="148.1" cy="148.1" r="98.9"/></g></svg>
|
||||
|
After Width: | Height: | Size: 216 B |
1
svg/viber.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 631.99 666.43"><defs><style>.cls-1{fill:#7360f2;}.cls-2{fill:none;stroke:#7360f2;stroke-linecap:round;stroke-linejoin:round;stroke-width:16.86px;}</style></defs><title>Artboard 6</title><path class="cls-1" d="M560.65,65C544.09,49.72,477.17,1.14,328.11.48c0,0-175.78-10.6-261.47,68C18.94,116.19,2.16,186,.39,272.55S-3.67,521.3,152.68,565.28l.15,0-.1,67.11s-1,27.17,16.89,32.71c21.64,6.72,34.34-13.93,55-36.19,11.34-12.22,27-30.17,38.8-43.89,106.93,9,189.17-11.57,198.51-14.61,21.59-7,143.76-22.66,163.63-184.84C646.07,218.4,615.64,112.66,560.65,65Zm18.12,308.58C562,509,462.91,517.51,444.64,523.37c-7.77,2.5-80,20.47-170.83,14.54,0,0-67.68,81.65-88.82,102.88-3.3,3.32-7.18,4.66-9.77,4-3.64-.89-4.64-5.2-4.6-11.5.06-9,.58-111.52.58-111.52s-.08,0,0,0C38.94,485.05,46.65,347,48.15,274.71S63.23,143.2,103.57,103.37c72.48-65.65,221.79-55.84,221.79-55.84,126.09.55,186.51,38.52,200.52,51.24C572.4,138.6,596.1,233.91,578.77,373.54Z"/><path class="cls-2" d="M389.47,268.77q-2.46-49.59-50.38-52.09"/><path class="cls-2" d="M432.72,283.27q1-46.2-27.37-77.2c-19-20.74-45.3-32.16-79.05-34.63"/><path class="cls-2" d="M477,300.59q-.61-80.17-47.91-126.28t-117.65-46.6"/><path class="cls-1" d="M340.76,381.68s11.85,1,18.23-6.86l12.44-15.65c6-7.76,20.48-12.71,34.66-4.81A366.67,366.67,0,0,1,437,374.1c9.41,6.92,28.68,23,28.74,23,9.18,7.75,11.3,19.13,5.05,31.13,0,.07-.05.19-.05.25a129.81,129.81,0,0,1-25.89,31.88c-.12.06-.12.12-.23.18q-13.38,11.18-26.29,12.71a17.39,17.39,0,0,1-3.84.24,35,35,0,0,1-11.18-1.72l-.28-.41c-13.26-3.74-35.4-13.1-72.27-33.44a430.39,430.39,0,0,1-60.72-40.11,318.31,318.31,0,0,1-27.31-24.22l-.92-.92-.92-.92h0l-.92-.92c-.31-.3-.61-.61-.92-.92a318.31,318.31,0,0,1-24.22-27.31,430.83,430.83,0,0,1-40.11-60.71c-20.34-36.88-29.7-59-33.44-72.28l-.41-.28a35,35,0,0,1-1.71-11.18,16.87,16.87,0,0,1,.23-3.84Q141,181.42,152.12,168c.06-.11.12-.11.18-.23a129.53,129.53,0,0,1,31.88-25.88c.06,0,.18-.06.25-.06,12-6.25,23.38-4.13,31.12,5,.06.06,16.11,19.33,23,28.74a366.67,366.67,0,0,1,19.74,30.94c7.9,14.17,2.95,28.68-4.81,34.66l-15.65,12.44c-7.9,6.38-6.86,18.23-6.86,18.23S254.15,359.57,340.76,381.68Z"/></svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
17
tree.json
@@ -357,6 +357,7 @@
|
||||
"css-light.png",
|
||||
"css.png",
|
||||
"ctfreak.png",
|
||||
"cup.png",
|
||||
"cups-light.png",
|
||||
"cups.png",
|
||||
"cura.png",
|
||||
@@ -1065,6 +1066,7 @@
|
||||
"mailgun.png",
|
||||
"mailhog.png",
|
||||
"mailjet.png",
|
||||
"mailpit.png",
|
||||
"mailu.png",
|
||||
"mainsail.png",
|
||||
"maintainerr.png",
|
||||
@@ -1306,6 +1308,7 @@
|
||||
"nvidia.png",
|
||||
"nxfilter.png",
|
||||
"nxlog.png",
|
||||
"nzbgeek.png",
|
||||
"nzbget.png",
|
||||
"nzbhydra.png",
|
||||
"nzbhydra2-light.png",
|
||||
@@ -1641,6 +1644,7 @@
|
||||
"rdt-client.png",
|
||||
"reactive-resume-light.png",
|
||||
"reactive-resume.png",
|
||||
"reactjs.png",
|
||||
"readarr.png",
|
||||
"readeck.png",
|
||||
"readthedocs-light.png",
|
||||
@@ -2047,6 +2051,7 @@
|
||||
"ups.png",
|
||||
"upsnap.png",
|
||||
"uptime-kuma.png",
|
||||
"uptimerobot.png",
|
||||
"upvote-rss.png",
|
||||
"urbackup-server.png",
|
||||
"urbackup.png",
|
||||
@@ -2062,6 +2067,7 @@
|
||||
"vercel.png",
|
||||
"verizon.png",
|
||||
"vi.png",
|
||||
"viber.png",
|
||||
"victoriametrics-light.png",
|
||||
"victoriametrics.png",
|
||||
"vidzy.png",
|
||||
@@ -2519,6 +2525,7 @@
|
||||
"css-light.svg",
|
||||
"css.svg",
|
||||
"ctfreak.svg",
|
||||
"cup.svg",
|
||||
"cups-light.svg",
|
||||
"cups.svg",
|
||||
"cura.svg",
|
||||
@@ -3111,6 +3118,7 @@
|
||||
"mailfence.svg",
|
||||
"mailgun.svg",
|
||||
"mailjet.svg",
|
||||
"mailpit.svg",
|
||||
"mainsail.svg",
|
||||
"maintainerr.svg",
|
||||
"manjaro-linux.svg",
|
||||
@@ -3585,6 +3593,7 @@
|
||||
"rdt-client.svg",
|
||||
"reactive-resume-light.svg",
|
||||
"reactive-resume.svg",
|
||||
"reactjs.svg",
|
||||
"readarr.svg",
|
||||
"readeck.svg",
|
||||
"readthedocs-light.svg",
|
||||
@@ -3888,6 +3897,7 @@
|
||||
"ups.svg",
|
||||
"upsnap.svg",
|
||||
"uptime-kuma.svg",
|
||||
"uptimerobot.svg",
|
||||
"upvote-rss.svg",
|
||||
"valetudo.svg",
|
||||
"valkey.svg",
|
||||
@@ -3901,6 +3911,7 @@
|
||||
"vercel.svg",
|
||||
"verizon.svg",
|
||||
"vi.svg",
|
||||
"viber.svg",
|
||||
"victoriametrics-light.svg",
|
||||
"victoriametrics.svg",
|
||||
"vidzy.svg",
|
||||
@@ -4399,6 +4410,7 @@
|
||||
"css-light.webp",
|
||||
"css.webp",
|
||||
"ctfreak.webp",
|
||||
"cup.webp",
|
||||
"cups-light.webp",
|
||||
"cups.webp",
|
||||
"cura.webp",
|
||||
@@ -5107,6 +5119,7 @@
|
||||
"mailgun.webp",
|
||||
"mailhog.webp",
|
||||
"mailjet.webp",
|
||||
"mailpit.webp",
|
||||
"mailu.webp",
|
||||
"mainsail.webp",
|
||||
"maintainerr.webp",
|
||||
@@ -5348,6 +5361,7 @@
|
||||
"nvidia.webp",
|
||||
"nxfilter.webp",
|
||||
"nxlog.webp",
|
||||
"nzbgeek.webp",
|
||||
"nzbget.webp",
|
||||
"nzbhydra.webp",
|
||||
"nzbhydra2-light.webp",
|
||||
@@ -5683,6 +5697,7 @@
|
||||
"rdt-client.webp",
|
||||
"reactive-resume-light.webp",
|
||||
"reactive-resume.webp",
|
||||
"reactjs.webp",
|
||||
"readarr.webp",
|
||||
"readeck.webp",
|
||||
"readthedocs-light.webp",
|
||||
@@ -6089,6 +6104,7 @@
|
||||
"ups.webp",
|
||||
"upsnap.webp",
|
||||
"uptime-kuma.webp",
|
||||
"uptimerobot.webp",
|
||||
"upvote-rss.webp",
|
||||
"urbackup-server.webp",
|
||||
"urbackup.webp",
|
||||
@@ -6104,6 +6120,7 @@
|
||||
"vercel.webp",
|
||||
"verizon.webp",
|
||||
"vi.webp",
|
||||
"viber.webp",
|
||||
"victoriametrics-light.webp",
|
||||
"victoriametrics.webp",
|
||||
"vidzy.webp",
|
||||
|
||||
@@ -85,7 +85,6 @@ export async function generateMetadata({ params, searchParams }: Props, parent:
|
||||
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.`,
|
||||
},
|
||||
alternates: {
|
||||
canonical: pageUrl,
|
||||
media: {
|
||||
png: `${BASE_URL}/png/${icon}.png`,
|
||||
svg: `${BASE_URL}/svg/${icon}.svg`,
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Suspense, useEffect } from "react"
|
||||
|
||||
export function PostHogProvider({ children }: { children: React.ReactNode }) {
|
||||
useEffect(() => {
|
||||
if (process.env.DISABLE_POSTHOG === "true") return
|
||||
if (process.env.NEXT_PUBLIC_DISABLE_POSTHOG === "true") return
|
||||
// biome-ignore lint/style/noNonNullAssertion: The NEXT_PUBLIC_POSTHOG_KEY environment variable is guaranteed to be set in production.
|
||||
posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY!, {
|
||||
ui_host: "https://eu.posthog.com",
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
"use client"
|
||||
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command"
|
||||
import { useMediaQuery } from "@/hooks/use-media-query"
|
||||
import { formatIconName, fuzzySearch, filterAndSortIcons } from "@/lib/utils"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { useCallback, useEffect, useState, useMemo } from "react"
|
||||
import { filterAndSortIcons, formatIconName, fuzzySearch } from "@/lib/utils"
|
||||
import type { IconWithName } from "@/types/icons"
|
||||
import { Tag, Search as SearchIcon, Info } from "lucide-react"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Info, Search as SearchIcon, Tag } from "lucide-react"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { useCallback, useEffect, useMemo, useState } from "react"
|
||||
|
||||
interface CommandMenuProps {
|
||||
icons: IconWithName[]
|
||||
@@ -37,10 +37,7 @@ export function CommandMenu({ icons, open: externalOpen, onOpenChange: externalO
|
||||
[externalOnOpenChange],
|
||||
)
|
||||
|
||||
const filteredIcons = useMemo(() =>
|
||||
filterAndSortIcons({ icons, query, limit: 20 }),
|
||||
[icons, query]
|
||||
)
|
||||
const filteredIcons = useMemo(() => filterAndSortIcons({ icons, query, limit: 20 }), [icons, query])
|
||||
|
||||
const totalIcons = icons.length
|
||||
|
||||
@@ -70,11 +67,7 @@ export function CommandMenu({ icons, open: externalOpen, onOpenChange: externalO
|
||||
}
|
||||
|
||||
return (
|
||||
<CommandDialog
|
||||
open={isOpen}
|
||||
onOpenChange={setIsOpen}
|
||||
contentClassName="bg-background/90 backdrop-blur-sm border border-border/60"
|
||||
>
|
||||
<CommandDialog open={isOpen} onOpenChange={setIsOpen} contentClassName="bg-background/90 backdrop-blur-sm border border-border/60">
|
||||
<CommandInput
|
||||
placeholder={`Search our collection of ${totalIcons} icons by name or category...`}
|
||||
value={query}
|
||||
@@ -83,7 +76,7 @@ export function CommandMenu({ icons, open: externalOpen, onOpenChange: externalO
|
||||
<CommandList className="max-h-[300px]">
|
||||
{/* Icon Results */}
|
||||
<CommandGroup heading="Icons">
|
||||
{filteredIcons.length > 0 && (
|
||||
{filteredIcons.length > 0 &&
|
||||
filteredIcons.map(({ name, data }) => {
|
||||
const formatedIconName = formatIconName(name)
|
||||
const hasCategories = data.categories && data.categories.length > 0
|
||||
@@ -97,7 +90,9 @@ export function CommandMenu({ icons, open: externalOpen, onOpenChange: externalO
|
||||
>
|
||||
<div className="flex-shrink-0 h-5 w-5 relative">
|
||||
<div className="h-full w-full bg-primary/10 dark:bg-primary/20 rounded-md flex items-center justify-center">
|
||||
<span className="text-[9px] font-medium text-primary dark:text-primary-foreground">{name.substring(0, 2).toUpperCase()}</span>
|
||||
<span className="text-[9px] font-medium text-primary dark:text-primary-foreground">
|
||||
{name.substring(0, 2).toUpperCase()}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<span className="flex-grow capitalize font-medium text-sm">{formatedIconName}</span>
|
||||
@@ -110,9 +105,7 @@ export function CommandMenu({ icons, open: externalOpen, onOpenChange: externalO
|
||||
className="text-xs font-normal inline-flex items-center gap-1 whitespace-nowrap max-w-[120px] overflow-hidden"
|
||||
>
|
||||
<Tag size={8} className="mr-1 flex-shrink-0" />
|
||||
<span className="truncate">
|
||||
{data.categories[0].replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase())}
|
||||
</span>
|
||||
<span className="truncate">{data.categories[0].replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase())}</span>
|
||||
</Badge>
|
||||
{/* "+N" badge if more than one category */}
|
||||
{data.categories.length > 1 && (
|
||||
@@ -124,8 +117,7 @@ export function CommandMenu({ icons, open: externalOpen, onOpenChange: externalO
|
||||
)}
|
||||
</CommandItem>
|
||||
)
|
||||
})
|
||||
)}
|
||||
})}
|
||||
</CommandGroup>
|
||||
<CommandEmpty>
|
||||
{/* Minimal empty state */}
|
||||
@@ -138,12 +130,10 @@ export function CommandMenu({ icons, open: externalOpen, onOpenChange: externalO
|
||||
|
||||
{/* Separator and Browse section - Styled div outside CommandList */}
|
||||
<div className="border-t border-border/40 pt-1 mt-1 px-1 pb-1">
|
||||
<div
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className="flex items-center gap-2 cursor-pointer rounded-sm px-2 py-1 text-sm outline-none hover:bg-accent hover:text-accent-foreground focus-visible:bg-accent focus-visible:text-accent-foreground"
|
||||
<button
|
||||
type="button"
|
||||
className="flex items-center gap-2 cursor-pointer rounded-sm px-2 py-1 text-sm outline-none hover:bg-accent hover:text-accent-foreground focus-visible:bg-accent focus-visible:text-accent-foreground w-full"
|
||||
onClick={handleBrowseAll}
|
||||
onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') handleBrowseAll() }}
|
||||
>
|
||||
<div className="flex-shrink-0 h-5 w-5 relative">
|
||||
<div className="h-full w-full bg-primary/80 dark:bg-primary/40 rounded-md flex items-center justify-center">
|
||||
@@ -151,7 +141,7 @@ export function CommandMenu({ icons, open: externalOpen, onOpenChange: externalO
|
||||
</div>
|
||||
</div>
|
||||
<span className="flex-grow text-sm">Browse all icons – {totalIcons} available</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</CommandDialog>
|
||||
)
|
||||
|
||||
@@ -10,7 +10,7 @@ import { formatIconName } from "@/lib/utils"
|
||||
import type { AuthorData, Icon, IconFile } from "@/types/icons"
|
||||
import confetti from "canvas-confetti"
|
||||
import { motion } from "framer-motion"
|
||||
import { Check, Copy, Download, FileType, Github, Moon, PaletteIcon, Sun } from "lucide-react"
|
||||
import { ArrowRight, Check, Copy, Download, FileType, Github, Moon, PaletteIcon, Sun } from "lucide-react"
|
||||
import dynamic from "next/dynamic"
|
||||
import Image from "next/image"
|
||||
import Link from "next/link"
|
||||
@@ -479,31 +479,63 @@ export function IconDetails({ icon, iconData, authorData, allIcons }: IconDetail
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
{iconData.categories && iconData.categories.length > 0 && (
|
||||
<section className="container mx-auto mt-12" aria-labelledby="related-icons-title">
|
||||
<Card className="bg-background/50 border shadow-lg">
|
||||
<CardHeader>
|
||||
<CardTitle>
|
||||
<h2 id="related-icons-title">Related Icons</h2>
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Other icons from {iconData.categories.map((cat) => cat.replace(/-/g, " ")).join(", ")} categories
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<IconsGrid
|
||||
filteredIcons={Object.entries(allIcons)
|
||||
.filter(([name, data]) => {
|
||||
if (name === icon) return false
|
||||
return data.categories?.some((cat) => iconData.categories?.includes(cat))
|
||||
})
|
||||
.map(([name, data]) => ({ name, data }))}
|
||||
matchedAliases={{}}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</section>
|
||||
)}
|
||||
{iconData.categories &&
|
||||
iconData.categories.length > 0 &&
|
||||
(() => {
|
||||
const MAX_RELATED_ICONS = 16
|
||||
const currentCategories = iconData.categories || []
|
||||
|
||||
const relatedIconsWithScore = Object.entries(allIcons)
|
||||
.map(([name, data]) => {
|
||||
if (name === icon) return null // Exclude the current icon
|
||||
|
||||
const otherCategories = data.categories || []
|
||||
const commonCategories = currentCategories.filter((cat) => otherCategories.includes(cat))
|
||||
const score = commonCategories.length
|
||||
|
||||
return score > 0 ? { name, data, score } : null
|
||||
})
|
||||
.filter((item): item is { name: string; data: Icon; score: number } => item !== null) // Type guard
|
||||
.sort((a, b) => b.score - a.score) // Sort by score DESC
|
||||
|
||||
const topRelatedIcons = relatedIconsWithScore.slice(0, MAX_RELATED_ICONS)
|
||||
|
||||
const viewMoreUrl = `/icons?${currentCategories.map((cat) => `category=${encodeURIComponent(cat)}`).join("&")}`
|
||||
|
||||
if (topRelatedIcons.length === 0) return null
|
||||
|
||||
return (
|
||||
<section className="container mx-auto mt-12" aria-labelledby="related-icons-title">
|
||||
<Card className="bg-background/50 border shadow-lg">
|
||||
<CardHeader>
|
||||
<CardTitle>
|
||||
<h2 id="related-icons-title">Related Icons</h2>
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Other icons from {currentCategories.map((cat) => cat.replace(/-/g, " ")).join(", ")} categories
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<IconsGrid filteredIcons={topRelatedIcons} matchedAliases={{}} />
|
||||
{relatedIconsWithScore.length > MAX_RELATED_ICONS && (
|
||||
<div className="mt-6 text-center">
|
||||
<Button
|
||||
asChild
|
||||
variant="link"
|
||||
className="text-muted-foreground hover:text-primary transition-colors duration-200 hover:no-underline"
|
||||
>
|
||||
<Link href={viewMoreUrl} className="no-underline">
|
||||
View all related icons
|
||||
<ArrowRight className="ml-2 h-4 w-4" />
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</section>
|
||||
)
|
||||
})()}
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
} from "@/components/ui/dropdown-menu"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Separator } from "@/components/ui/separator"
|
||||
import { type SortOption, filterAndSortIcons } from "@/lib/utils"
|
||||
import type { IconSearchProps } from "@/types/icons"
|
||||
import { ArrowDownAZ, ArrowUpZA, Calendar, Filter, Search, SortAsc, X } from "lucide-react"
|
||||
import { useTheme } from "next-themes"
|
||||
@@ -24,7 +25,6 @@ import { usePathname, useRouter, useSearchParams } from "next/navigation"
|
||||
import posthog from "posthog-js"
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
|
||||
import { toast } from "sonner"
|
||||
import { filterAndSortIcons, SortOption } from "@/lib/utils"
|
||||
|
||||
export function IconSearch({ icons }: IconSearchProps) {
|
||||
const searchParams = useSearchParams()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { IconWithName } from "@/types/icons"
|
||||
import { type ClassValue, clsx } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
import type { IconWithName } from "@/types/icons"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
@@ -173,41 +173,40 @@ export function filterAndSortIcons({
|
||||
// Filter by categories if any are selected
|
||||
if (categories.length > 0) {
|
||||
filtered = filtered.filter(({ data }) =>
|
||||
data.categories.some((cat) =>
|
||||
categories.some((selectedCat) => cat.toLowerCase() === selectedCat.toLowerCase()),
|
||||
),
|
||||
data.categories.some((cat) => categories.some((selectedCat) => cat.toLowerCase() === selectedCat.toLowerCase())),
|
||||
)
|
||||
}
|
||||
|
||||
if (query.trim()) {
|
||||
const queryWords = query.toLowerCase().split(/\s+/)
|
||||
const scored = filtered.map((icon) => {
|
||||
const nameScore = fuzzySearch(icon.name, query) * NAME_WEIGHT
|
||||
const aliasScore =
|
||||
icon.data.aliases && icon.data.aliases.length > 0
|
||||
? Math.max(...icon.data.aliases.map((alias) => fuzzySearch(alias, query))) * ALIAS_WEIGHT
|
||||
: 0
|
||||
const categoryScore =
|
||||
icon.data.categories && icon.data.categories.length > 0
|
||||
? Math.max(...icon.data.categories.map((category) => fuzzySearch(category, query))) * CATEGORY_WEIGHT
|
||||
: 0
|
||||
const scored = filtered
|
||||
.map((icon) => {
|
||||
const nameScore = fuzzySearch(icon.name, query) * NAME_WEIGHT
|
||||
const aliasScore =
|
||||
icon.data.aliases && icon.data.aliases.length > 0
|
||||
? Math.max(...icon.data.aliases.map((alias) => fuzzySearch(alias, query))) * ALIAS_WEIGHT
|
||||
: 0
|
||||
const categoryScore =
|
||||
icon.data.categories && icon.data.categories.length > 0
|
||||
? Math.max(...icon.data.categories.map((category) => fuzzySearch(category, query))) * CATEGORY_WEIGHT
|
||||
: 0
|
||||
|
||||
const maxScore = Math.max(nameScore, aliasScore, categoryScore)
|
||||
const maxScore = Math.max(nameScore, aliasScore, categoryScore)
|
||||
|
||||
// Penalize if only category matches
|
||||
const onlyCategoryMatch =
|
||||
categoryScore > 0.7 && nameScore < 0.5 && aliasScore < 0.5
|
||||
const finalScore = onlyCategoryMatch ? maxScore * CATEGORY_PENALTY : maxScore
|
||||
// Penalize if only category matches
|
||||
const onlyCategoryMatch = categoryScore > 0.7 && nameScore < 0.5 && aliasScore < 0.5
|
||||
const finalScore = onlyCategoryMatch ? maxScore * CATEGORY_PENALTY : maxScore
|
||||
|
||||
// Require all query words to be present in at least one field
|
||||
const allWordsPresent = queryWords.every((word) =>
|
||||
icon.name.toLowerCase().includes(word) ||
|
||||
icon.data.aliases.some((alias) => alias.toLowerCase().includes(word)) ||
|
||||
icon.data.categories.some((cat) => cat.toLowerCase().includes(word))
|
||||
)
|
||||
// Require all query words to be present in at least one field
|
||||
const allWordsPresent = queryWords.every(
|
||||
(word) =>
|
||||
icon.name.toLowerCase().includes(word) ||
|
||||
icon.data.aliases.some((alias) => alias.toLowerCase().includes(word)) ||
|
||||
icon.data.categories.some((cat) => cat.toLowerCase().includes(word)),
|
||||
)
|
||||
|
||||
return { icon, score: allWordsPresent ? finalScore : finalScore * 0.4 }
|
||||
})
|
||||
return { icon, score: allWordsPresent ? finalScore : finalScore * 0.4 }
|
||||
})
|
||||
.filter((item) => item.score > 0.7)
|
||||
.sort((a, b) => {
|
||||
if (b.score !== a.score) return b.score - a.score
|
||||
|
||||
BIN
webp/cup.webp
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
webp/mailpit.webp
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
webp/nzbgeek.webp
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
webp/reactjs.webp
Normal file
|
After Width: | Height: | Size: 57 KiB |
BIN
webp/uptimerobot.webp
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
webp/viber.webp
Normal file
|
After Width: | Height: | Size: 36 KiB |