mirror of
https://github.com/lucide-icons/lucide.git
synced 2026-11-11 13:28:19 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
03eae6069e | ||
|
|
0991512434 | ||
|
|
68081128b3 | ||
|
|
c8d0ddd437 | ||
|
|
e9cfd3c6d3 | ||
|
|
a25efe8dba | ||
|
|
6f61f73a91 | ||
|
|
bfa381ef26 | ||
|
|
3fef7aca71 | ||
|
|
45aaec4b72 | ||
|
|
f8932f8eb8 | ||
|
|
1a3f0a85a1 | ||
|
|
adc62f5963 | ||
|
|
b3058bb8cf | ||
|
|
53fab88ff9 | ||
|
|
1a49fd88d8 | ||
|
|
b826f0c527 | ||
|
|
558bdd83b5 | ||
|
|
a805955781 | ||
|
|
ee8e10d746 | ||
|
|
2c2ee80687 | ||
|
|
1c93e0a476 | ||
|
|
91a1359b5e | ||
|
|
90e8041a35 | ||
|
|
3898742b6a | ||
|
|
a4a0e20ff5 | ||
|
|
1980e3d59b | ||
|
|
aa0b77386c | ||
|
|
7bb15b80a3 | ||
|
|
4b203ec9b6 | ||
|
|
58c85b4758 | ||
|
|
b196797ede | ||
|
|
7ebbb6abc3 | ||
|
|
5beea9bf83 | ||
|
|
c06c5f0703 | ||
|
|
588113e359 | ||
|
|
08193b1ef5 | ||
|
|
e5e55d2b65 | ||
|
|
01d130afdd | ||
|
|
dd9e311c0c | ||
|
|
5297593247 | ||
|
|
9fe2cb8792 | ||
|
|
6fbb37f7ab | ||
|
|
0669a71ff5 | ||
|
|
7515e7cd42 | ||
|
|
857a08d1f2 | ||
|
|
6446cf2b15 | ||
|
|
fc6c1ed941 | ||
|
|
a529c28a87 | ||
|
|
14cab02398 | ||
|
|
a630cec77e | ||
|
|
929b22faab | ||
|
|
6e25d21b2f | ||
|
|
5abef78c8e | ||
|
|
a03f10393c | ||
|
|
1afb1d9ee2 | ||
|
|
6489e9e4cb | ||
|
|
eb83d2fbbf |
@@ -15,17 +15,22 @@ monstera
|
||||
|
||||
# Search tags and terms used in icon and category metadata
|
||||
checkmark
|
||||
activitypub
|
||||
ageusia
|
||||
artboard
|
||||
barbeque
|
||||
choy
|
||||
cicd
|
||||
ecommerce
|
||||
enby
|
||||
fediverse
|
||||
flexbox
|
||||
futbol
|
||||
gddr
|
||||
glassmorphism
|
||||
gltf
|
||||
halloumi
|
||||
helpdesk
|
||||
interpoint
|
||||
interpunct
|
||||
middot
|
||||
|
||||
@@ -12,6 +12,6 @@
|
||||
"@octokit/rest": "^22.0.1",
|
||||
"dotenv": "^17.4.2",
|
||||
"openai": "^7.9.0",
|
||||
"zod": "^4.5.4"
|
||||
"zod": "^4.6.7"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ When checking or adding a `$group:` marker:
|
||||
|
||||
- A marker must be exactly `"$group:"` followed by the name of an existing file in `groups/`, without the `.json` extension, in lowercase kebab-case.
|
||||
- `$group:` is allowed **only** inside `tags`. It is not valid in `categories` or `contributors`.
|
||||
- Groups are named after the variant suffix: `file-plus` uses `$group:plus`, `file-question-mark` uses `$group:question-mark`. Suffixes with the same meaning share a group: `mail-warning` uses `$group:exclamation-mark`, and every currency suffix uses `$group:currency`.
|
||||
- Groups are named after the variant suffix: `file-plus` uses `$group:plus`, `file-question-mark` uses `$group:question-mark`. Suffixes with the same meaning share a group: `mail-exclamation-point` uses `$group:exclamation-mark`, and every currency suffix uses `$group:currency`.
|
||||
- The same suffix can mean different things in different contexts — `-off` means "disabled" on `wifi-off` but "allergy free" on `wheat-off` — so only use the group when the icon carries the group's meaning, and keep context-specific tags as literals.
|
||||
- Do not repeat a tag a group already provides, and do not use the same group twice in one array.
|
||||
- Conventional order: the `$extends:` base icon first, then `$group:` markers, then the icon's own literal tags.
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
@@ -95,7 +95,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
# Release metadata is generated by diffing every release tag
|
||||
fetch-depth: 0
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
# Release metadata is generated by diffing every release tag
|
||||
fetch-depth: 0
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
with:
|
||||
repository: lucide-icons/lucide
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version-file: 'package.json'
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -70,7 +70,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version-file: 'package.json'
|
||||
cache: 'pnpm'
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version-file: 'package.json'
|
||||
cache: 'pnpm'
|
||||
@@ -52,7 +52,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version-file: 'package.json'
|
||||
cache: 'pnpm'
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
run: pnpm build:font
|
||||
|
||||
- name: 'Upload to Artifacts'
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: lucide-font
|
||||
path: lucide-font
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -53,7 +53,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -57,7 +57,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -76,7 +76,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -52,7 +52,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
repository: lucide-icons/lucide
|
||||
persist-credentials: false
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
with:
|
||||
repository: lucide-icons/lucide
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
fetch-depth: 0 # Required to diff the lab icons against the previous tag
|
||||
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -126,7 +126,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
@@ -60,7 +60,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -92,13 +92,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
||||
with:
|
||||
name: lucide-font
|
||||
path: lucide-font
|
||||
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -129,7 +129,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -146,7 +146,7 @@ jobs:
|
||||
BLOB_READ_WRITE_TOKEN: ${{ secrets.BLOB_READ_WRITE_TOKEN }}
|
||||
|
||||
- name: 'Upload to Artifacts'
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: lucide-font
|
||||
path: lucide-font
|
||||
@@ -159,7 +159,7 @@ jobs:
|
||||
contents: write # Required to upload the release assets
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
||||
with:
|
||||
name: lucide-font
|
||||
path: lucide-font
|
||||
|
||||
@@ -23,8 +23,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -39,8 +39,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -55,8 +55,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
@@ -66,6 +66,7 @@ docs/.vitepress/data/categoriesData.json
|
||||
docs/.vitepress/data/iconDetails
|
||||
docs/.vitepress/data/relatedIcons.json
|
||||
docs/.vitepress/data/brandStopwords.json
|
||||
docs/.vitepress/data/sponsorsData.json
|
||||
docs/.vitepress/data/iconPopularity
|
||||
docs/.vitepress/data/lab/iconNodes
|
||||
docs/.vitepress/data/lab/iconMetaData.ts
|
||||
@@ -74,3 +75,4 @@ docs/.vercel
|
||||
docs/.nitro
|
||||
docs/public/og/**
|
||||
!docs/public/og/general.png
|
||||
.claude
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
https://lucide.dev/funding.json
|
||||
@@ -1,9 +1,9 @@
|
||||
<p align="center">
|
||||
<a href="https://github.com/lucide-icons/lucide#gh-light-mode-only">
|
||||
<img src="https://lucide.dev/lucide-logo-repo.svg#gh-light-mode-only" alt="Lucide - Beautiful & consistent icon toolkit made by the community. Open-source project and a fork of Feather Icons." width="480">
|
||||
</a>
|
||||
<a href="https://github.com/lucide-icons/lucide#gh-dark-mode-only">
|
||||
<img src="https://lucide.dev/lucide-logo-repo-dark.svg#gh-dark-mode-only" alt="Lucide - Beautiful & consistent icon toolkit made by the community. Open-source project and a fork of Feather Icons." width="480">
|
||||
<a href="https://github.com/lucide-icons/lucide">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://lucide.dev/lucide-logo-repo-dark.svg">
|
||||
<img src="https://lucide.dev/lucide-logo-repo.svg" alt="Lucide - Beautiful & consistent icon toolkit made by the community. Open-source project and a fork of Feather Icons." width="480">
|
||||
</picture>
|
||||
</a>
|
||||
</p>
|
||||
<p align="center">
|
||||
@@ -88,24 +88,29 @@ Thank you to all the people who contributed to Lucide!
|
||||
<img src="docs/public/vercel.svg" alt="Powered by Vercel" width="200" />
|
||||
</a>
|
||||
|
||||
<a href="https://www.digitalocean.com/?refcode=b0877a2caebd&utm_campaign=Referral_Invite&utm_medium=Referral_Program&utm_source=badge"><img src="docs/public/digitalocean.svg" width="200" alt="DigitalOcean Referral Badge" /></a>
|
||||
|
||||
[//]: <> (Sponsor tiers start)
|
||||
|
||||
### Hero Backer 🦸
|
||||
### Bronze 🥉
|
||||
|
||||
<a href="https://about.sourcegraph.com"><img src="https://images.opencollective.com/sourcegraph/f67b621/logo.png" height="64" alt="Sourcegraph – Understand, fix, and automate across your codebase with Sourcegraph's code intelligence platform" /></a>
|
||||
<a href="https://opencollective.com/zephyr-cloud"><img src="docs/public/sponsors/zephyr-cloud.svg" height="64" alt="Zephyr Cloud" /></a>
|
||||
<a href="https://about.sourcegraph.com"><picture><source media="(prefers-color-scheme: dark)" srcset="docs/public/sponsors/dark/sourcegraph.svg" /><img src="docs/public/sponsors/sourcegraph.svg" height="48" alt="Sourcegraph – Understand, fix, and automate across your codebase with Sourcegraph's code intelligence platform" /></picture></a>
|
||||
|
||||
<a href="https://opencollective.com/zephyr-cloud"><picture><source media="(prefers-color-scheme: dark)" srcset="docs/public/sponsors/dark/zephyr-cloud.svg" /><img src="docs/public/sponsors/zephyr-cloud.svg" height="48" alt="Zephyr Cloud" /></picture></a>
|
||||
|
||||
### Awesome backers 🍺
|
||||
[//]: <> (Main README only start)
|
||||
|
||||
<a href="https://www.paxhistoria.co"><img src="https://images.opencollective.com/pax-historia/9b4e54b/logo.png" height="64" alt="Pax Historia" /></a>
|
||||
<a href="https://pdfme.com"><img src="https://images.opencollective.com/pdfmeorg/9e0174d/logo.png" height="64" alt="pdfme – Free and Open source PDF generation library!" /></a>
|
||||
### Backers 🍺
|
||||
|
||||
### Backer ☕️
|
||||
<a href="https://www.paxhistoria.co"><img src="https://images.opencollective.com/pax-historia/9b4e54b/logo.png" height="32" alt="Pax Historia" /></a>
|
||||
|
||||
<a href="https://pdfme.com"><img src="https://images.opencollective.com/pdfmeorg/9e0174d/logo.png" height="32" alt="pdfme – Free and Open source PDF generation library!" /></a>
|
||||
|
||||
<a href="https://www.fina.money"><img src="docs/public/sponsors/fina-money.png" height="64" alt="Fina Money – Build the most flexible finance tracker" /></a>
|
||||
<a href="https://opencollective.com/rjvdw"><img src="https://images.opencollective.com/rjvdw/28bc9cd/avatar.png" height="64" alt="Ruud" /></a>
|
||||
### Supporters ☕️
|
||||
|
||||
<a href="https://www.fina.money"><img src="https://images.opencollective.com/fina-money/7c1f196/logo.png" height="32" alt="Fina Money – Build the most flexible finance tracker" /></a>
|
||||
|
||||
<a href="https://opencollective.com/rjvdw"><img src="https://images.opencollective.com/rjvdw/28bc9cd/avatar.png" height="32" alt="Ruud" /></a>
|
||||
|
||||
[//]: <> (Main README only end)
|
||||
|
||||
[//]: <> (Sponsor tiers end)
|
||||
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
"$schema": "../category.schema.json",
|
||||
"title": "Notifications",
|
||||
"description": "Icons for alerts, status updates, reminders, approvals, and attention states.",
|
||||
"icon": "triangle-alert"
|
||||
"icon": "triangle-exclamation-point"
|
||||
}
|
||||
|
||||
@@ -88,6 +88,7 @@ export default defineConfig({
|
||||
'index.md',
|
||||
'packages.md',
|
||||
'showcase.md',
|
||||
'sponsor.md',
|
||||
'brand-logo-statement.md',
|
||||
'icons/**', // Not working, need investigation
|
||||
],
|
||||
@@ -123,6 +124,9 @@ export default defineConfig({
|
||||
socialLinks: [
|
||||
{ icon: 'github', link: 'https://github.com/lucide-icons/lucide' },
|
||||
{ icon: 'discord', link: 'https://discord.gg/EH6nSts' },
|
||||
{ icon: 'x', link: 'https://x.com/lucide_icons' },
|
||||
{ icon: 'linkedin', link: 'https://www.linkedin.com/company/lucide-icons/' },
|
||||
{ icon: 'bluesky', link: 'https://bsky.app/profile/lucide-icons.bsky.social' },
|
||||
],
|
||||
footer: {
|
||||
message: 'Released under the ISC License.',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[
|
||||
{
|
||||
"name": "Eric Fennis",
|
||||
"title": "Creator of Lucide & Software engineer @nedap",
|
||||
"title": "Creator & Maintainer of Lucide",
|
||||
"image": "https://github.com/ericfennis.png?size=192",
|
||||
"sponsor": "https://github.com/sponsors/ericfennis",
|
||||
"socialLinks": [
|
||||
@@ -9,9 +9,17 @@
|
||||
"icon": "github",
|
||||
"link": "https://github.com/ericfennis"
|
||||
},
|
||||
{
|
||||
"icon": "linkedin",
|
||||
"link": "https://www.linkedin.com/in/ericfennis"
|
||||
},
|
||||
{
|
||||
"icon": "x",
|
||||
"link": "https://x.com/ericfennis"
|
||||
},
|
||||
{
|
||||
"icon": "linkedin",
|
||||
"link": "https://www.linkedin.com/in/ericfennis"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -32,7 +40,7 @@
|
||||
},
|
||||
{
|
||||
"name": "Jakob Guddas",
|
||||
"title": "Maintainer of Lucide & Software engineer @LEGO",
|
||||
"title": "Creator of Lucide Studio & Software engineer @LEGO",
|
||||
"image": "https://github.com/jguddas.png?size=192",
|
||||
"socialLinks": [
|
||||
{
|
||||
|
||||
@@ -79,3 +79,23 @@ export default async function getStructuredData(iconName: string, pageData: Page
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export interface FaqEntry {
|
||||
question: string;
|
||||
answer: string;
|
||||
}
|
||||
|
||||
export function getFaqStructuredData(faq: FaqEntry[]) {
|
||||
return {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'FAQPage',
|
||||
mainEntity: faq.map(({ question, answer }) => ({
|
||||
'@type': 'Question',
|
||||
name: question,
|
||||
acceptedAnswer: {
|
||||
'@type': 'Answer',
|
||||
text: answer,
|
||||
},
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ const rel = computed(() => {
|
||||
|
||||
<style scoped>
|
||||
.card {
|
||||
border: 1px solid var(--vp-c-bg-soft);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 12px;
|
||||
background-color: var(--vp-c-bg-soft);
|
||||
display: flex;
|
||||
@@ -52,5 +52,4 @@ const rel = computed(() => {
|
||||
.card[href]:hover {
|
||||
border-color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -79,6 +79,15 @@ const model = computed({
|
||||
border: 1px solid var(--vp-input-border-color);
|
||||
background-color: var(--vp-input-switch-bg-color);
|
||||
border-radius: 4px;
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
transition-duration: 0.2s;
|
||||
}
|
||||
|
||||
.checkbox:hover,
|
||||
.checkbox:focus-visible {
|
||||
outline: none;
|
||||
border-color: var(--vp-c-brand);
|
||||
box-shadow: 0 0 0 3px color-mix(in srgb, var(--vp-c-brand) 20%, transparent);
|
||||
}
|
||||
|
||||
.checkbox:checked {
|
||||
@@ -87,4 +96,8 @@ const model = computed({
|
||||
center no-repeat var(--vp-c-brand);;
|
||||
}
|
||||
|
||||
.checkbox-wrapper:hover .checkbox-label {
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup>
|
||||
import Icon from '@lucide/vue/src/Icon';
|
||||
import { search } from '../../../data/iconNodes';
|
||||
import Kbd from './Kbd.vue';
|
||||
|
||||
defineProps({
|
||||
shortcut: {
|
||||
@@ -17,10 +18,10 @@ defineProps({
|
||||
class="search-icon"
|
||||
/>
|
||||
<slot />
|
||||
<kbd
|
||||
<Kbd
|
||||
v-if="shortcut"
|
||||
class="shortcut"
|
||||
>{{ shortcut }}</kbd
|
||||
>{{ shortcut }}</Kbd
|
||||
>
|
||||
</button>
|
||||
</template>
|
||||
@@ -35,32 +36,43 @@ defineProps({
|
||||
font-size: 14px;
|
||||
/* box-shadow: var(--vp-shadow-4), var(--vp-shadow-2); */
|
||||
text-align: left;
|
||||
border: 1px solid transparent;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
cursor: text;
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
transition:
|
||||
color 0.25s,
|
||||
border-color 0.25s,
|
||||
background-color 0.25s;
|
||||
background-color 0.25s,
|
||||
box-shadow 0.25s;
|
||||
transition-duration: 0.25s;
|
||||
}
|
||||
|
||||
/* A quiet border at rest; on hover the border takes the brand colour with a soft ring,
|
||||
and the search icon tilts as if it starts looking */
|
||||
.fake-input:hover,
|
||||
.fake-input:focus {
|
||||
.fake-input:focus-visible {
|
||||
outline: none;
|
||||
color: var(--vp-c-text-1);
|
||||
border-color: var(--vp-c-brand);
|
||||
box-shadow: 0 0 0 3px color-mix(in srgb, var(--vp-c-brand) 15%, transparent);
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
flex-shrink: 0;
|
||||
transition:
|
||||
color 0.25s,
|
||||
transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
transition-duration: 0.25s, 0.3s;
|
||||
}
|
||||
|
||||
.fake-input:hover .search-icon,
|
||||
.fake-input:focus-visible .search-icon {
|
||||
color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.shortcut {
|
||||
margin-left: auto;
|
||||
padding: 2px 6px;
|
||||
font-size: 12px;
|
||||
font-family: inherit;
|
||||
font-weight: 500;
|
||||
line-height: 1.5;
|
||||
color: var(--vp-c-text-3);
|
||||
background: var(--vp-c-default-soft);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
@media (hover: none) {
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { useData } from 'vitepress';
|
||||
import FaqItem from './FaqItem.vue';
|
||||
|
||||
interface FaqEntry {
|
||||
question: string;
|
||||
answer: string;
|
||||
}
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
title?: string;
|
||||
headingLevel?: 1 | 2 | 3 | 4 | 5 | 6;
|
||||
/** Defaults to the page's `faq` frontmatter, which also feeds the FAQPage structured data. */
|
||||
items?: FaqEntry[];
|
||||
}>(),
|
||||
{
|
||||
headingLevel: 2,
|
||||
},
|
||||
);
|
||||
|
||||
const { frontmatter } = useData();
|
||||
|
||||
const entries = computed<FaqEntry[]>(() => props.items ?? frontmatter.value.faq ?? []);
|
||||
const headingElement = computed(() => `h${props.headingLevel}`);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="faq">
|
||||
<component
|
||||
v-if="title"
|
||||
:is="headingElement"
|
||||
class="title"
|
||||
>
|
||||
{{ title }}
|
||||
</component>
|
||||
<div class="items">
|
||||
<FaqItem
|
||||
v-for="entry in entries"
|
||||
:key="entry.question"
|
||||
:question="entry.question"
|
||||
:answer="entry.answer"
|
||||
/>
|
||||
<slot />
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.faq {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0;
|
||||
font-size: 32px;
|
||||
line-height: 40px;
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.02em;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.items {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-top: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,74 @@
|
||||
<script setup lang="ts">
|
||||
import { chevronDown } from '../../../data/iconNodes';
|
||||
import Icon from '@lucide/vue/src/Icon';
|
||||
|
||||
defineProps<{
|
||||
question: string;
|
||||
answer?: string;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<details class="faq-item">
|
||||
<summary class="question">
|
||||
{{ question }}
|
||||
<Icon
|
||||
:iconNode="chevronDown"
|
||||
name="chevron-down"
|
||||
:size="20"
|
||||
class="chevron"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</summary>
|
||||
<div class="answer">
|
||||
<slot>
|
||||
<p>{{ answer }}</p>
|
||||
</slot>
|
||||
</div>
|
||||
</details>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.faq-item {
|
||||
border-bottom: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.question {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
min-height: 56px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-1);
|
||||
list-style: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.question::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.chevron {
|
||||
flex-shrink: 0;
|
||||
color: var(--vp-c-text-2);
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.faq-item[open] .chevron {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.answer {
|
||||
padding-bottom: 20px;
|
||||
font-size: 16px;
|
||||
line-height: 28px;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.answer :slotted(p),
|
||||
.answer p {
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -15,6 +15,7 @@ import { ref, onMounted, nextTick, watch } from 'vue';
|
||||
import Icon from '@lucide/vue/src/Icon';
|
||||
import { x } from '../../../data/iconNodes';
|
||||
import Button from './Button.vue';
|
||||
import Kbd from './Kbd.vue';
|
||||
|
||||
const props = withDefaults(defineProps<InputProps>(), {
|
||||
type: 'text',
|
||||
@@ -29,7 +30,7 @@ const emit = defineEmits(['change', 'input', 'update:modelValue']);
|
||||
const updateShortcutSpacing = () => {
|
||||
nextTick(() => {
|
||||
if (shortcutEl.value && wrapperEl.value) {
|
||||
const shortcutWidth = shortcutEl.value.offsetWidth;
|
||||
const shortcutWidth = shortcutEl.value.$el.offsetWidth;
|
||||
wrapperEl.value.style.setProperty('--shortcut-width', `${shortcutWidth}px`);
|
||||
}
|
||||
});
|
||||
@@ -83,11 +84,11 @@ defineExpose({
|
||||
:size="20"
|
||||
/>
|
||||
</Button>
|
||||
<kbd
|
||||
<Kbd
|
||||
v-if="shortcut"
|
||||
class="shortcut"
|
||||
ref="shortcutEl"
|
||||
>{{ shortcut }}</kbd
|
||||
>{{ shortcut }}</Kbd
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
@@ -99,7 +100,7 @@ defineExpose({
|
||||
|
||||
.input {
|
||||
justify-content: flex-start;
|
||||
border: 1px solid transparent;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
padding: 0 10px 0 12px;
|
||||
width: 100%;
|
||||
@@ -109,16 +110,21 @@ defineExpose({
|
||||
transition:
|
||||
color 0.25s,
|
||||
border-color 0.25s,
|
||||
background-color 0.25s;
|
||||
background-color 0.25s,
|
||||
box-shadow 0.25s;
|
||||
transition-duration: 0.25s;
|
||||
}
|
||||
|
||||
.input.has-shortcut {
|
||||
padding-right: calc(var(--shortcut-width, 40px) + 22px);
|
||||
}
|
||||
|
||||
/* Same as FakeInput: a quiet border at rest, a brand border with a soft ring on hover and focus */
|
||||
.input:hover,
|
||||
.input:focus {
|
||||
outline: none;
|
||||
border-color: var(--vp-c-brand);
|
||||
box-shadow: 0 0 0 3px color-mix(in srgb, var(--vp-c-brand) 15%, transparent);
|
||||
}
|
||||
.input:focus {
|
||||
background-color: var(--vp-c-bg);
|
||||
@@ -141,33 +147,24 @@ defineExpose({
|
||||
right: 12px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
padding: 2px 6px;
|
||||
font-size: 12px;
|
||||
font-family: inherit;
|
||||
font-weight: 500;
|
||||
line-height: 1.5;
|
||||
color: var(--vp-c-text-3);
|
||||
background: var(--vp-c-default-soft);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 4px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.input[type="search"]::-webkit-search-cancel-button {
|
||||
.input[type='search']::-webkit-search-cancel-button {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.input[type="search"]::-ms-clear {
|
||||
.input[type='search']::-ms-clear {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.input[type="search"]::-o-clear {
|
||||
.input[type='search']::-o-clear {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.input[type="search"]::-moz-clear {
|
||||
.input[type='search']::-moz-clear {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -190,8 +187,15 @@ defineExpose({
|
||||
z-index: 1;
|
||||
color: var(--vp-c-text-2);
|
||||
pointer-events: none;
|
||||
transition:
|
||||
color 0.25s,
|
||||
transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
transition-duration: 0.25s, 0.3s;
|
||||
}
|
||||
|
||||
/* Like the FakeInput search icon, it tilts as if it starts looking */
|
||||
.input-wrapper > svg:has(+ .input.has-icon:hover),
|
||||
.input-wrapper > svg:has(+ .input.has-icon:focus) {
|
||||
color: var(--vp-c-text-1);
|
||||
color: var(--vp-c-brand-1);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,18 +1,28 @@
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
label: string
|
||||
id: string
|
||||
}>()
|
||||
label: string;
|
||||
id: string;
|
||||
class?: string;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="input-field">
|
||||
<div class="input-label" v-if="label">
|
||||
<label :for="id" class="customize-label">
|
||||
<div
|
||||
class="input-field"
|
||||
:class="class"
|
||||
>
|
||||
<div
|
||||
class="input-label"
|
||||
v-if="label"
|
||||
>
|
||||
<label
|
||||
:for="id"
|
||||
class="customize-label"
|
||||
>
|
||||
{{ label }}
|
||||
</label>
|
||||
<div class="display-value" >
|
||||
<slot name="display"/>
|
||||
<div class="display-value">
|
||||
<slot name="display" />
|
||||
</div>
|
||||
</div>
|
||||
<slot />
|
||||
@@ -25,7 +35,7 @@ defineProps<{
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--vt-c-text-1);
|
||||
transition: color .5s;
|
||||
transition: color 0.5s;
|
||||
display: block;
|
||||
}
|
||||
|
||||
@@ -43,6 +53,6 @@ defineProps<{
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
gap: 12px;
|
||||
gap: 6px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<kbd class="kbd"><slot /></kbd>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.kbd {
|
||||
display: inline-block;
|
||||
min-width: 24px;
|
||||
padding: 2px 6px;
|
||||
font-family: inherit;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
line-height: 1.5;
|
||||
text-align: center;
|
||||
color: var(--vp-c-text-2);
|
||||
background: var(--vp-c-default-soft);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,71 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeUnmount, ref, watch } from 'vue';
|
||||
import { animate, useInView, useReducedMotion } from 'motion-v';
|
||||
|
||||
/**
|
||||
* Springs a number up from 0 once it scrolls into view.
|
||||
* Ported from motion-counter by Tanja Gomilar (MIT): https://github.com/tgomilar/motion-components
|
||||
*/
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
value: number;
|
||||
suffix?: string;
|
||||
/** Spring duration, in seconds. */
|
||||
duration?: number;
|
||||
/** Delay before counting starts, in seconds. */
|
||||
delay?: number;
|
||||
}>(),
|
||||
{
|
||||
suffix: '',
|
||||
duration: 1.5,
|
||||
delay: 0,
|
||||
},
|
||||
);
|
||||
|
||||
const el = ref<HTMLElement>();
|
||||
const inView = useInView(el, { once: true, amount: 0.2 });
|
||||
const reducedMotion = useReducedMotion();
|
||||
const current = ref(0);
|
||||
|
||||
const format = (value: number) => `${Math.round(value).toLocaleString('en-US')}${props.suffix}`;
|
||||
const finalText = computed(() => format(props.value));
|
||||
|
||||
let controls: ReturnType<typeof animate> | undefined;
|
||||
|
||||
watch(inView, (isInView) => {
|
||||
if (!isInView) return;
|
||||
|
||||
if (reducedMotion.value) {
|
||||
current.value = props.value;
|
||||
return;
|
||||
}
|
||||
|
||||
controls = animate(0, props.value, {
|
||||
type: 'spring',
|
||||
duration: props.duration,
|
||||
delay: props.delay,
|
||||
bounce: 0.05,
|
||||
onUpdate: (latest) => {
|
||||
current.value = latest;
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => controls?.stop());
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span
|
||||
ref="el"
|
||||
class="motion-counter"
|
||||
>
|
||||
<span class="visually-hidden">{{ finalText }}</span>
|
||||
<span aria-hidden="true">{{ format(current) }}</span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.motion-counter {
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
</style>
|
||||
@@ -33,12 +33,45 @@ defineProps<{
|
||||
padding: 11px 16px 13px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transition: border-color 0.25s;
|
||||
transition: border-color 0.25s, background-color 0.25s, transform 0.25s ease, box-shadow 0.25s;
|
||||
transition-duration: 0.25s;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.overview-link:hover {
|
||||
/* On hover the card lifts with a light brand tint, like the previous / next page links,
|
||||
and Lucide's arrow-right icon slides in after the title */
|
||||
.overview-link:hover,
|
||||
.overview-link:focus-visible {
|
||||
outline: none;
|
||||
border-color: var(--vp-c-brand-1);
|
||||
transform: translateY(-2px);
|
||||
background-color: color-mix(in srgb, var(--vp-c-brand) 6%, transparent);
|
||||
box-shadow: var(--vp-shadow-2);
|
||||
}
|
||||
|
||||
.overview-link .title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.overview-link .title::after {
|
||||
content: '';
|
||||
flex-shrink: 0;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-color: currentColor;
|
||||
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12h14'/%3E%3Cpath d='m12 5 7 7-7 7'/%3E%3C/svg%3E") center / contain no-repeat;
|
||||
opacity: 0;
|
||||
transform: translateX(-6px);
|
||||
transition: opacity 0.25s, transform 0.25s ease;
|
||||
transition-duration: 0.25s;
|
||||
}
|
||||
|
||||
.overview-link:hover .title::after,
|
||||
.overview-link:focus-visible .title::after {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.desc {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TabGroup>
|
||||
<TabGroup as="div">
|
||||
<TabList class="tab-list">
|
||||
<template v-for="(tab, index) in props.tabs" :key="index">
|
||||
<Tab :id="`tab-${index}`" class="tab">{{ tab }}</Tab>
|
||||
|
||||
@@ -111,6 +111,36 @@ const percentage = computed<string>(() => `${((Number(props.modelValue) - props.
|
||||
border-radius: 10px;
|
||||
box-shadow: var(--vp-shadow-2);
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
transition-duration: 0.2s;
|
||||
}
|
||||
|
||||
.slider input::-moz-range-thumb {
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
transition-duration: 0.2s;
|
||||
}
|
||||
|
||||
/* The thumb grows with a soft brand ring on hover and focus, and a little more while dragged */
|
||||
.slider input:hover::-webkit-slider-thumb,
|
||||
.slider input:focus-visible::-webkit-slider-thumb {
|
||||
transform: scale(1.1);
|
||||
box-shadow: var(--vp-shadow-2), 0 0 0 5px color-mix(in srgb, var(--vp-c-brand) 25%, transparent);
|
||||
}
|
||||
|
||||
.slider input:active::-webkit-slider-thumb {
|
||||
transform: scale(1.2);
|
||||
box-shadow: var(--vp-shadow-2), 0 0 0 7px color-mix(in srgb, var(--vp-c-brand) 25%, transparent);
|
||||
}
|
||||
|
||||
.slider input:hover::-moz-range-thumb,
|
||||
.slider input:focus-visible::-moz-range-thumb {
|
||||
transform: scale(1.1);
|
||||
box-shadow: var(--vp-shadow-2), 0 0 0 5px color-mix(in srgb, var(--vp-c-brand) 25%, transparent);
|
||||
}
|
||||
|
||||
.slider input:active::-moz-range-thumb {
|
||||
transform: scale(1.2);
|
||||
box-shadow: var(--vp-shadow-2), 0 0 0 7px color-mix(in srgb, var(--vp-c-brand) 25%, transparent);
|
||||
}
|
||||
|
||||
.slider input::-moz-range-thumb {
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { rotateCw } from '../../../data/iconNodes';
|
||||
import Icon from '@lucide/vue/src/Icon';
|
||||
import Button from './Button.vue';
|
||||
|
||||
const spinning = ref(false);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Button class="reset-button" aria-label="Reset style">
|
||||
<Button
|
||||
class="reset-button"
|
||||
:class="{ spinning }"
|
||||
aria-label="Reset style"
|
||||
@click="spinning = true"
|
||||
@animationend="spinning = false"
|
||||
>
|
||||
<Icon
|
||||
:size="20"
|
||||
:iconNode="rotateCw"
|
||||
@@ -16,13 +25,16 @@ import Button from './Button.vue';
|
||||
|
||||
<style scoped>
|
||||
.reset-button {
|
||||
transition: ease-in-out 0.1s opacity;
|
||||
transition: ease-in-out 0.25s opacity;
|
||||
transition-duration: 0.25s;
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.reset-button .lucide {
|
||||
transition: ease-in-out 0.1s transform;
|
||||
/* The arrow spins once in place when clicked */
|
||||
.reset-button.spinning .lucide {
|
||||
animation: rotate 0.6s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
animation-duration: 0.6s !important;
|
||||
}
|
||||
|
||||
.reset-button:hover {
|
||||
@@ -36,18 +48,13 @@ import Button from './Button.vue';
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/* a rotate css animation keyframes */
|
||||
@keyframes rotate {
|
||||
0% {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(359deg);
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.reset-button:active .lucide {
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -36,6 +36,8 @@ const selected = defineModel<{ name: string; value: string; icon?: string; iconD
|
||||
</ListboxButton>
|
||||
|
||||
<transition
|
||||
enter-active-class="transition-enter"
|
||||
enter-from-class="transition-enter-from"
|
||||
leave-active-class="transition-leave"
|
||||
leave-from-class="transition-leave-from"
|
||||
leave-to-class="transition-leave-to"
|
||||
@@ -83,20 +85,33 @@ const selected = defineModel<{ name: string; value: string; icon?: string; iconD
|
||||
padding: 7px 14px;
|
||||
height: auto;
|
||||
font-size: 14px;
|
||||
border: 1px solid transparent;
|
||||
cursor: text;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
transition:
|
||||
color 0.25s,
|
||||
border-color 0.25s,
|
||||
background-color 0.25s;
|
||||
background-color 0.25s,
|
||||
box-shadow 0.25s;
|
||||
transition-duration: 0.25s;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.select-button:focus {
|
||||
/* Same as FakeInput: a quiet border at rest, a brand border with a soft ring on hover, focus
|
||||
and while open, and the chevrons take the text colour */
|
||||
.select-button:hover,
|
||||
.select-button:focus,
|
||||
.select-button[aria-expanded='true'] {
|
||||
outline: none;
|
||||
border-color: var(--vp-c-brand);
|
||||
box-shadow: 0 0 0 3px color-mix(in srgb, var(--vp-c-brand) 15%, transparent);
|
||||
}
|
||||
|
||||
.select-button:hover .chevron-icon,
|
||||
.select-button[aria-expanded='true'] .chevron-icon {
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.select-text {
|
||||
@@ -121,10 +136,26 @@ const selected = defineModel<{ name: string; value: string; icon?: string; iconD
|
||||
height: 1.25rem;
|
||||
width: 1.25rem;
|
||||
color: #9ca3af;
|
||||
transition: color 0.25s;
|
||||
transition-duration: 0.25s;
|
||||
}
|
||||
|
||||
/* The list fades in and slides down a little from the button */
|
||||
.transition-enter {
|
||||
transition:
|
||||
opacity 150ms ease-out,
|
||||
transform 200ms cubic-bezier(0.34, 1.4, 0.64, 1);
|
||||
transition-duration: 150ms, 200ms;
|
||||
}
|
||||
|
||||
.transition-enter-from {
|
||||
opacity: 0;
|
||||
transform: translateY(-6px) scale(0.98);
|
||||
}
|
||||
|
||||
.transition-leave {
|
||||
transition: opacity 100ms ease-in;
|
||||
transition-duration: 100ms;
|
||||
}
|
||||
|
||||
.transition-leave-from {
|
||||
@@ -172,6 +203,7 @@ const selected = defineModel<{ name: string; value: string; icon?: string; iconD
|
||||
transition:
|
||||
background-color 0.25s,
|
||||
color 0.25s;
|
||||
transition-duration: 0.25s;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
border: 1px solid var(--vp-input-border-color);
|
||||
background-color: var(--vp-input-switch-bg-color);
|
||||
transition: border-color 0.25s, background-color 0.4s ease;
|
||||
transition-duration: 0.25s, 0.4s;
|
||||
border-radius: 11px;
|
||||
}
|
||||
|
||||
@@ -56,21 +57,51 @@
|
||||
border-color: var(--vp-input-hover-border-color);
|
||||
}
|
||||
|
||||
.switch:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* The thumb sits 2px inside the track and slides with a small bounce. Like the RangeSlider
|
||||
thumb, it grows with a soft brand ring on hover and focus, and a little more while pressed */
|
||||
.thumb {
|
||||
display: inline-block;
|
||||
margin: 2px;
|
||||
background-color: #fff;
|
||||
transition: transform 0.25s;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
transition:
|
||||
transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1),
|
||||
box-shadow 0.2s ease;
|
||||
transition-duration: 0.4s, 0.2s;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 8px;
|
||||
/* background-color: var(--vp-c-neutral); */
|
||||
box-shadow: var(--vp-shadow-1);
|
||||
}
|
||||
|
||||
.switch:hover .thumb,
|
||||
.switch:focus-visible .thumb {
|
||||
transform: scale(1.1);
|
||||
box-shadow: var(--vp-shadow-1), 0 0 0 5px color-mix(in srgb, var(--vp-c-brand) 25%, transparent);
|
||||
}
|
||||
|
||||
.switch:active .thumb {
|
||||
transform: scale(1.2);
|
||||
box-shadow: var(--vp-shadow-1), 0 0 0 7px color-mix(in srgb, var(--vp-c-brand) 25%, transparent);
|
||||
}
|
||||
|
||||
.switch.enabled .thumb {
|
||||
transform: translateX(18px);
|
||||
}
|
||||
|
||||
.switch.enabled:hover .thumb,
|
||||
.switch.enabled:focus-visible .thumb {
|
||||
transform: translateX(18px) scale(1.1);
|
||||
}
|
||||
|
||||
.switch.enabled:active .thumb {
|
||||
transform: translateX(18px) scale(1.2);
|
||||
}
|
||||
|
||||
/* ------------------- */
|
||||
.VPSwitch {
|
||||
position: relative;
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
<script setup lang="ts">
|
||||
import { Toaster } from 'vue-sonner';
|
||||
import 'vue-sonner/style.css';
|
||||
import { useData } from 'vitepress';
|
||||
import Icon from '@lucide/vue/src/Icon.ts';
|
||||
import { check } from '../../../data/iconNodes';
|
||||
|
||||
const { isDark } = useData();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ClientOnly>
|
||||
<Toaster
|
||||
position="bottom-center"
|
||||
:theme="isDark ? 'dark' : 'light'"
|
||||
>
|
||||
<template #success-icon>
|
||||
<Icon
|
||||
:iconNode="check"
|
||||
name="check"
|
||||
:size="24"
|
||||
/>
|
||||
</template>
|
||||
</Toaster>
|
||||
</ClientOnly>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
/* Same surface as base/Card.vue. The extra :root outranks sonner's per-theme rules */
|
||||
:root [data-sonner-toaster][data-sonner-theme] {
|
||||
--normal-bg: var(--vp-c-bg-soft);
|
||||
--normal-border: var(--vp-c-divider);
|
||||
--normal-text: var(--vp-c-text-1);
|
||||
--border-radius: 12px;
|
||||
}
|
||||
|
||||
/* Keep the icon and the text on one centred line; the site's line-height pushes the text down */
|
||||
[data-sonner-toast] {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
[data-sonner-toast] [data-icon] {
|
||||
align-self: center;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* Sonner's icon box is 16px, so match the 24px check to stop it overflowing into the text */
|
||||
[data-sonner-toast][data-styled='true'][data-type='success'] [data-icon] {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
color: var(--vp-c-success-1);
|
||||
}
|
||||
|
||||
[data-sonner-toast] [data-content],
|
||||
[data-sonner-toast] [data-title] {
|
||||
line-height: 20px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,8 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, shallowRef } from 'vue';
|
||||
import { onMounted, ref, shallowRef, type DefineComponent } from 'vue';
|
||||
import { data } from './HomeHeroIconsCard.data';
|
||||
import { useRouter } from 'vitepress';
|
||||
import { motion, useScroll, useTransform, type Options as MotionOptions } from 'motion-v';
|
||||
import { useMediaQuery } from '@vueuse/core';
|
||||
import LucideIcon from '../base/LucideIcon.vue';
|
||||
|
||||
// motion-v re-exports framer-motion's `Variants`, which doesn't match the `variants` prop of its components
|
||||
@@ -10,7 +11,8 @@ type Variants = MotionOptions['variants'];
|
||||
|
||||
const emit = defineEmits(['animation-complete']);
|
||||
|
||||
const MotionLucideIcon = motion.create(LucideIcon);
|
||||
// motion.create only accepts a props-less DefineComponent, so the SFC's typed props need widening
|
||||
const MotionLucideIcon = motion.create(LucideIcon as unknown as DefineComponent);
|
||||
|
||||
const preventFocusProps = {
|
||||
tabindex: '-1',
|
||||
@@ -21,6 +23,89 @@ const COLUMNS = 8;
|
||||
const SIZE = 2;
|
||||
const GAP = 1;
|
||||
|
||||
/** The two spirals of the Lucide logo, drawn the way the SVG editor previews them. */
|
||||
const LOGO_PATH_OUTER =
|
||||
'M14 12C14 9.79086 12.2091 8 10 8C7.79086 8 6 9.79086 6 12C6 16.4183 9.58172 20 14 20C18.4183 20 22 16.4183 22 12C22 8.446 20.455 5.25285 18 3.05557';
|
||||
|
||||
const LOGO_PATH_INNER =
|
||||
'M10 12C10 14.2091 11.7909 16 14 16C16.2091 16 18 14.2091 18 12C18 7.58172 14.4183 4 10 4C5.58172 4 2 7.58172 2 12C2 15.5841 3.57127 18.8012 6.06253 21';
|
||||
|
||||
const VERTICES =
|
||||
'M14 12h.01M10 8h.01M6 12h.01M14 20h.01M22 12h.01M18 3.05557h.01M10 12h.01M14 16h.01M18 12h.01M10 4h.01M2 12h.01M6.06253 21h.01';
|
||||
|
||||
const MARKERS = [
|
||||
[14, 12],
|
||||
[18, 3.05557],
|
||||
[10, 12],
|
||||
[6.06253, 21],
|
||||
];
|
||||
|
||||
/** Bezier handles as [x1, y1, x2, y2] segments, grouped by when they appear. */
|
||||
const HANDLE_GROUPS = [
|
||||
// Outer spiral
|
||||
{ delay: 0.2, segments: [[14, 12, 14, 9.79086]] },
|
||||
{
|
||||
delay: 0.4,
|
||||
segments: [
|
||||
[10, 8, 12.2091, 8],
|
||||
[10, 8, 7.79086, 8],
|
||||
],
|
||||
},
|
||||
{
|
||||
delay: 0.6,
|
||||
segments: [
|
||||
[6, 12, 6, 9.79086],
|
||||
[6, 12, 6, 16.4183],
|
||||
],
|
||||
},
|
||||
{
|
||||
delay: 0.8,
|
||||
segments: [
|
||||
[14, 20, 9.58172, 20],
|
||||
[14, 20, 18.4183, 20],
|
||||
],
|
||||
},
|
||||
{
|
||||
delay: 1,
|
||||
segments: [
|
||||
[22, 12, 22, 16.4183],
|
||||
[22, 12, 22, 8.446],
|
||||
],
|
||||
},
|
||||
{ delay: 1.2, segments: [[18, 3.05557, 20.455, 5.25285]] },
|
||||
// Inner spiral
|
||||
{ delay: 0.2, segments: [[10, 12, 10, 14.2091]] },
|
||||
{
|
||||
delay: 0.4,
|
||||
segments: [
|
||||
[14, 16, 11.7909, 16],
|
||||
[14, 16, 16.2091, 16],
|
||||
],
|
||||
},
|
||||
{
|
||||
delay: 0.6,
|
||||
segments: [
|
||||
[18, 12, 18, 14.2091],
|
||||
[18, 12, 18, 7.58172],
|
||||
],
|
||||
},
|
||||
{
|
||||
delay: 0.8,
|
||||
segments: [
|
||||
[10, 4, 14.4183, 4],
|
||||
[10, 4, 5.58172, 4],
|
||||
],
|
||||
},
|
||||
{
|
||||
delay: 1,
|
||||
segments: [
|
||||
[2, 12, 2, 7.58172],
|
||||
[2, 12, 2, 15.5841],
|
||||
],
|
||||
},
|
||||
{ delay: 1.2, segments: [[6.06253, 21, 3.57127, 18.8012]] },
|
||||
];
|
||||
|
||||
const { scrollYProgress } = useScroll();
|
||||
const opacity = useTransform(() => 1 - scrollYProgress.get() * 8);
|
||||
|
||||
@@ -101,6 +186,9 @@ const draw: Variants = {
|
||||
|
||||
// motion-v passes the variant label that finished, not the resolved variant
|
||||
const onAnimationComplete = (variant: unknown) => {
|
||||
// Reduced motion stops at the icon grid, so don't shrink the icons or loop
|
||||
if (reducedMotion.value) return;
|
||||
|
||||
if (variant === 'visible') {
|
||||
drawAnimation.value = 'exit';
|
||||
return;
|
||||
@@ -155,6 +243,24 @@ const shrinkIconVariants: Variants = {
|
||||
transition: { delay: 1.8, duration: 1.5, ease: 'easeInOut' },
|
||||
}),
|
||||
};
|
||||
|
||||
// Only rendered on the client, so reduced motion is known before the first frame
|
||||
const mounted = ref(false);
|
||||
const reducedMotion = useMediaQuery('(prefers-reduced-motion: reduce)');
|
||||
|
||||
onMounted(() => {
|
||||
if (reducedMotion.value) {
|
||||
drawAnimation.value = 'exit';
|
||||
showHandles.value = false;
|
||||
scaleDownAnimation.value = 'small';
|
||||
iconGridAnimation.value = 'showIcons';
|
||||
}
|
||||
|
||||
mounted.value = true;
|
||||
});
|
||||
|
||||
/** Skips straight to the end state when the user prefers reduced motion. */
|
||||
const initial = <T,>(value: T) => (reducedMotion.value ? false : value);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -167,6 +273,7 @@ const shrinkIconVariants: Variants = {
|
||||
v-bind="preventFocusProps"
|
||||
>
|
||||
<motion.svg
|
||||
v-if="mounted"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="-12 -12 48 48"
|
||||
fill="none"
|
||||
@@ -209,7 +316,7 @@ const shrinkIconVariants: Variants = {
|
||||
<MotionLucideIcon
|
||||
v-for="(icon, index) in icons"
|
||||
size="2"
|
||||
initial="initial"
|
||||
:initial="initial('initial')"
|
||||
:key="icon.name"
|
||||
:variants="iconAnimationVariants"
|
||||
:animate="iconGridAnimation"
|
||||
@@ -223,15 +330,15 @@ const shrinkIconVariants: Variants = {
|
||||
<motion.g
|
||||
:variants="scaleDownVariants"
|
||||
:animate="scaleDownAnimation"
|
||||
initial="hidden"
|
||||
:initial="initial('hidden')"
|
||||
@animation-complete="onAnimationComplete"
|
||||
v-bind="preventFocusProps"
|
||||
>
|
||||
<motion.path
|
||||
d="M14 12C14 9.79086 12.2091 8 10 8C7.79086 8 6 9.79086 6 12C6 16.4183 9.58172 20 14 20C18.4183 20 22 16.4183 22 12C22 8.446 20.455 5.25285 18 3.05557"
|
||||
:style="{ stroke: 'var(--vp-c-gray-1)' }"
|
||||
:d="LOGO_PATH_OUTER"
|
||||
:style="{ stroke: reducedMotion ? 'var(--vp-c-text-1)' : 'var(--vp-c-gray-1)' }"
|
||||
:animate="drawAnimation"
|
||||
initial="hidden"
|
||||
:initial="initial('hidden')"
|
||||
:variants="draw"
|
||||
:custom="1"
|
||||
@animation-complete="onAnimationComplete"
|
||||
@@ -239,10 +346,10 @@ const shrinkIconVariants: Variants = {
|
||||
/>
|
||||
|
||||
<motion.path
|
||||
d="M10 12C10 14.2091 11.7909 16 14 16C16.2091 16 18 14.2091 18 12C18 7.58172 14.4183 4 10 4C5.58172 4 2 7.58172 2 12C2 15.5841 3.57127 18.8012 6.06253 21"
|
||||
:style="{ stroke: 'var(--vp-c-gray-1)' }"
|
||||
:d="LOGO_PATH_INNER"
|
||||
:style="{ stroke: reducedMotion ? 'var(--vp-c-brand)' : 'var(--vp-c-gray-1)' }"
|
||||
:animate="drawAnimation"
|
||||
initial="hidden"
|
||||
:initial="initial('hidden')"
|
||||
:variants="draw"
|
||||
:custom="0"
|
||||
v-bind="preventFocusProps"
|
||||
@@ -253,80 +360,36 @@ const shrinkIconVariants: Variants = {
|
||||
class="svg-preview-control-path-marker-group"
|
||||
:style="{ stroke: 'var(--vp-c-text-1)' }"
|
||||
stroke-width="0.125"
|
||||
:initial="{ opacity: 1 }"
|
||||
:animate="showHandles ? { opacity: 1 } : { opacity: 0 }"
|
||||
:transition="{ delay: 0, duration: 0.2 }"
|
||||
:initial="initial({ opacity: 1 })"
|
||||
:animate="{ opacity: showHandles ? 1 : 0 }"
|
||||
:transition="{ duration: 0.2 }"
|
||||
v-bind="preventFocusProps"
|
||||
>
|
||||
<motion.path
|
||||
d="M14 12C14 9.79086 12.2091 8 10 8C7.79086 8 6 9.79086 6 12C6 16.4183 9.58172 20 14 20C18.4183 20 22 16.4183 22 12C22 8.446 20.455 5.25285 18 3.05557"
|
||||
:initial="{ opacity: 0 }"
|
||||
v-for="d in [LOGO_PATH_OUTER, LOGO_PATH_INNER]"
|
||||
:key="d"
|
||||
:d="d"
|
||||
:initial="initial({ opacity: 0 })"
|
||||
:animate="{ opacity: 1 }"
|
||||
:transition="{ delay: 1.6, duration: 1.5 }"
|
||||
v-bind="preventFocusProps"
|
||||
/>
|
||||
<motion.path
|
||||
d="M10 12C10 14.2091 11.7909 16 14 16C16.2091 16 18 14.2091 18 12C18 7.58172 14.4183 4 10 4C5.58172 4 2 7.58172 2 12C2 15.5841 3.57127 18.8012 6.06253 21"
|
||||
:initial="{ opacity: 0 }"
|
||||
:animate="{ opacity: 1 }"
|
||||
:transition="{ delay: 1.6, duration: 1.5 }"
|
||||
v-bind="preventFocusProps"
|
||||
/>
|
||||
<motion.g
|
||||
:initial="{ opacity: 0 }"
|
||||
:d="VERTICES"
|
||||
:initial="initial({ opacity: 0 })"
|
||||
:animate="{ opacity: 1 }"
|
||||
:transition="{ delay: 0.2, duration: 0.3 }"
|
||||
v-bind="preventFocusProps"
|
||||
>
|
||||
<path
|
||||
d="M14 12h.01M10 8h.01M10 8h.01M6 12h.01M6 12h.01M14 20h.01M14 20h.01M22 12h.01M22 12h.01M18 3.05557h.01M10 12h.01M14 16h.01M14 16h.01M18 12h.01M18 12h.01M10 4h.01M10 4h.01M2 12h.01M2 12h.01M6.06253 21h.01"
|
||||
>
|
||||
</path>
|
||||
</motion.g>
|
||||
|
||||
<motion.circle
|
||||
:initial="{ opacity: 0, scale: 0.2 }"
|
||||
:animate="{ opacity: 1, scale: 1 }"
|
||||
:transition="{ delay: 0, duration: 0.8 }"
|
||||
cx="14"
|
||||
cy="12"
|
||||
r="0.5"
|
||||
v-bind="preventFocusProps"
|
||||
/>
|
||||
<motion.circle
|
||||
:initial="{ opacity: 0, scale: 0.2 }"
|
||||
:animate="{ opacity: 1, scale: 1 }"
|
||||
:transition="{ delay: 0, duration: 0.8 }"
|
||||
cx="14"
|
||||
cy="12"
|
||||
v-for="[cx, cy] in MARKERS"
|
||||
:key="`${cx},${cy}`"
|
||||
:cx="cx"
|
||||
:cy="cy"
|
||||
r="0.5"
|
||||
v-bind="preventFocusProps"
|
||||
/>
|
||||
<motion.circle
|
||||
:initial="{ opacity: 0, scale: 0.2 }"
|
||||
:initial="initial({ opacity: 0, scale: 0.2 })"
|
||||
:animate="{ opacity: 1, scale: 1 }"
|
||||
:transition="{ delay: 0, duration: 0.8 }"
|
||||
cx="18"
|
||||
cy="3.05557"
|
||||
r="0.5"
|
||||
v-bind="preventFocusProps"
|
||||
/>
|
||||
<motion.circle
|
||||
:initial="{ opacity: 0, scale: 0.2 }"
|
||||
:animate="{ opacity: 1, scale: 1 }"
|
||||
:transition="{ delay: 0, duration: 0.8 }"
|
||||
cx="10"
|
||||
cy="12"
|
||||
r="0.5"
|
||||
v-bind="preventFocusProps"
|
||||
/>
|
||||
<motion.circle
|
||||
:initial="{ opacity: 0, scale: 0.2 }"
|
||||
:animate="{ opacity: 1, scale: 1 }"
|
||||
:transition="{ delay: 0, duration: 0.8 }"
|
||||
cx="6.06253"
|
||||
cy="21"
|
||||
r="0.5"
|
||||
:transition="{ duration: 0.8 }"
|
||||
v-bind="preventFocusProps"
|
||||
/>
|
||||
</motion.g>
|
||||
@@ -335,221 +398,30 @@ const shrinkIconVariants: Variants = {
|
||||
stroke-width="0.12"
|
||||
stroke="#FFF"
|
||||
stroke-opacity="0.3"
|
||||
:initial="{ opacity: 1 }"
|
||||
:animate="showHandles ? { opacity: 1 } : { opacity: 0 }"
|
||||
:transition="{ delay: 0, duration: 0.6 }"
|
||||
:initial="initial({ opacity: 1 })"
|
||||
:animate="{ opacity: showHandles ? 1 : 0 }"
|
||||
:transition="{ duration: 0.6 }"
|
||||
v-bind="preventFocusProps"
|
||||
>
|
||||
<motion.g
|
||||
:initial="{ opacity: 0, scale: 0.2 }"
|
||||
v-for="(group, index) in HANDLE_GROUPS"
|
||||
:key="index"
|
||||
:initial="initial({ opacity: 0, scale: 0.2 })"
|
||||
:animate="{ opacity: 1, scale: 1 }"
|
||||
:transition="{ delay: 0.2, duration: 0.3 }"
|
||||
:transition="{ delay: group.delay, duration: 0.3 }"
|
||||
v-bind="preventFocusProps"
|
||||
>
|
||||
<path d="M14 12 14 9.79086"></path>
|
||||
<circle
|
||||
cy="9.79086"
|
||||
cx="14"
|
||||
r="0.25"
|
||||
></circle>
|
||||
</motion.g>
|
||||
|
||||
<motion.g
|
||||
:initial="{ opacity: 0, scale: 0.2 }"
|
||||
:animate="{ opacity: 1, scale: 1 }"
|
||||
:transition="{ delay: 0.4, duration: 0.3 }"
|
||||
v-bind="preventFocusProps"
|
||||
>
|
||||
<path d="M10 8 12.2091 8"></path>
|
||||
<circle
|
||||
cy="8"
|
||||
cx="12.2091"
|
||||
r="0.25"
|
||||
></circle>
|
||||
<path d="M10 8 7.79086 8"></path>
|
||||
<circle
|
||||
cy="8"
|
||||
cx="7.79086"
|
||||
r="0.25"
|
||||
></circle>
|
||||
</motion.g>
|
||||
|
||||
<motion.g
|
||||
:initial="{ opacity: 0, scale: 0.2 }"
|
||||
:animate="{ opacity: 1, scale: 1 }"
|
||||
:transition="{ delay: 0.6, duration: 0.3 }"
|
||||
v-bind="preventFocusProps"
|
||||
>
|
||||
<path d="M6 12 6 9.79086"></path>
|
||||
<circle
|
||||
cy="9.79086"
|
||||
cx="6"
|
||||
r="0.25"
|
||||
></circle>
|
||||
<path d="M6 12 6 16.4183"></path>
|
||||
<circle
|
||||
cy="16.4183"
|
||||
cx="6"
|
||||
r="0.25"
|
||||
></circle>
|
||||
</motion.g>
|
||||
|
||||
<motion.g
|
||||
:initial="{ opacity: 0, scale: 0.2 }"
|
||||
:animate="{ opacity: 1, scale: 1 }"
|
||||
:transition="{ delay: 0.8, duration: 0.3 }"
|
||||
v-bind="preventFocusProps"
|
||||
>
|
||||
<path d="M14 20 9.58172 20"></path>
|
||||
<circle
|
||||
cy="20"
|
||||
cx="9.58172"
|
||||
r="0.25"
|
||||
></circle>
|
||||
<path d="M14 20 18.4183 20"></path>
|
||||
<circle
|
||||
cy="20"
|
||||
cx="18.4183"
|
||||
r="0.25"
|
||||
></circle>
|
||||
</motion.g>
|
||||
|
||||
<motion.g
|
||||
:initial="{ opacity: 0, scale: 0.2 }"
|
||||
:animate="{ opacity: 1, scale: 1 }"
|
||||
:transition="{ delay: 1, duration: 0.3 }"
|
||||
v-bind="preventFocusProps"
|
||||
>
|
||||
<path d="M22 12 22 16.4183"></path>
|
||||
<circle
|
||||
cy="16.4183"
|
||||
cx="22"
|
||||
r="0.25"
|
||||
></circle>
|
||||
<path d="M22 12 22 8.446"></path>
|
||||
<circle
|
||||
cy="8.446"
|
||||
cx="22"
|
||||
r="0.25"
|
||||
></circle>
|
||||
</motion.g>
|
||||
<motion.g
|
||||
:initial="{ opacity: 0, scale: 0.2 }"
|
||||
:animate="{ opacity: 1, scale: 1 }"
|
||||
:transition="{ delay: 1.2, duration: 0.3 }"
|
||||
v-bind="preventFocusProps"
|
||||
>
|
||||
<path d="M18 3.05557 20.455 5.25285"></path>
|
||||
<circle
|
||||
cy="5.25285"
|
||||
cx="20.455"
|
||||
r="0.25"
|
||||
></circle>
|
||||
</motion.g>
|
||||
|
||||
<motion.g
|
||||
:initial="{ opacity: 0, scale: 0.2 }"
|
||||
:animate="{ opacity: 1, scale: 1 }"
|
||||
:transition="{ delay: 0.2, duration: 0.3 }"
|
||||
v-bind="preventFocusProps"
|
||||
>
|
||||
<path d="M10 12 10 14.2091"></path>
|
||||
<circle
|
||||
cy="14.2091"
|
||||
cx="10"
|
||||
r="0.25"
|
||||
></circle>
|
||||
</motion.g>
|
||||
<motion.g
|
||||
:initial="{ opacity: 0, scale: 0.2 }"
|
||||
:animate="{ opacity: 1, scale: 1 }"
|
||||
:transition="{ delay: 0.4, duration: 0.3 }"
|
||||
v-bind="preventFocusProps"
|
||||
>
|
||||
<path d="M14 16 11.7909 16"></path>
|
||||
<circle
|
||||
cy="16"
|
||||
cx="11.7909"
|
||||
r="0.25"
|
||||
></circle>
|
||||
<path d="M14 16 16.2091 16"></path>
|
||||
<circle
|
||||
cy="16"
|
||||
cx="16.2091"
|
||||
r="0.25"
|
||||
></circle>
|
||||
</motion.g>
|
||||
|
||||
<motion.g
|
||||
:initial="{ opacity: 0, scale: 0.2 }"
|
||||
:animate="{ opacity: 1, scale: 1 }"
|
||||
:transition="{ delay: 0.6, duration: 0.3 }"
|
||||
v-bind="preventFocusProps"
|
||||
>
|
||||
<path d="M18 12 18 14.2091"></path>
|
||||
<circle
|
||||
cy="14.2091"
|
||||
cx="18"
|
||||
r="0.25"
|
||||
></circle>
|
||||
<path d="M18 12 18 7.58172"></path>
|
||||
<circle
|
||||
cy="7.58172"
|
||||
cx="18"
|
||||
r="0.25"
|
||||
></circle>
|
||||
</motion.g>
|
||||
|
||||
<motion.g
|
||||
:initial="{ opacity: 0, scale: 0.2 }"
|
||||
:animate="{ opacity: 1, scale: 1 }"
|
||||
:transition="{ delay: 0.8, duration: 0.3 }"
|
||||
v-bind="preventFocusProps"
|
||||
>
|
||||
<path d="M10 4 14.4183 4"></path>
|
||||
<circle
|
||||
cy="4"
|
||||
cx="14.4183"
|
||||
r="0.25"
|
||||
></circle>
|
||||
<path d="M10 4 5.58172 4"></path>
|
||||
<circle
|
||||
cy="4"
|
||||
cx="5.58172"
|
||||
r="0.25"
|
||||
></circle>
|
||||
</motion.g>
|
||||
<motion.g
|
||||
:initial="{ opacity: 0, scale: 0.2 }"
|
||||
:animate="{ opacity: 1, scale: 1 }"
|
||||
:transition="{ delay: 1, duration: 0.3 }"
|
||||
v-bind="preventFocusProps"
|
||||
>
|
||||
<path d="M2 12 2 7.58172"></path>
|
||||
<circle
|
||||
cy="7.58172"
|
||||
cx="2"
|
||||
r="0.25"
|
||||
></circle>
|
||||
<path d="M2 12 2 15.5841"></path>
|
||||
<circle
|
||||
cy="15.5841"
|
||||
cx="2"
|
||||
r="0.25"
|
||||
></circle>
|
||||
</motion.g>
|
||||
<motion.g
|
||||
:initial="{ opacity: 0, scale: 0.2 }"
|
||||
:animate="{ opacity: 1, scale: 1 }"
|
||||
:transition="{ delay: 1.2, duration: 0.3 }"
|
||||
v-bind="preventFocusProps"
|
||||
>
|
||||
<path d="M6.06253 21 3.57127 18.8012"></path>
|
||||
<circle
|
||||
cy="18.8012"
|
||||
cx="3.57127"
|
||||
r="0.25"
|
||||
></circle>
|
||||
<template
|
||||
v-for="[x1, y1, x2, y2] in group.segments"
|
||||
:key="`${x2},${y2}`"
|
||||
>
|
||||
<path :d="`M${x1} ${y1} ${x2} ${y2}`" />
|
||||
<circle
|
||||
:cx="x2"
|
||||
:cy="y2"
|
||||
r="0.25"
|
||||
/>
|
||||
</template>
|
||||
</motion.g>
|
||||
</motion.g>
|
||||
</motion.svg>
|
||||
|
||||
@@ -8,6 +8,7 @@ import ColorPicker from '../base/ColorPicker.vue';
|
||||
import ResetButton from '../base/ResetButton.vue';
|
||||
import HomeIconCustomizerIcons from './HomeIconCustomizerIcons.vue';
|
||||
import Switch from '../base/Switch.vue';
|
||||
import Card from '../base/Card.vue';
|
||||
import useTrackStyleChanges from '../../composables/useTrackStyleChanges';
|
||||
import { track } from '../../utils/openpanel';
|
||||
|
||||
@@ -31,7 +32,7 @@ const iconStyle = computed(() => ({
|
||||
|
||||
<template>
|
||||
<HomeContainer>
|
||||
<div class="card">
|
||||
<Card class="customizer-card">
|
||||
<div class="card-column">
|
||||
<h2 class="title">
|
||||
Style as you please
|
||||
@@ -116,16 +117,14 @@ const iconStyle = computed(() => ({
|
||||
:style="iconStyle"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</HomeContainer>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.card {
|
||||
display: block;
|
||||
border-radius: 12px;
|
||||
.customizer-card {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
background-color: var(--vp-c-bg-soft);
|
||||
padding: 24px;
|
||||
--slider-bar-color: var(--vp-c-bg-soft-down);
|
||||
--color-picker-bg: var(--vp-c-bg-soft-down);
|
||||
|
||||
@@ -22,7 +22,7 @@ import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vu
|
||||
height="42"
|
||||
/>
|
||||
<VPButton
|
||||
href="https://opencollective.com/lucide-icons"
|
||||
href="/sponsor"
|
||||
class="sponsor-button"
|
||||
text="Become a sponsor"
|
||||
/>
|
||||
|
||||
@@ -10,9 +10,10 @@ export interface TeamMember {
|
||||
|
||||
<script setup lang="ts">
|
||||
import { DefaultTheme } from 'vitepress';
|
||||
import Icon from '@lucide/vue/src/Icon'
|
||||
import Card from '../base/Card.vue'
|
||||
import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue'
|
||||
import VPSocialLinks from 'vitepress/dist/client/theme-default/components/VPSocialLinks.vue'
|
||||
import { heart } from '../../../data/iconNodes'
|
||||
|
||||
defineProps<TeamMember>()
|
||||
</script>
|
||||
@@ -20,7 +21,9 @@ defineProps<TeamMember>()
|
||||
<template>
|
||||
<div>
|
||||
<Card class="member-card">
|
||||
<a :href="socialLinks[0]?.link" target="_blank" rel="noreferrer noopener">
|
||||
<img :src="image" :alt="name" class="member-image"/>
|
||||
</a>
|
||||
<h3 class="member-name">
|
||||
{{name}}
|
||||
</h3>
|
||||
@@ -28,16 +31,25 @@ defineProps<TeamMember>()
|
||||
{{title}}
|
||||
</p>
|
||||
<div class="member-links">
|
||||
<VPButton
|
||||
<a
|
||||
v-if="sponsor"
|
||||
:href="sponsor"
|
||||
text="Sponsor"
|
||||
theme="sponsor"
|
||||
class="sponsor-button"
|
||||
size="medium"
|
||||
/>
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
<Icon
|
||||
:iconNode="heart"
|
||||
name="heart"
|
||||
:size="16"
|
||||
class="sponsor-heart"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
Sponsor
|
||||
</a>
|
||||
<VPSocialLinks
|
||||
:links="socialLinks"
|
||||
class="member-social-links"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -57,6 +69,14 @@ defineProps<TeamMember>()
|
||||
border-radius: 32px;
|
||||
margin: 0 auto;
|
||||
background-color: var(--vp-c-bg);
|
||||
box-shadow: 0 0 0 0 transparent;
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
transition-duration: 0.3s;
|
||||
}
|
||||
|
||||
.member-image:hover {
|
||||
transform: scale(1.06);
|
||||
box-shadow: 0 0 0 3px var(--vp-c-bg-soft), 0 0 0 5px var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.member-name {
|
||||
@@ -79,14 +99,81 @@ defineProps<TeamMember>()
|
||||
margin-bottom: 16px;;
|
||||
}
|
||||
|
||||
.sponsor-button {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.member-links {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.sponsor-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
height: 36px;
|
||||
padding: 0 16px 0 14px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-2);
|
||||
text-decoration: none;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 18px;
|
||||
background-color: var(--vp-c-bg);
|
||||
transition: color 0.25s, background-color 0.25s, border-color 0.25s;
|
||||
transition-duration: 0.25s;
|
||||
}
|
||||
|
||||
.sponsor-heart {
|
||||
color: var(--vp-c-sponsor);
|
||||
transition: color 0.25s, fill 0.25s;
|
||||
transition-duration: 0.25s;
|
||||
}
|
||||
|
||||
.sponsor-button:hover,
|
||||
.sponsor-button:focus-visible {
|
||||
outline: none;
|
||||
color: #fff;
|
||||
border-color: var(--vp-c-sponsor);
|
||||
background-color: var(--vp-c-sponsor);
|
||||
}
|
||||
|
||||
.sponsor-button:hover .sponsor-heart,
|
||||
.sponsor-button:focus-visible .sponsor-heart {
|
||||
color: #fff;
|
||||
fill: #fff;
|
||||
animation: heartbeat 1s ease-in-out infinite;
|
||||
animation-duration: 1s !important;
|
||||
animation-iteration-count: infinite !important;
|
||||
}
|
||||
|
||||
@keyframes heartbeat {
|
||||
0%,
|
||||
40%,
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
20% {
|
||||
transform: scale(1.25);
|
||||
}
|
||||
|
||||
60% {
|
||||
transform: scale(1.15);
|
||||
}
|
||||
}
|
||||
|
||||
/* Social icons get a soft rounded background and lift a little on hover */
|
||||
.member-social-links :deep(.VPSocialLink) {
|
||||
border-radius: 8px;
|
||||
transition: color 0.25s, background-color 0.25s, transform 0.25s;
|
||||
transition-duration: 0.25s;
|
||||
}
|
||||
|
||||
.member-social-links :deep(.VPSocialLink:hover),
|
||||
.member-social-links :deep(.VPSocialLink:focus-visible) {
|
||||
outline: none;
|
||||
color: var(--vp-c-text-1);
|
||||
background-color: var(--vp-c-default-soft);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -74,7 +74,8 @@ watch(headers, () => {
|
||||
line-height: 24px;
|
||||
font-size: 14px;
|
||||
display: block;
|
||||
transition: color 0.25s;
|
||||
transition: color 0.25s, transform 0.25s ease;
|
||||
transition-duration: 0.25s;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
@@ -86,6 +87,13 @@ watch(headers, () => {
|
||||
.sidebar-link:hover, .sidebar-link.active {
|
||||
color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
/* All and Categories slide in a little on hover */
|
||||
.sidebar-link:hover,
|
||||
.sidebar-link:focus-visible {
|
||||
outline: none;
|
||||
transform: translateX(4px);
|
||||
}
|
||||
.content {
|
||||
margin-top: 12px;
|
||||
position: relative;
|
||||
@@ -101,8 +109,9 @@ watch(headers, () => {
|
||||
left: -1px;
|
||||
z-index: 0;
|
||||
opacity: 0;
|
||||
width: 1px;
|
||||
width: 2px;
|
||||
height: 18px;
|
||||
border-radius: 1px;
|
||||
background-color: var(--vp-c-brand);
|
||||
transition: top 0.25s cubic-bezier(0, 1, 0.5, 1), background-color 0.5s, opacity 0.25s;
|
||||
}
|
||||
|
||||
@@ -68,22 +68,45 @@ function onClick(categoryName: string) {
|
||||
padding-left: 13px;
|
||||
}
|
||||
|
||||
/* Each category is a soft pill on hover: the name slides in a little and the count badge
|
||||
takes the brand colour. The category in view keeps the brand colour. */
|
||||
.outline-link {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
line-height: 28px;
|
||||
align-items: center;
|
||||
margin: 1px -8px;
|
||||
padding: 0 6px 0 8px;
|
||||
line-height: 26px;
|
||||
color: var(--vp-c-text-2);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
transition: color 0.5s;
|
||||
border-radius: 6px;
|
||||
transition:
|
||||
color 0.25s,
|
||||
background-color 0.25s;
|
||||
transition-duration: 0.25s;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.outline-link > span:first-child {
|
||||
transition: transform 0.25s ease;
|
||||
transition-duration: 0.25s;
|
||||
}
|
||||
|
||||
.outline-link:hover,
|
||||
.outline-link.active {
|
||||
.outline-link:focus-visible {
|
||||
outline: none;
|
||||
color: var(--vp-c-text-1);
|
||||
transition: color 0.25s;
|
||||
background-color: var(--vp-c-default-soft);
|
||||
}
|
||||
|
||||
.outline-link:hover > span:first-child,
|
||||
.outline-link:focus-visible > span:first-child {
|
||||
transform: translateX(3px);
|
||||
}
|
||||
|
||||
.outline-link.active {
|
||||
color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.outline-link.inactive {
|
||||
@@ -96,10 +119,26 @@ function onClick(categoryName: string) {
|
||||
}
|
||||
|
||||
.icon-count {
|
||||
opacity: 0.5;
|
||||
margin-left: auto;
|
||||
padding: 0 6px;
|
||||
min-width: 26px;
|
||||
line-height: 18px;
|
||||
text-align: center;
|
||||
font-size: 11px;
|
||||
font-weight: 400;
|
||||
font-weight: 500;
|
||||
color: var(--vp-c-text-3);
|
||||
border-radius: 9px;
|
||||
transition:
|
||||
color 0.25s,
|
||||
background-color 0.25s;
|
||||
transition-duration: 0.25s;
|
||||
}
|
||||
|
||||
.outline-link:hover .icon-count,
|
||||
.outline-link:focus-visible .icon-count,
|
||||
.outline-link.active .icon-count {
|
||||
color: var(--vp-c-brand-1);
|
||||
background-color: color-mix(in srgb, var(--vp-c-brand) 14%, transparent);
|
||||
}
|
||||
|
||||
.outline-link.inactive .icon-count {
|
||||
|
||||
@@ -157,6 +157,8 @@ const Expand = createLucideIcon('Expand', expand);
|
||||
margin: 0 auto;
|
||||
padding: 1rem;
|
||||
background-color: var(--vp-c-bg-elv);
|
||||
border-top: 1px solid var(--vp-c-divider);
|
||||
border-right: 1px solid var(--vp-c-divider);
|
||||
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
|
||||
will-change: transform;
|
||||
pointer-events: all;
|
||||
|
||||
@@ -3,6 +3,7 @@ import { computed } from 'vue';
|
||||
import createLucideIcon from '@lucide/vue/src/createLucideIcon';
|
||||
import { useMediaQuery } from '@vueuse/core';
|
||||
import { useRouter } from 'vitepress';
|
||||
import { toast } from 'vue-sonner';
|
||||
import getSVGIcon from '../../utils/getSVGIcon';
|
||||
import useConfetti from '../../composables/useConfetti';
|
||||
import Tooltip from '../base/Tooltip.vue';
|
||||
@@ -38,17 +39,37 @@ const href = computed(() =>
|
||||
props.externalLibrary ? `/icons/${props.externalLibrary}/${props.name}` : `/icons/${props.name}`,
|
||||
);
|
||||
|
||||
// The toast says what was copied, since the name and the SVG are copied the same way
|
||||
async function copyToClipboard(text: string, confettiMessage: string, toastMessage: string) {
|
||||
await navigator.clipboard.writeText(text);
|
||||
|
||||
confettiText.value = confettiMessage;
|
||||
confetti();
|
||||
toast.success(toastMessage);
|
||||
}
|
||||
|
||||
function copyName() {
|
||||
return copyToClipboard(props.name, `Copied name!`, `Copied the name of ${props.name}`);
|
||||
}
|
||||
|
||||
function copySVG(iconButton: HTMLElement) {
|
||||
const svgString = getSVGIcon(iconButton.querySelector('svg'), {
|
||||
class: `lucide lucide-${props.name}`,
|
||||
});
|
||||
|
||||
return copyToClipboard(svgString, `Copied SVG!`, `Copied the SVG of ${props.name}`);
|
||||
}
|
||||
|
||||
async function navigateToIcon(event) {
|
||||
if (event.shiftKey) {
|
||||
event.preventDefault();
|
||||
const svgString = getSVGIcon(event.target.firstChild, {
|
||||
class: `lucide lucide-${props.name}`,
|
||||
});
|
||||
await copySVG(event.currentTarget);
|
||||
return;
|
||||
}
|
||||
|
||||
await navigator.clipboard.writeText(svgString);
|
||||
|
||||
confettiText.value = copiedText;
|
||||
confetti();
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
event.preventDefault();
|
||||
await copyName();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -76,6 +97,34 @@ async function navigateToIcon(event) {
|
||||
}
|
||||
}
|
||||
|
||||
// On a selected (focused) icon: Enter opens it, Ctrl/Cmd + C copies its name, Shift + C its SVG.
|
||||
// (Ctrl/Cmd + Shift + C is the browser's inspect shortcut, so it can't be used for the SVG.)
|
||||
async function handleKeydown(event: KeyboardEvent) {
|
||||
if (
|
||||
event.key === 'Enter' &&
|
||||
!(event.shiftKey || event.ctrlKey || event.metaKey || event.altKey)
|
||||
) {
|
||||
await navigateToIcon(event);
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key.toLowerCase() !== 'c' || event.altKey) return;
|
||||
|
||||
if (event.shiftKey && !(event.ctrlKey || event.metaKey)) {
|
||||
event.preventDefault();
|
||||
await copySVG(event.currentTarget as HTMLElement);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(event.ctrlKey || event.metaKey) || event.shiftKey) return;
|
||||
|
||||
// Leave a normal copy alone when some text on the page is selected
|
||||
if (window.getSelection()?.toString()) return;
|
||||
|
||||
event.preventDefault();
|
||||
await copyName();
|
||||
}
|
||||
|
||||
const DiamondIcon = createLucideIcon('Diamond', diamond);
|
||||
</script>
|
||||
|
||||
@@ -83,7 +132,9 @@ const DiamondIcon = createLucideIcon('Diamond', diamond);
|
||||
<Tooltip :title="name">
|
||||
<a
|
||||
class="icon-button confetti-button vp-raw"
|
||||
tabindex="0"
|
||||
@click="navigateToIcon"
|
||||
@keydown="handleKeydown"
|
||||
:class="{ active, animate }"
|
||||
:aria-label="name"
|
||||
:data-confetti-text="confettiText"
|
||||
@@ -151,6 +202,12 @@ const DiamondIcon = createLucideIcon('Diamond', diamond);
|
||||
|
||||
.confetti-button:before {
|
||||
line-height: 80px;
|
||||
/* Center the "Copied ..." text over the icon (it started at the icon's left edge and ran off to the right). */
|
||||
width: 160px;
|
||||
max-width: none;
|
||||
left: calc(50% - 80px);
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.icon-button:active {
|
||||
@@ -186,10 +243,16 @@ const DiamondIcon = createLucideIcon('Diamond', diamond);
|
||||
background-color: var(--vp-button-alt-active-bg);
|
||||
}
|
||||
|
||||
.icon-button.active {
|
||||
.icon-button.active,
|
||||
.icon-button.animate,
|
||||
.icon-button:focus-visible {
|
||||
border-color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.icon-button:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.lucide-icon {
|
||||
margin: auto;
|
||||
pointer-events: none;
|
||||
|
||||
@@ -6,7 +6,7 @@ import Calendar from '../../../data/iconDetails/calendar.ts';
|
||||
import Clock from '../../../data/iconDetails/clock.ts';
|
||||
import Bug from '../../../data/iconDetails/bug.ts';
|
||||
import Rocket from '../../../data/iconDetails/rocket.ts';
|
||||
import TriangleAlert from '../../../data/iconDetails/triangle-alert.ts';
|
||||
import TriangleExclamationPoint from '../../../data/iconDetails/triangle-exclamation-point.ts';
|
||||
import PartyPopper from '../../../data/iconDetails/party-popper.ts';
|
||||
import Scissors from '../../../data/iconDetails/scissors.ts';
|
||||
import Copy from '../../../data/iconDetails/copy.ts';
|
||||
@@ -41,7 +41,7 @@ const CalendarIcon = createLucideIcon('calendar', Calendar.iconNode);
|
||||
const ClockIcon = createLucideIcon('clock', Clock.iconNode);
|
||||
const BugIcon = createLucideIcon('bug', Bug.iconNode);
|
||||
const RocketIcon = createLucideIcon('rocket', Rocket.iconNode);
|
||||
const AlertTriangleIcon = createLucideIcon('alert-triangle', TriangleAlert.iconNode);
|
||||
const TriangleExclamationPointIcon = createLucideIcon('triangle-exclamation-point', TriangleExclamationPoint.iconNode);
|
||||
const ScissorsIcon = createLucideIcon('scissors', Scissors.iconNode);
|
||||
const CopyIcon = createLucideIcon('copy', Copy.iconNode);
|
||||
const SaveIcon = createLucideIcon('save', Save.iconNode);
|
||||
@@ -128,7 +128,7 @@ const prettyName = props.name
|
||||
>
|
||||
<div class="placeholder"></div>
|
||||
<div class="badge badge-red">
|
||||
<AlertTriangleIcon :size="20" />
|
||||
<TriangleExclamationPointIcon :size="20" />
|
||||
Alert
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -208,6 +208,43 @@ function handleCloseDrawer() {
|
||||
|
||||
window.history.pushState({}, '', url);
|
||||
}
|
||||
|
||||
const ARROW_STEPS = {
|
||||
ArrowLeft: () => -1,
|
||||
ArrowRight: () => 1,
|
||||
ArrowUp: () => -columnSize.value,
|
||||
ArrowDown: () => columnSize.value,
|
||||
};
|
||||
|
||||
// Arrow keys move through the search results: from the search field down into the grid, then
|
||||
// between the icons. The grid shows `columnSize` icons per row, so up and down skip a whole row.
|
||||
function handleArrowKeys(event: KeyboardEvent) {
|
||||
const step = ARROW_STEPS[event.key]?.();
|
||||
if (step === undefined || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey)
|
||||
return;
|
||||
|
||||
const iconButtons = Array.from(
|
||||
overviewEl.value?.querySelectorAll<HTMLElement>('.icons .icon-button') ?? [],
|
||||
);
|
||||
const target = event.target as HTMLElement;
|
||||
const fromSearch = target.tagName === 'INPUT' && target.closest('.input-wrapper') !== null;
|
||||
|
||||
let next: HTMLElement | undefined;
|
||||
if (fromSearch) {
|
||||
if (event.key !== 'ArrowDown') return;
|
||||
next = iconButtons[0];
|
||||
} else {
|
||||
const index = iconButtons.indexOf(target);
|
||||
if (index === -1) return;
|
||||
next = iconButtons[index + step];
|
||||
}
|
||||
|
||||
if (next) {
|
||||
event.preventDefault();
|
||||
next.focus();
|
||||
next.scrollIntoView({ block: 'nearest' });
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -215,6 +252,7 @@ function handleCloseDrawer() {
|
||||
ref="overviewEl"
|
||||
class="overview-container"
|
||||
:class="{ 'icon-drawer-open': activeIconName }"
|
||||
@keydown="handleArrowKeys"
|
||||
>
|
||||
<StickyBar>
|
||||
<InputSearch
|
||||
|
||||
@@ -0,0 +1,297 @@
|
||||
<script setup lang="ts">
|
||||
import { nextTick, onBeforeUnmount, onMounted, ref } from 'vue';
|
||||
import Icon from '@lucide/vue/src/Icon';
|
||||
import Kbd from '../base/Kbd.vue';
|
||||
import { x } from '~/.vitepress/data/iconNodes';
|
||||
|
||||
const dialog = ref<HTMLDialogElement | null>(null);
|
||||
const isClosing = ref(false);
|
||||
const modifierKey = ref('Ctrl');
|
||||
|
||||
const shortcuts = [
|
||||
{ keys: ['mod', 'K'], alternative: ['/'], description: 'Search' },
|
||||
{ keys: ['Shift', 'Click'], description: 'Copy the SVG of an icon' },
|
||||
{ keys: ['mod', 'Click'], description: 'Copy the name of an icon' },
|
||||
{ keys: ['Arrow keys'], description: 'Move through the search results' },
|
||||
{ keys: ['Enter'], description: 'Open the selected icon' },
|
||||
{ keys: ['mod', 'C'], description: 'Copy the name of the selected icon' },
|
||||
{ keys: ['Shift', 'C'], description: 'Copy the SVG of the selected icon' },
|
||||
{ keys: ['Shift', '?'], description: 'Show keyboard shortcuts' },
|
||||
];
|
||||
|
||||
function keyLabel(key: string) {
|
||||
return key === 'mod' ? modifierKey.value : key;
|
||||
}
|
||||
|
||||
function open() {
|
||||
dialog.value?.showModal();
|
||||
// Focus the dialog itself rather than its close button, so the button is not shown as selected
|
||||
dialog.value?.focus();
|
||||
}
|
||||
|
||||
// The dialog leaves the top layer as soon as it closes, so play the exit transition first
|
||||
async function close() {
|
||||
const element = dialog.value;
|
||||
|
||||
if (!element?.open || isClosing.value) return;
|
||||
|
||||
isClosing.value = true;
|
||||
await nextTick();
|
||||
await Promise.allSettled(element.getAnimations().map((animation) => animation.finished));
|
||||
element.close();
|
||||
isClosing.value = false;
|
||||
}
|
||||
|
||||
function handleDialogClick(event: MouseEvent) {
|
||||
// A click on the backdrop lands on the dialog element itself
|
||||
if (event.target === dialog.value) {
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
function isTyping(event: KeyboardEvent) {
|
||||
const target = event.target as HTMLElement | null;
|
||||
|
||||
return (
|
||||
target?.isContentEditable ||
|
||||
target?.tagName === 'INPUT' ||
|
||||
target?.tagName === 'TEXTAREA' ||
|
||||
target?.tagName === 'SELECT'
|
||||
);
|
||||
}
|
||||
|
||||
// The ? key opens the dialog, except while typing in a field
|
||||
function handleKeydown(event: KeyboardEvent) {
|
||||
if (event.key === '?' && !event.ctrlKey && !event.metaKey && !event.altKey && !isTyping(event)) {
|
||||
event.preventDefault();
|
||||
open();
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
modifierKey.value = /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform) ? '⌘' : 'Ctrl';
|
||||
window.addEventListener('keydown', handleKeydown);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('keydown', handleKeydown);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button
|
||||
type="button"
|
||||
class="keyboard-shortcuts-hint"
|
||||
aria-haspopup="dialog"
|
||||
@click="open"
|
||||
>
|
||||
<span class="hint-label">Keyboard shortcuts</span>
|
||||
<span class="hint-keys">
|
||||
<Kbd>Shift</Kbd>
|
||||
<span>+</span>
|
||||
<Kbd>?</Kbd>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<dialog
|
||||
ref="dialog"
|
||||
class="shortcuts-dialog"
|
||||
:class="{ closing: isClosing }"
|
||||
tabindex="-1"
|
||||
aria-labelledby="shortcuts-title"
|
||||
@click="handleDialogClick"
|
||||
@cancel.prevent="close"
|
||||
>
|
||||
<div class="shortcuts-header">
|
||||
<h2 id="shortcuts-title">Keyboard shortcuts</h2>
|
||||
<button
|
||||
type="button"
|
||||
class="shortcuts-close"
|
||||
aria-label="Close"
|
||||
@click="close"
|
||||
>
|
||||
<Icon
|
||||
:iconNode="x"
|
||||
name="x"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<dl class="shortcuts-list">
|
||||
<div
|
||||
v-for="shortcut in shortcuts"
|
||||
:key="shortcut.description"
|
||||
class="shortcut-row"
|
||||
>
|
||||
<dt class="shortcut-keys">
|
||||
<template
|
||||
v-for="(key, index) in shortcut.keys"
|
||||
:key="key"
|
||||
>
|
||||
<span
|
||||
v-if="index > 0"
|
||||
class="shortcut-plus"
|
||||
>+</span
|
||||
>
|
||||
<Kbd>{{ keyLabel(key) }}</Kbd>
|
||||
</template>
|
||||
<template v-if="shortcut.alternative">
|
||||
<span class="shortcut-or">or</span>
|
||||
<Kbd
|
||||
v-for="key in shortcut.alternative"
|
||||
:key="key"
|
||||
>{{ key }}</Kbd
|
||||
>
|
||||
</template>
|
||||
</dt>
|
||||
<dd class="shortcut-description">{{ shortcut.description }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</dialog>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.keyboard-shortcuts-hint {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-top: 12px;
|
||||
padding: 24px 0px;
|
||||
border-top: 1px solid var(--vp-c-divider);
|
||||
font-size: 12px;
|
||||
color: var(--vp-c-text-1);
|
||||
position: sticky;
|
||||
bottom: -96px;
|
||||
background-color: var(--vp-sidebar-bg-color);
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.hint-keys {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.shortcuts-dialog {
|
||||
width: min(480px, calc(100vw - 32px));
|
||||
padding: 20px 24px 24px;
|
||||
color: var(--vp-c-text-1);
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 12px;
|
||||
box-shadow: var(--vp-shadow-4);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.shortcuts-dialog::backdrop {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.shortcuts-dialog[open] {
|
||||
transition:
|
||||
opacity 150ms ease-out,
|
||||
transform 200ms cubic-bezier(0.34, 1.4, 0.64, 1);
|
||||
}
|
||||
|
||||
.shortcuts-dialog[open]::backdrop {
|
||||
transition: background-color 150ms ease-out;
|
||||
}
|
||||
|
||||
@starting-style {
|
||||
.shortcuts-dialog[open] {
|
||||
opacity: 0;
|
||||
transform: translateY(-6px) scale(0.98);
|
||||
}
|
||||
|
||||
.shortcuts-dialog[open]::backdrop {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.shortcuts-dialog.closing {
|
||||
opacity: 0;
|
||||
transform: translateY(-6px) scale(0.98);
|
||||
transition:
|
||||
opacity 150ms ease-in,
|
||||
transform 150ms ease-in;
|
||||
}
|
||||
|
||||
.shortcuts-dialog.closing::backdrop {
|
||||
background-color: transparent;
|
||||
transition: background-color 150ms ease-in;
|
||||
}
|
||||
|
||||
.shortcuts-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.shortcuts-header h2 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.shortcuts-close {
|
||||
display: flex;
|
||||
padding: 4px;
|
||||
color: var(--vp-c-text-2);
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.shortcuts-close:hover,
|
||||
.shortcuts-close:focus-visible {
|
||||
color: var(--vp-c-text-1);
|
||||
background: var(--vp-c-default-soft);
|
||||
}
|
||||
|
||||
.shortcuts-close svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.shortcuts-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.shortcut-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.shortcut-keys {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
order: 2;
|
||||
}
|
||||
|
||||
.shortcut-description {
|
||||
margin: 0;
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.shortcut-plus,
|
||||
.shortcut-or {
|
||||
color: var(--vp-c-text-3);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.shortcut-or {
|
||||
margin: 0 4px;
|
||||
}
|
||||
</style>
|
||||
@@ -7,6 +7,7 @@ import InputField from '../base/InputField.vue';
|
||||
import ColorPicker from '../base/ColorPicker.vue';
|
||||
import ResetButton from '../base/ResetButton.vue';
|
||||
import Switch from '../base/Switch.vue';
|
||||
import Card from '../base/Card.vue';
|
||||
import useTrackStyleChanges from '../../composables/useTrackStyleChanges';
|
||||
import { track } from '../../utils/openpanel';
|
||||
|
||||
@@ -29,7 +30,7 @@ function onReset() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
<Card
|
||||
class="customizer-card"
|
||||
:class="{ customized: showCustomized }"
|
||||
>
|
||||
@@ -88,6 +89,7 @@ function onReset() {
|
||||
<InputField
|
||||
id="non-scaling-stroke"
|
||||
label="Non-scaling stroke"
|
||||
class="non-scaling-stroke-switch"
|
||||
>
|
||||
<Switch
|
||||
id="non-scaling-stroke-switch"
|
||||
@@ -95,7 +97,7 @@ function onReset() {
|
||||
v-model="nonScalingStroke"
|
||||
/>
|
||||
</InputField>
|
||||
</div>
|
||||
</Card>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
@@ -113,15 +115,13 @@ function onReset() {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.customizer-card {
|
||||
background: var(--vp-c-bg);
|
||||
.card.customizer-card {
|
||||
padding: 12px 24px 24px;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 24px;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
border: 1px solid transparent;
|
||||
transition: border-color 0.4s ease-in-out;
|
||||
background: var(--vp-c-bg);
|
||||
}
|
||||
|
||||
.customizer-card.customized {
|
||||
|
||||
@@ -108,7 +108,7 @@
|
||||
@media (min-width: 960px) {
|
||||
.search-bar {
|
||||
padding-top: 32px;
|
||||
margin-top: calc((var(--vp-layout-top-height, 0px) + 32px) * -1);
|
||||
margin-top: calc((var(--vp-layout-top-height, 0px) + 33px) * -1);
|
||||
top: calc(var(--vp-layout-top-height, 0px) + 64px);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,3 +173,12 @@
|
||||
0% 0%;
|
||||
}
|
||||
}
|
||||
|
||||
/* Keep the copy confetti even with reduced motion turned on (owner request): the site-wide
|
||||
reduced-motion rule in style.css shrinks every animation to 0.01ms, which hid it at once. */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.confetti-button.animate:before,
|
||||
.confetti-button.animate:after {
|
||||
animation-duration: 1s !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,13 +64,14 @@ if (carbonOptions) {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
padding: 16px 20px 20px;
|
||||
border-radius: 12px;
|
||||
min-height: 256px;
|
||||
text-align: center;
|
||||
line-height: 18px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
background-color: var(--vp-carbon-ads-bg-color);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,10 @@ const links = computed(() => [
|
||||
text: 'Contribute',
|
||||
href: '/contribute/',
|
||||
},
|
||||
{
|
||||
text: 'Sponsor',
|
||||
href: '/sponsor',
|
||||
},
|
||||
{
|
||||
text: 'Changelog',
|
||||
href: `${githubLink.value}/releases`,
|
||||
|
||||
@@ -26,21 +26,90 @@ defineProps<{
|
||||
class="logo dark"
|
||||
:alt="`${showcaseItem.name} logo`"
|
||||
/>
|
||||
<span
|
||||
class="company-name"
|
||||
aria-hidden="true"
|
||||
>{{ showcaseItem.name }}</span
|
||||
>
|
||||
<span
|
||||
class="company-arrow"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.company {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
padding: 48px 0;
|
||||
}
|
||||
|
||||
/* On hover the card lifts with a brand border, the logo grows and moves up a little to make
|
||||
room for the name sliding in below it, and an arrow shows the link opens the site */
|
||||
.company:hover,
|
||||
.company:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.logo {
|
||||
height:64px;
|
||||
width: 240px;
|
||||
margin: auto;
|
||||
opacity: 0.8;
|
||||
transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.3s;
|
||||
transition-duration: 0.4s, 0.3s;
|
||||
}
|
||||
|
||||
.company:hover .logo,
|
||||
.company:focus-visible .logo {
|
||||
opacity: 1;
|
||||
transform: translateY(-8px) scale(1.02);
|
||||
}
|
||||
|
||||
.company-name {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 14px;
|
||||
left: 0;
|
||||
text-align: center;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-2);
|
||||
opacity: 0;
|
||||
transform: translateY(3px);
|
||||
transition: opacity 0.3s, transform 0.3s ease;
|
||||
transition-duration: 0.3s;
|
||||
}
|
||||
|
||||
.company:hover .company-name,
|
||||
.company:focus-visible .company-name {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* Lucide's arrow-up-right icon */
|
||||
.company-arrow {
|
||||
position: absolute;
|
||||
top: 14px;
|
||||
right: 14px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
background-color: var(--vp-c-brand-1);
|
||||
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M7 7h10v10'/%3E%3Cpath d='M7 17 17 7'/%3E%3C/svg%3E") center / contain no-repeat;
|
||||
opacity: 0;
|
||||
transform: translate(-6px, 6px);
|
||||
transition: opacity 0.3s, transform 0.3s ease;
|
||||
transition-duration: 0.3s;
|
||||
}
|
||||
|
||||
.company:hover .company-arrow,
|
||||
.company:focus-visible .company-arrow {
|
||||
opacity: 1;
|
||||
transform: translate(0, 0);
|
||||
}
|
||||
|
||||
html.dark .logo.dark {
|
||||
|
||||
@@ -0,0 +1,466 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue';
|
||||
import Icon from '@lucide/vue/src/Icon';
|
||||
import { check } from '../../../data/iconNodes';
|
||||
import SponsorSection from './SponsorSection.vue';
|
||||
import SponsorSectionHeader from './SponsorSectionHeader.vue';
|
||||
import {
|
||||
TIERS,
|
||||
formatEuro,
|
||||
getAmountLinks,
|
||||
getPreviousTier,
|
||||
getTierForAmount,
|
||||
getTierLinks,
|
||||
type Frequency,
|
||||
} from './tiers';
|
||||
|
||||
const frequencies: { id: Frequency; label: string }[] = [
|
||||
{ id: 'monthly', label: 'Monthly' },
|
||||
{ id: 'once', label: 'One-time' },
|
||||
];
|
||||
|
||||
const frequency = ref<Frequency>('monthly');
|
||||
const amount = ref(20);
|
||||
// `v-model` on a number input casts to a number, an empty field stays ''.
|
||||
const customAmount = ref<number | ''>('');
|
||||
|
||||
const isMonthly = computed(() => frequency.value === 'monthly');
|
||||
|
||||
const total = computed(() => {
|
||||
const value = customAmount.value === '' ? amount.value : Number(customAmount.value);
|
||||
|
||||
return value > 0 ? Math.round(value) : 0;
|
||||
});
|
||||
|
||||
const tier = computed(() => (isMonthly.value ? getTierForAmount(total.value) : null));
|
||||
|
||||
// A monthly preset opens its tier's checkout, other amounts open a checkout prefilled with
|
||||
// the amount. The platforms expect whole amounts, so decimals are rounded.
|
||||
const links = computed(() => {
|
||||
const value = Math.round(total.value);
|
||||
|
||||
if (customAmount.value === '' && tier.value) return getTierLinks(tier.value);
|
||||
if (value > 0) return getAmountLinks(value, frequency.value);
|
||||
|
||||
return getTierLinks();
|
||||
});
|
||||
|
||||
const amountText = computed(() => formatEuro(total.value));
|
||||
|
||||
const summary = computed(() => {
|
||||
if (total.value === 0) {
|
||||
return { title: 'Choose an amount', perks: 'Pick a preset or enter your own.' };
|
||||
}
|
||||
if (!isMonthly.value) {
|
||||
return {
|
||||
title: `${amountText.value} one-time`,
|
||||
perks: 'A one-time thank-you. Tier perks apply to monthly sponsorships.',
|
||||
};
|
||||
}
|
||||
if (tier.value) {
|
||||
return {
|
||||
title: `${amountText.value} / month · ${tier.value.name}`,
|
||||
perks: tier.value.summary,
|
||||
};
|
||||
}
|
||||
return { title: `${amountText.value} / month`, perks: 'Every bit helps. Thank you!' };
|
||||
});
|
||||
|
||||
const perksPanel = computed(() => {
|
||||
if (tier.value) {
|
||||
const previousTier = getPreviousTier(tier.value.id);
|
||||
|
||||
return {
|
||||
title: tier.value.name,
|
||||
subtitle: `From ${formatEuro(tier.value.monthly)} / month`,
|
||||
perks: [
|
||||
...(previousTier ? [{ text: `Everything in ${previousTier.name}`, inherited: true }] : []),
|
||||
...tier.value.perks.map((text) => ({ text, inherited: false })),
|
||||
],
|
||||
message: '',
|
||||
};
|
||||
}
|
||||
if (!isMonthly.value) {
|
||||
return {
|
||||
title: 'One-time gift',
|
||||
subtitle: total.value > 0 ? `${amountText.value}, once` : 'Any amount',
|
||||
perks: [],
|
||||
message:
|
||||
'Thank you! Tier perks come with monthly sponsorships. Switch to Monthly to see what you would get.',
|
||||
};
|
||||
}
|
||||
return {
|
||||
title: 'No tier yet',
|
||||
subtitle: total.value > 0 ? `${amountText.value} / month` : 'Choose an amount',
|
||||
perks: [],
|
||||
message: `Every bit helps. From ${formatEuro(TIERS[0].monthly)} a month you become a ${TIERS[0].name}.`,
|
||||
};
|
||||
});
|
||||
|
||||
const isSelected = (value: number) => customAmount.value === '' && amount.value === value;
|
||||
|
||||
function pickAmount(value: number) {
|
||||
amount.value = value;
|
||||
customAmount.value = '';
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SponsorSection band>
|
||||
<SponsorSectionHeader
|
||||
title="Choose your support"
|
||||
description="Pick an amount, monthly or one-time. Monthly amounts map to a sponsor tier with its own perks. Lucide stays free and open source either way."
|
||||
/>
|
||||
|
||||
<div class="layout">
|
||||
<div class="card picker">
|
||||
<div
|
||||
role="group"
|
||||
aria-label="Frequency"
|
||||
class="frequency"
|
||||
>
|
||||
<button
|
||||
v-for="option in frequencies"
|
||||
:key="option.id"
|
||||
type="button"
|
||||
class="frequency-option"
|
||||
:class="{ active: frequency === option.id }"
|
||||
:aria-pressed="frequency === option.id"
|
||||
@click="frequency = option.id"
|
||||
>
|
||||
{{ option.label }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="amounts">
|
||||
<button
|
||||
v-for="option in TIERS"
|
||||
:key="option.id"
|
||||
type="button"
|
||||
class="amount"
|
||||
:class="{ selected: isSelected(option.monthly) }"
|
||||
:aria-pressed="isSelected(option.monthly)"
|
||||
@click="pickAmount(option.monthly)"
|
||||
>
|
||||
<span class="amount-value">{{ formatEuro(option.monthly) }}</span>
|
||||
<span class="amount-label">{{ isMonthly ? option.name : 'One-time' }}</span>
|
||||
</button>
|
||||
<label
|
||||
class="amount custom"
|
||||
:class="{ selected: customAmount !== '' }"
|
||||
>
|
||||
<span class="amount-label">Custom (€)</span>
|
||||
<input
|
||||
v-model="customAmount"
|
||||
type="number"
|
||||
min="1"
|
||||
inputmode="numeric"
|
||||
placeholder="Other"
|
||||
class="amount-value custom-input"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="summary">
|
||||
<div class="summary-text">
|
||||
<span class="summary-title">{{ summary.title }}</span>
|
||||
<span class="summary-perks">{{ summary.perks }}</span>
|
||||
</div>
|
||||
<div class="summary-actions">
|
||||
<VPButton
|
||||
:href="links.openCollective"
|
||||
text="Open Collective"
|
||||
/>
|
||||
<VPButton
|
||||
:href="links.githubSponsors"
|
||||
text="GitHub Sponsors"
|
||||
theme="alt"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<aside
|
||||
class="card perks"
|
||||
aria-live="polite"
|
||||
>
|
||||
<div class="perks-header">
|
||||
<span class="eyebrow">Your perks</span>
|
||||
<h3 class="perks-title">{{ perksPanel.title }}</h3>
|
||||
<span class="perks-subtitle">{{ perksPanel.subtitle }}</span>
|
||||
</div>
|
||||
<ul
|
||||
v-if="perksPanel.perks.length"
|
||||
class="perks-list"
|
||||
>
|
||||
<li
|
||||
v-for="perk in perksPanel.perks"
|
||||
:key="perk.text"
|
||||
class="perk"
|
||||
:class="{ inherited: perk.inherited }"
|
||||
>
|
||||
<Icon
|
||||
:iconNode="check"
|
||||
name="check"
|
||||
:size="18"
|
||||
class="perk-icon"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{{ perk.text }}
|
||||
</li>
|
||||
</ul>
|
||||
<p
|
||||
v-else
|
||||
class="perks-message"
|
||||
>
|
||||
{{ perksPanel.message }}
|
||||
</p>
|
||||
<a
|
||||
href="#tiers"
|
||||
class="compare"
|
||||
>
|
||||
Compare all tiers
|
||||
</a>
|
||||
</aside>
|
||||
</div>
|
||||
</SponsorSection>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.layout {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 24px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.card {
|
||||
min-width: 0;
|
||||
padding: 24px;
|
||||
border-radius: 16px;
|
||||
background-color: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.picker {
|
||||
flex: 999 1 520px;
|
||||
}
|
||||
|
||||
.frequency {
|
||||
display: inline-flex;
|
||||
align-self: flex-start;
|
||||
padding: 4px;
|
||||
gap: 4px;
|
||||
border-radius: 999px;
|
||||
background-color: var(--vp-c-bg-soft);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.frequency-option {
|
||||
min-height: 40px;
|
||||
padding: 0 18px;
|
||||
border-radius: 999px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-2);
|
||||
transition:
|
||||
color 0.25s,
|
||||
background-color 0.25s;
|
||||
}
|
||||
|
||||
.frequency-option.active {
|
||||
background-color: var(--vp-c-bg);
|
||||
color: var(--vp-c-text-1);
|
||||
box-shadow: var(--vp-shadow-1);
|
||||
}
|
||||
|
||||
.amounts {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.amount {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
gap: 2px;
|
||||
min-height: 72px;
|
||||
padding: 8px 12px;
|
||||
border-radius: 12px;
|
||||
border: 2px solid var(--vp-c-divider);
|
||||
background-color: var(--vp-c-bg);
|
||||
color: var(--vp-c-text-1);
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
border-color 0.25s,
|
||||
background-color 0.25s;
|
||||
}
|
||||
|
||||
.amount:hover {
|
||||
border-color: var(--vp-c-border);
|
||||
}
|
||||
|
||||
.amount.selected {
|
||||
border-color: var(--sponsor-accent);
|
||||
background-color: rgba(245, 101, 101, 0.08);
|
||||
}
|
||||
|
||||
.amount-value {
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.amount-label {
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
font-weight: 500;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.custom {
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.custom-input {
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
outline: none;
|
||||
background: transparent;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.custom:focus-within {
|
||||
border-color: var(--sponsor-accent);
|
||||
}
|
||||
|
||||
.summary {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
background-color: var(--vp-c-bg-soft);
|
||||
}
|
||||
|
||||
.summary-text {
|
||||
flex: 1 1 280px;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.summary-title {
|
||||
font-size: 18px;
|
||||
line-height: 24px;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.summary-perks {
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.summary-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.perks {
|
||||
flex: 1 1 300px;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.perks-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-2);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.perks-title {
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
line-height: 32px;
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.02em;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.perks-subtitle {
|
||||
font-size: 14px;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.perks-list {
|
||||
margin: 0;
|
||||
padding: 16px 0 0;
|
||||
list-style: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
border-top: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.perk {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.perk.inherited {
|
||||
color: var(--vp-c-text-2);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.perk-icon {
|
||||
flex-shrink: 0;
|
||||
margin-top: 2px;
|
||||
color: var(--sponsor-accent);
|
||||
}
|
||||
|
||||
.perks-message {
|
||||
margin: 0;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid var(--vp-c-divider);
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.compare {
|
||||
margin-top: auto;
|
||||
padding: 10px 0;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--sponsor-accent);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.compare:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,123 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { getSupporters } from './sponsors';
|
||||
import { OPEN_COLLECTIVE_URL } from './tiers';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
title?: string;
|
||||
headingLevel?: 2 | 3 | 4 | 5 | 6;
|
||||
/** Maximum number of avatars shown before "and N more". */
|
||||
max?: number;
|
||||
}>(),
|
||||
{
|
||||
title: 'Backers and supporters',
|
||||
headingLevel: 3,
|
||||
max: 48,
|
||||
},
|
||||
);
|
||||
|
||||
const supporters = getSupporters();
|
||||
|
||||
const visibleSupporters = computed(() => supporters.slice(0, props.max));
|
||||
const remainingCount = computed(() => supporters.length - visibleSupporters.value.length);
|
||||
const headingElement = computed(() => `h${props.headingLevel}`);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="supporters.length"
|
||||
class="avatars"
|
||||
>
|
||||
<component
|
||||
:is="headingElement"
|
||||
class="title"
|
||||
>
|
||||
{{ title }}
|
||||
</component>
|
||||
<div class="list">
|
||||
<a
|
||||
v-for="supporter in visibleSupporters"
|
||||
:key="supporter.slug"
|
||||
:href="supporter.website"
|
||||
:title="supporter.name"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
class="avatar-link"
|
||||
>
|
||||
<img
|
||||
:src="supporter.avatar"
|
||||
:alt="supporter.name"
|
||||
width="40"
|
||||
height="40"
|
||||
class="avatar"
|
||||
loading="lazy"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
v-if="remainingCount > 0"
|
||||
:href="OPEN_COLLECTIVE_URL"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
class="more"
|
||||
>
|
||||
and {{ remainingCount }} more
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.avatars {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 20px;
|
||||
color: var(--vp-c-text-2);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.avatar-link {
|
||||
display: block;
|
||||
border-radius: 999px;
|
||||
transition: transform 0.25s;
|
||||
}
|
||||
|
||||
.avatar-link:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 999px;
|
||||
object-fit: cover;
|
||||
background-color: var(--vp-c-bg-soft);
|
||||
border: 2px solid var(--vp-c-bg);
|
||||
}
|
||||
|
||||
.more {
|
||||
padding-left: 8px;
|
||||
font-size: 14px;
|
||||
color: var(--vp-c-text-2);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.more:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,71 @@
|
||||
<script setup lang="ts">
|
||||
import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue';
|
||||
import SponsorSection from './SponsorSection.vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SponsorSection class="cta">
|
||||
<div class="cta-card">
|
||||
<h2 class="title">You like building with Lucide?</h2>
|
||||
<p class="description">
|
||||
Help keep it beautiful and consistent for everyone who comes after you.
|
||||
</p>
|
||||
<div class="actions">
|
||||
<VPButton
|
||||
href="#tiers"
|
||||
text="Become a sponsor"
|
||||
size="big"
|
||||
/>
|
||||
<VPButton
|
||||
href="https://github.com/lucide-icons/lucide"
|
||||
text="Star on GitHub"
|
||||
size="big"
|
||||
theme="alt"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</SponsorSection>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.cta {
|
||||
padding-top: 0;
|
||||
padding-bottom: 96px;
|
||||
}
|
||||
|
||||
.cta-card {
|
||||
padding: 48px 32px;
|
||||
border-radius: 16px;
|
||||
background-color: var(--vp-c-bg-soft);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0;
|
||||
font-size: 32px;
|
||||
line-height: 40px;
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.02em;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.description {
|
||||
margin-bottom: 24px;
|
||||
max-width: 640px;
|
||||
font-size: 18px;
|
||||
line-height: 28px;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,105 @@
|
||||
<script setup lang="ts">
|
||||
import Icon from '@lucide/vue/src/Icon';
|
||||
import { gitPullRequest, server, shapes, users } from '../../../data/iconNodes';
|
||||
import SponsorSection from './SponsorSection.vue';
|
||||
import SponsorSectionHeader from './SponsorSectionHeader.vue';
|
||||
|
||||
const uses = [
|
||||
{
|
||||
title: 'Maintenance and reviews',
|
||||
description: 'Reviewing icon PRs, fixing issues and shipping releases across every package.',
|
||||
icon: gitPullRequest,
|
||||
name: 'git-pull-request',
|
||||
},
|
||||
{
|
||||
title: 'New icons and packages',
|
||||
description: 'Designing icons the community asks for and supporting new frameworks.',
|
||||
icon: shapes,
|
||||
name: 'shapes',
|
||||
},
|
||||
{
|
||||
title: 'Contributors',
|
||||
description: 'Supporting the people who design, review and document Lucide.',
|
||||
icon: users,
|
||||
name: 'users',
|
||||
},
|
||||
{
|
||||
title: 'Infrastructure',
|
||||
description: 'Hosting lucide.dev, search, CI and the tools the team relies on.',
|
||||
icon: server,
|
||||
name: 'server',
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SponsorSection band>
|
||||
<SponsorSectionHeader
|
||||
title="Where your money goes"
|
||||
description="Lucide holds its funds on Open Collective, so every expense is public."
|
||||
/>
|
||||
<div class="uses">
|
||||
<div
|
||||
v-for="use in uses"
|
||||
:key="use.title"
|
||||
class="use"
|
||||
>
|
||||
<div class="icon-tile">
|
||||
<Icon
|
||||
:iconNode="use.icon"
|
||||
:name="use.name"
|
||||
:size="22"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
<h3 class="use-title">{{ use.title }}</h3>
|
||||
<p class="use-description">{{ use.description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</SponsorSection>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.uses {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.use {
|
||||
padding: 24px;
|
||||
border-radius: 12px;
|
||||
background-color: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.icon-tile {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 8px;
|
||||
background-color: var(--vp-c-default-soft);
|
||||
color: var(--sponsor-accent);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.use-title {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.use-description {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
font-weight: 500;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,196 @@
|
||||
<script setup lang="ts">
|
||||
import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue';
|
||||
import MotionCounter from '../base/MotionCounter.vue';
|
||||
import SponsorSection from './SponsorSection.vue';
|
||||
import SponsorHeroAnimation from './SponsorHeroAnimation.vue';
|
||||
import { data } from './SponsorStats.data';
|
||||
import { allTimeSponsorCount } from './sponsors';
|
||||
|
||||
/** Rounds down to a short "120M+" / "25k+" figure, split so the counter only counts the number. */
|
||||
function toCompact(value: number) {
|
||||
if (value >= 1_000_000) return { value: Math.floor(value / 1_000_000), suffix: 'M+' };
|
||||
if (value >= 1_000) return { value: Math.floor(value / 1_000), suffix: 'k+' };
|
||||
return { value, suffix: '' };
|
||||
}
|
||||
|
||||
const stats = [
|
||||
{ value: data.weeklyDownloads, label: 'Weekly downloads', format: toCompact },
|
||||
{ value: data.stars, label: 'GitHub stars', format: toCompact },
|
||||
{
|
||||
value: data.iconsCount,
|
||||
label: 'Icons',
|
||||
format: (value: number) => ({ value: Math.floor(value / 100) * 100, suffix: '+' }),
|
||||
},
|
||||
{
|
||||
value: allTimeSponsorCount,
|
||||
label: 'All-time sponsors',
|
||||
format: (value: number) => ({ value, suffix: '' }),
|
||||
},
|
||||
]
|
||||
// Stats the build couldn't fetch are left out.
|
||||
.filter((stat): stat is typeof stat & { value: number } => stat.value != null)
|
||||
.map(({ value, label, format }) => ({ ...format(value), label }));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SponsorSection class="hero">
|
||||
<div class="hero-content">
|
||||
<div class="intro">
|
||||
<h1 class="title">Sponsor <span class="name">Lucide</span></h1>
|
||||
<p class="lead">
|
||||
Lucide is built and maintained by a small team and a big community. Your sponsorship pays
|
||||
for icon reviews, new packages and the hours that keep every release dependable.
|
||||
</p>
|
||||
<div class="actions">
|
||||
<VPButton
|
||||
href="#tiers"
|
||||
text="Become a sponsor"
|
||||
size="big"
|
||||
/>
|
||||
<VPButton
|
||||
href="#sponsors"
|
||||
text="See our sponsors"
|
||||
size="big"
|
||||
theme="alt"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<dl class="stats">
|
||||
<div
|
||||
v-for="(stat, index) in stats"
|
||||
:key="stat.label"
|
||||
class="stat"
|
||||
>
|
||||
<dt class="stat-label">{{ stat.label }}</dt>
|
||||
<dd class="stat-value">
|
||||
<MotionCounter
|
||||
:value="stat.value"
|
||||
:suffix="stat.suffix"
|
||||
:delay="0.5 +(index * 0.1)"
|
||||
/>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<SponsorHeroAnimation class="hero-animation" />
|
||||
</div>
|
||||
</SponsorSection>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.hero {
|
||||
padding-top: 48px;
|
||||
}
|
||||
|
||||
.hero-content {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 48px;
|
||||
align-items: center;
|
||||
flex-direction: column-reverse;
|
||||
|
||||
}
|
||||
|
||||
.intro {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0;
|
||||
font-size: 40px;
|
||||
line-height: 48px;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.02em;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.name {
|
||||
color: var(--vp-c-brand-3);
|
||||
}
|
||||
|
||||
.lead {
|
||||
margin: 0;
|
||||
max-width: 560px;
|
||||
font-size: 18px;
|
||||
line-height: 28px;
|
||||
font-weight: 500;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.stats {
|
||||
max-width: 560px;
|
||||
margin: 0;
|
||||
padding-top: 24px;
|
||||
border-top: 1px solid var(--vp-c-divider);
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 24px 16px;
|
||||
}
|
||||
|
||||
.stat {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
order: -1;
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
line-height: 32px;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.02em;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 13px;
|
||||
color: var(--vp-c-text-2);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.hero-animation {
|
||||
display: block;
|
||||
flex: 1 1 340px;
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
margin: 0 -24px;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.stats {
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.hero {
|
||||
padding-top: 96px;
|
||||
}
|
||||
.title {
|
||||
font-size: 56px;
|
||||
line-height: 64px;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 28px;
|
||||
line-height: 36px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.hero-content {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,259 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { motion, type Options as MotionOptions } from 'motion-v';
|
||||
import { useMediaQuery } from '@vueuse/core';
|
||||
|
||||
// motion-v re-exports framer-motion's `Variants`, which doesn't match the `variants` prop of its components
|
||||
type Variants = MotionOptions['variants'];
|
||||
|
||||
/**
|
||||
* Draws the heart icon the way the SVG editor previews it (handles and arc radii),
|
||||
* played once and stopped at the red heart.
|
||||
*/
|
||||
|
||||
const HEART_PATH =
|
||||
'M2 9.5a5.5 5.5 0 0 1 9.591-3.676.56.56 0 0 0 .818 0A5.49 5.49 0 0 1 22 9.5c0 2.29-1.5 4-3 5.5l-5.492 5.313a2 2 0 0 1-3 .019L5 15c-1.5-1.5-3-3.2-3-5.5';
|
||||
|
||||
const VERTICES =
|
||||
'M2 9.5h.01M11.591 5.824h.01M12.409 5.824h.01M22 9.5h.01M19 15h.01M13.508 20.313h.01M10.508 20.332h.01M5 15h.01';
|
||||
|
||||
const MARKERS = [[2, 9.5]];
|
||||
|
||||
/** Bezier handles as [x1, y1, x2, y2] segments, grouped by when they appear. */
|
||||
const HANDLE_GROUPS = [
|
||||
{ delay: 0.2, segments: [[22, 9.5, 22, 11.79]] },
|
||||
{ delay: 0.4, segments: [[19, 15, 20.5, 13.5]] },
|
||||
{ delay: 0.6, segments: [[5, 15, 3.5, 13.5]] },
|
||||
{ delay: 0.8, segments: [[2, 9.5, 2, 11.8]] },
|
||||
];
|
||||
|
||||
/** Arc radius guides: the arc's centre and radius, drawn from one arc end to the centre to the other. */
|
||||
const ARC_GUIDES = [
|
||||
{ cx: 7.5, cy: 9.5, r: 5.5, from: [2, 9.5], to: [11.591, 5.824], delay: 0.3 },
|
||||
{ cx: 12, cy: 5.4415, r: 0.56, from: [11.591, 5.824], to: [12.409, 5.824], delay: 0.5 },
|
||||
{ cx: 16.5101, cy: 9.4738, r: 5.49, from: [12.409, 5.824], to: [22, 9.5], delay: 0.7 },
|
||||
{ cx: 11.9996, cy: 18.9997, r: 2, from: [13.508, 20.313], to: [10.508, 20.332], delay: 0.9 },
|
||||
];
|
||||
|
||||
const draw: Variants = {
|
||||
hidden: { pathLength: 0, opacity: 0 },
|
||||
visible: {
|
||||
pathLength: 1.02,
|
||||
opacity: 1,
|
||||
transition: {
|
||||
pathLength: { delay: 2.4, type: 'spring', duration: 2.8, bounce: 0 },
|
||||
opacity: { delay: 2.4, duration: 0.1 },
|
||||
},
|
||||
},
|
||||
exit: {
|
||||
stroke: 'var(--vp-c-brand)',
|
||||
pathLength: 1.02,
|
||||
opacity: 1,
|
||||
transition: {
|
||||
duration: 0.8,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const drawAnimation = ref('visible');
|
||||
const showHandles = ref(true);
|
||||
|
||||
const onAnimationComplete = (variant: unknown) => {
|
||||
if (variant === 'visible') {
|
||||
drawAnimation.value = 'exit';
|
||||
} else if (variant === 'exit') {
|
||||
showHandles.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// Only rendered on the client, so reduced motion is known before the first frame
|
||||
const mounted = ref(false);
|
||||
const reducedMotion = useMediaQuery('(prefers-reduced-motion: reduce)');
|
||||
|
||||
onMounted(() => {
|
||||
if (reducedMotion.value) {
|
||||
drawAnimation.value = 'exit';
|
||||
}
|
||||
|
||||
mounted.value = true;
|
||||
});
|
||||
|
||||
/** Skips straight to the end state when the user prefers reduced motion. */
|
||||
const initial = <T,>(value: T) => (reducedMotion.value ? false : value);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="sponsor-hero-animation-container"
|
||||
>
|
||||
<div
|
||||
class="sponsor-hero-animation"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<svg
|
||||
v-if="mounted"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="-12 -12 48 48"
|
||||
fill="none"
|
||||
overflow="auto"
|
||||
stroke="currentColor"
|
||||
stroke-width="1"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="hero-background"
|
||||
focusable="false"
|
||||
>
|
||||
<g
|
||||
stroke-linecap="butt"
|
||||
stroke-width="0.1"
|
||||
stroke="#777"
|
||||
stroke-opacity="0.3"
|
||||
>
|
||||
<path
|
||||
stroke-dasharray="0 0.1 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0 0.15"
|
||||
stroke-width="0.1"
|
||||
d="M1 0.1v23.8M2 0.1v23.8M4 0.1v23.8M5 0.1v23.8M7 0.1v23.8M8 0.1v23.8M10 0.1v23.8M11 0.1v23.8M13 0.1v23.8M14 0.1v23.8M16 0.1v23.8M17 0.1v23.8M19 0.1v23.8M20 0.1v23.8M22 0.1v23.8M23 0.1v23.8M0.1 1h23.8M0.1 2h23.8M0.1 4h23.8M0.1 5h23.8M0.1 7h23.8M0.1 8h23.8M0.1 10h23.8M0.1 11h23.8M0.1 13h23.8M0.1 14h23.8M0.1 16h23.8M0.1 17h23.8M0.1 19h23.8M0.1 20h23.8M0.1 22h23.8M0.1 23h23.8"
|
||||
/>
|
||||
<path
|
||||
d="M3 0.1v23.8M6 0.1v23.8M9 0.1v23.8M12 0.1v23.8M15 0.1v23.8M18 0.1v23.8M21 0.1v23.8M0.1 3h23.8M0.1 6h23.8M0.1 9h23.8M0.1 12h23.8M0.1 15h23.8M0.1 18h23.8M0.1 21h23.8"
|
||||
/>
|
||||
</g>
|
||||
|
||||
<motion.g
|
||||
stroke-width="0.12"
|
||||
stroke-dasharray="0 0.25 0.25"
|
||||
stroke="#777"
|
||||
stroke-opacity="0.3"
|
||||
:initial="initial({ opacity: 1 })"
|
||||
:animate="{ opacity: showHandles ? 1 : 0 }"
|
||||
:transition="{ duration: 0.6 }"
|
||||
>
|
||||
<motion.g
|
||||
v-for="arc in ARC_GUIDES"
|
||||
:key="`${arc.cx},${arc.cy}`"
|
||||
:initial="initial({ opacity: 0, scale: 0.2 })"
|
||||
:animate="{ opacity: 1, scale: 1 }"
|
||||
:transition="{ delay: arc.delay, duration: 0.3 }"
|
||||
>
|
||||
<path :d="`M${arc.from.join(' ')}L${arc.cx} ${arc.cy}L${arc.to.join(' ')}`" />
|
||||
<circle
|
||||
:cx="arc.cx"
|
||||
:cy="arc.cy"
|
||||
r="0.25"
|
||||
stroke-dasharray="0"
|
||||
stroke="red"
|
||||
/>
|
||||
<circle
|
||||
:cx="arc.cx"
|
||||
:cy="arc.cy"
|
||||
:r="arc.r"
|
||||
stroke="red"
|
||||
/>
|
||||
</motion.g>
|
||||
</motion.g>
|
||||
|
||||
<motion.path
|
||||
:d="HEART_PATH"
|
||||
stroke-width="2"
|
||||
:style="{ stroke: reducedMotion? 'var(--vp-c-brand)' : 'var(--vp-c-gray-1)' }"
|
||||
:initial="initial('hidden')"
|
||||
:animate="drawAnimation"
|
||||
:variants="draw"
|
||||
@animation-complete="onAnimationComplete"
|
||||
/>
|
||||
|
||||
<motion.g
|
||||
:style="{ stroke: 'var(--vp-c-text-1)' }"
|
||||
stroke-width="0.125"
|
||||
:initial="initial({ opacity: 1 })"
|
||||
:animate="{ opacity: showHandles ? 1 : 0 }"
|
||||
:transition="{ duration: 0.2 }"
|
||||
>
|
||||
<motion.path
|
||||
:d="HEART_PATH"
|
||||
:initial="initial({ opacity: 0 })"
|
||||
:animate="{ opacity: 1 }"
|
||||
:transition="{ delay: 1.6, duration: 1.5 }"
|
||||
/>
|
||||
<motion.path
|
||||
:d="VERTICES"
|
||||
:initial="initial({ opacity: 0 })"
|
||||
:animate="{ opacity: 1 }"
|
||||
:transition="{ delay: 0.2, duration: 0.3 }"
|
||||
/>
|
||||
<motion.circle
|
||||
v-for="[cx, cy] in MARKERS"
|
||||
:key="`${cx},${cy}`"
|
||||
:cx="cx"
|
||||
:cy="cy"
|
||||
r="0.5"
|
||||
:initial="initial({ opacity: 0, scale: 0.2 })"
|
||||
:animate="{ opacity: 1, scale: 1 }"
|
||||
:transition="{ duration: 0.8 }"
|
||||
/>
|
||||
</motion.g>
|
||||
|
||||
<motion.g
|
||||
stroke-width="0.12"
|
||||
stroke="#FFF"
|
||||
stroke-opacity="0.3"
|
||||
:initial="initial({ opacity: 1 })"
|
||||
:animate="{ opacity: showHandles ? 1 : 0 }"
|
||||
:transition="{ duration: 0.6 }"
|
||||
>
|
||||
<motion.g
|
||||
v-for="(group, index) in HANDLE_GROUPS"
|
||||
:key="index"
|
||||
:initial="initial({ opacity: 0, scale: 0.2 })"
|
||||
:animate="{ opacity: 1, scale: 1 }"
|
||||
:transition="{ delay: group.delay, duration: 0.3 }"
|
||||
>
|
||||
<template
|
||||
v-for="[x1, y1, x2, y2] in group.segments"
|
||||
:key="`${x2},${y2}`"
|
||||
>
|
||||
<path :d="`M${x1} ${y1} ${x2} ${y2}`" />
|
||||
<circle
|
||||
:cx="x2"
|
||||
:cy="y2"
|
||||
r="0.25"
|
||||
/>
|
||||
</template>
|
||||
</motion.g>
|
||||
</motion.g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.sponsor-hero-animation-container {
|
||||
margin: -48px -24px 0;
|
||||
display: flex;
|
||||
}
|
||||
.sponsor-hero-animation {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-width: 520px;
|
||||
margin: 0 auto;
|
||||
aspect-ratio: 8 / 5;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.sponsor-hero-animation {
|
||||
max-width: 860px;
|
||||
}
|
||||
}
|
||||
|
||||
.hero-background {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 130%;
|
||||
aspect-ratio: 1;
|
||||
transform: translate(-50%, -50%) rotateX(-51deg) rotateZ(-43deg);
|
||||
transform-style: preserve-3d;
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,186 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { getSponsorsByTier } from './sponsors';
|
||||
import { getTier } from './tiers';
|
||||
|
||||
type LogoTier = 'platinum' | 'gold' | 'bronze';
|
||||
type Size = 'lg' | 'md' | 'sm';
|
||||
|
||||
const defaultSizes: Record<LogoTier, Size> = {
|
||||
platinum: 'lg',
|
||||
gold: 'md',
|
||||
bronze: 'sm',
|
||||
};
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
tier: LogoTier;
|
||||
/** Defaults to the tier name. */
|
||||
title?: string;
|
||||
headingLevel?: 2 | 3 | 4 | 5 | 6;
|
||||
size?: Size;
|
||||
/**
|
||||
* Number of "Your logo here" slots to fill up to. Without placeholders the grid
|
||||
* renders nothing while the tier has no sponsors.
|
||||
*/
|
||||
placeholders?: number;
|
||||
}>(),
|
||||
{
|
||||
headingLevel: 3,
|
||||
placeholders: 0,
|
||||
},
|
||||
);
|
||||
|
||||
const sponsors = getSponsorsByTier(props.tier);
|
||||
|
||||
const placeholderCount = computed(() => Math.max(props.placeholders - sponsors.length, 0));
|
||||
const headingElement = computed(() => `h${props.headingLevel}`);
|
||||
const sizeClass = computed(() => props.size ?? defaultSizes[props.tier]);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="sponsors.length || placeholderCount"
|
||||
class="logo-grid"
|
||||
>
|
||||
<component
|
||||
:is="headingElement"
|
||||
class="title"
|
||||
>
|
||||
{{ title ?? getTier(tier).name }}
|
||||
</component>
|
||||
<div
|
||||
class="tiles"
|
||||
:class="sizeClass"
|
||||
>
|
||||
<a
|
||||
v-for="sponsor in sponsors"
|
||||
:key="sponsor.slug"
|
||||
:href="sponsor.website"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
class="tile"
|
||||
>
|
||||
<img
|
||||
:src="sponsor.image"
|
||||
:alt="sponsor.name"
|
||||
class="logo"
|
||||
:class="{ 'light-only': sponsor.imageDark }"
|
||||
loading="lazy"
|
||||
/>
|
||||
<img
|
||||
v-if="sponsor.imageDark"
|
||||
:src="sponsor.imageDark"
|
||||
:alt="sponsor.name"
|
||||
class="logo dark-only"
|
||||
loading="lazy"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
v-for="index in placeholderCount"
|
||||
:key="`placeholder-${index}`"
|
||||
href="/sponsor#tiers"
|
||||
class="tile placeholder"
|
||||
>
|
||||
Your logo here
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.logo-grid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 20px;
|
||||
color: var(--vp-c-text-2);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.tiles {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.tiles.lg {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.tiles.md {
|
||||
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||||
}
|
||||
|
||||
.tiles.sm {
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
}
|
||||
|
||||
.tile {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 16px 24px;
|
||||
border-radius: 12px;
|
||||
background-color: var(--vp-c-bg-soft);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
text-decoration: none;
|
||||
transition: border-color 0.25s;
|
||||
}
|
||||
|
||||
.tile:hover {
|
||||
border-color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.lg .tile {
|
||||
min-height: 120px;
|
||||
}
|
||||
|
||||
.md .tile {
|
||||
min-height: 96px;
|
||||
}
|
||||
|
||||
.sm .tile {
|
||||
min-height: 72px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
/* max-width: 70%; */
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.lg .logo {
|
||||
max-height: 72px;
|
||||
}
|
||||
|
||||
.md .logo {
|
||||
max-height: 64px;
|
||||
}
|
||||
|
||||
.sm .logo {
|
||||
max-height: 56px;
|
||||
}
|
||||
|
||||
html:not(.dark) .logo.dark-only,
|
||||
html.dark .logo.light-only {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
background-color: transparent;
|
||||
border: 2px dashed var(--vp-c-border);
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.lg .placeholder {
|
||||
font-size: 16px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,51 @@
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
/** Full-bleed tinted background with divider borders. */
|
||||
band?: boolean;
|
||||
narrow?: boolean;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section
|
||||
class="sponsor-section"
|
||||
:class="{ band }"
|
||||
>
|
||||
<div
|
||||
class="inner"
|
||||
:class="{ narrow }"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.sponsor-section {
|
||||
--sponsor-accent: var(--vp-c-brand-5);
|
||||
padding: 72px 24px;
|
||||
}
|
||||
|
||||
html.dark .sponsor-section {
|
||||
--sponsor-accent: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.sponsor-section.band {
|
||||
background-color: var(--vp-c-bg-soft);
|
||||
border-top: 1px solid var(--vp-c-divider);
|
||||
border-bottom: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.inner {
|
||||
max-width: 1152px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 32px;
|
||||
}
|
||||
|
||||
.inner.narrow {
|
||||
max-width: 768px;
|
||||
gap: 24px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,43 @@
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
title: string;
|
||||
description?: string;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="section-header">
|
||||
<h2 class="title">{{ title }}</h2>
|
||||
<p
|
||||
v-if="description"
|
||||
class="description"
|
||||
>
|
||||
{{ description }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.section-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
max-width: 680px;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0;
|
||||
font-size: 32px;
|
||||
line-height: 40px;
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.02em;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.description {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
line-height: 28px;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,82 @@
|
||||
import iconNodes from '../../../data/iconNodes';
|
||||
import fetchPackages from '../../../lib/fetchPackages';
|
||||
|
||||
const NPM_DOWNLOADS_API = 'https://api.npmjs.org/downloads/point/last-week';
|
||||
|
||||
/**
|
||||
* Packages that are no longer in the repo but are still published and installed.
|
||||
*/
|
||||
const LEGACY_PACKAGES = ['lucide-vue-next', 'lucide-vue', 'lucide-svelte', 'lucide-angular'];
|
||||
|
||||
async function fetchStars() {
|
||||
try {
|
||||
const response = await fetch('https://api.github.com/repos/lucide-icons/lucide');
|
||||
if (!response.ok) return null;
|
||||
|
||||
const { stargazers_count } = (await response.json()) as { stargazers_count: number };
|
||||
|
||||
return stargazers_count;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchDownloads(url: string) {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(`npm downloads API responded with ${response.status} for ${url}`);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sum of last week's npm downloads of every published Lucide package, or `null` when any
|
||||
* request fails, so a partial total never shows up as the real number.
|
||||
*/
|
||||
async function fetchWeeklyDownloads() {
|
||||
try {
|
||||
const packageNames = (await fetchPackages())
|
||||
.filter((packageJson) => packageJson?.name && !packageJson.private)
|
||||
.map(({ name }) => name)
|
||||
.concat(LEGACY_PACKAGES);
|
||||
|
||||
// The bulk endpoint only accepts unscoped packages, scoped ones are fetched one by one.
|
||||
const unscoped = packageNames.filter((name) => !name.startsWith('@'));
|
||||
const scoped = packageNames.filter((name) => name.startsWith('@'));
|
||||
|
||||
const [bulk, ...single] = await Promise.all([
|
||||
fetchDownloads(`${NPM_DOWNLOADS_API}/${unscoped.join(',')}`) as Promise<
|
||||
Record<string, { downloads: number } | null>
|
||||
>,
|
||||
...scoped.map(
|
||||
(name) => fetchDownloads(`${NPM_DOWNLOADS_API}/${name}`) as Promise<{ downloads: number }>,
|
||||
),
|
||||
]);
|
||||
|
||||
return [...Object.values(bulk), ...single].reduce(
|
||||
(total, result) => total + (result?.downloads ?? 0),
|
||||
0,
|
||||
);
|
||||
} catch (error) {
|
||||
console.warn(`Could not fetch npm downloads. ${error}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const loader = {
|
||||
async load() {
|
||||
const [stars, weeklyDownloads] = await Promise.all([fetchStars(), fetchWeeklyDownloads()]);
|
||||
|
||||
return {
|
||||
stars,
|
||||
weeklyDownloads,
|
||||
iconsCount: Object.keys(iconNodes).length,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
declare const data: Awaited<ReturnType<typeof loader.load>>;
|
||||
|
||||
export { data };
|
||||
export default loader;
|
||||
@@ -0,0 +1,188 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue';
|
||||
import Icon from '@lucide/vue/src/Icon';
|
||||
import { check } from '../../../data/iconNodes';
|
||||
import { formatEuro, getPreviousTier, getTierLinks, type Tier } from './tiers';
|
||||
|
||||
const props = defineProps<{
|
||||
tier: Tier;
|
||||
}>();
|
||||
|
||||
const previousTier = computed(() => getPreviousTier(props.tier.id));
|
||||
const links = computed(() => getTierLinks(props.tier));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<article class="tier-card">
|
||||
<div class="header">
|
||||
<h3 class="name">{{ tier.name }}</h3>
|
||||
<div class="price">
|
||||
<span class="amount">{{ formatEuro(tier.monthly) }}</span>
|
||||
<span class="period">/ month</span>
|
||||
</div>
|
||||
<span
|
||||
v-if="tier.yearly"
|
||||
class="yearly"
|
||||
>
|
||||
or {{ formatEuro(tier.yearly) }} / year, two months free
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<ul class="perks">
|
||||
<li
|
||||
v-if="previousTier"
|
||||
class="inherited"
|
||||
>
|
||||
Everything in {{ previousTier.name }}, plus
|
||||
</li>
|
||||
<li
|
||||
v-for="perk in tier.perks"
|
||||
:key="perk"
|
||||
class="perk"
|
||||
>
|
||||
<Icon
|
||||
:iconNode="check"
|
||||
name="check"
|
||||
:size="18"
|
||||
class="perk-icon"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{{ perk }}
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="actions">
|
||||
<VPButton
|
||||
:href="links.openCollective"
|
||||
text="Open Collective"
|
||||
:theme="tier.featured ? 'brand' : 'alt'"
|
||||
class="primary-action"
|
||||
/>
|
||||
<a
|
||||
:href="links.githubSponsors"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
class="secondary-action"
|
||||
>
|
||||
GitHub Sponsors
|
||||
</a>
|
||||
</div>
|
||||
</article>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.tier-card {
|
||||
flex: 0 0 300px;
|
||||
scroll-snap-align: start;
|
||||
padding: 24px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
background-color: var(--vp-c-bg);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
transition: border-color 0.25s;
|
||||
}
|
||||
|
||||
.tier-card:last-child {
|
||||
scroll-snap-align: end;
|
||||
}
|
||||
|
||||
.tier-card:hover,
|
||||
.tier-card:focus-within {
|
||||
border-color: var(--vp-c-brand-3);
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.name {
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.price {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.amount {
|
||||
font-size: 32px;
|
||||
line-height: 40px;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.02em;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.period,
|
||||
.yearly {
|
||||
font-size: 14px;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.yearly {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.perks {
|
||||
flex-grow: 1;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.inherited {
|
||||
color: var(--vp-c-text-2);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.perk {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.perk-icon {
|
||||
flex-shrink: 0;
|
||||
margin-top: 2px;
|
||||
color: var(--sponsor-accent);
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.primary-action {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.secondary-action {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 44px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--sponsor-accent);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.secondary-action:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,274 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { useScroll } from '@vueuse/core';
|
||||
import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue';
|
||||
import Icon from '@lucide/vue/src/Icon';
|
||||
import { chevronLeft, chevronRight } from '../../../data/iconNodes';
|
||||
import SponsorSection from './SponsorSection.vue';
|
||||
import SponsorSectionHeader from './SponsorSectionHeader.vue';
|
||||
import SponsorTierCard from './SponsorTierCard.vue';
|
||||
import { PARTNER_EMAIL, TIERS } from './tiers';
|
||||
|
||||
// Card width plus the gap between cards.
|
||||
const SCROLL_STEP = 316;
|
||||
|
||||
const track = ref<HTMLElement | null>(null);
|
||||
const { arrivedState } = useScroll(track);
|
||||
|
||||
function scrollTrack(direction: 1 | -1) {
|
||||
track.value?.scrollBy({ left: direction * SCROLL_STEP, behavior: 'smooth' });
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SponsorSection
|
||||
id="tiers"
|
||||
class="tiers"
|
||||
>
|
||||
<div class="tiers-header">
|
||||
<SponsorSectionHeader
|
||||
title="Sponsor tiers"
|
||||
description="Two tiers for individuals, three for companies. Each step adds one clear placement, and perks stack as you go up. Same tiers on Open Collective (EUR) and GitHub Sponsors (USD)."
|
||||
class="tiers-title"
|
||||
/>
|
||||
<div class="track-controls">
|
||||
<button
|
||||
type="button"
|
||||
class="track-button"
|
||||
aria-label="Previous tiers"
|
||||
:disabled="arrivedState.left"
|
||||
@click="scrollTrack(-1)"
|
||||
>
|
||||
<Icon
|
||||
:iconNode="chevronLeft"
|
||||
name="chevron-left"
|
||||
:size="20"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="track-button"
|
||||
aria-label="Next tiers"
|
||||
:disabled="arrivedState.right"
|
||||
@click="scrollTrack(1)"
|
||||
>
|
||||
<Icon
|
||||
:iconNode="chevronRight"
|
||||
name="chevron-right"
|
||||
:size="20"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="track-wrapper">
|
||||
<div
|
||||
ref="track"
|
||||
class="track"
|
||||
role="region"
|
||||
aria-label="Sponsor tiers"
|
||||
tabindex="0"
|
||||
>
|
||||
<SponsorTierCard
|
||||
v-for="tier in TIERS"
|
||||
:key="tier.id"
|
||||
:tier="tier"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="fade fade-left"
|
||||
:class="{ visible: !arrivedState.left }"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<div
|
||||
class="fade fade-right"
|
||||
:class="{ visible: !arrivedState.right }"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="partner">
|
||||
<div class="partner-text">
|
||||
<h3 class="partner-title">Partner? Let's talk.</h3>
|
||||
<p class="partner-description">
|
||||
One partner at a time, custom amount. An exclusive "Supported by" slot, your logo in
|
||||
release posts, an annual roadmap call and priority review of your icon PRs.
|
||||
</p>
|
||||
</div>
|
||||
<VPButton
|
||||
:href="`mailto:${PARTNER_EMAIL}`"
|
||||
text="Contact us"
|
||||
theme="alt"
|
||||
target="_self"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p class="disclaimer">
|
||||
Sponsors never buy icon acceptance or design influence. Every icon meets the same guidelines
|
||||
and community review. Paying by invoice through GitHub Sponsors avoids part of GitHub's
|
||||
organization fee.
|
||||
</p>
|
||||
</SponsorSection>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.tiers {
|
||||
padding-top: 96px;
|
||||
padding-bottom: 48px;
|
||||
}
|
||||
|
||||
.tiers-header {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px 24px;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.tiers-title {
|
||||
flex: 999 1 480px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.track-controls {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.track-button {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid var(--vp-c-border);
|
||||
background-color: var(--vp-c-bg);
|
||||
color: var(--vp-c-text-1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition:
|
||||
background-color 0.25s,
|
||||
opacity 0.25s;
|
||||
}
|
||||
|
||||
.track-button:not(:disabled):hover {
|
||||
background-color: var(--vp-c-bg-soft);
|
||||
}
|
||||
|
||||
.track-button:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.track-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.track {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
align-items: stretch;
|
||||
overflow-x: auto;
|
||||
scroll-snap-type: x mandatory;
|
||||
scroll-behavior: smooth;
|
||||
padding-bottom: 12px;
|
||||
padding-right: 24px;
|
||||
scroll-padding-right: 24px;
|
||||
scrollbar-width: none;
|
||||
scroll-snap-align: center;
|
||||
scroll-padding-left: 24px;
|
||||
scroll-padding-right: 24px;
|
||||
}
|
||||
|
||||
.track::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.track:focus-visible {
|
||||
outline: 2px solid var(--vp-c-brand-1);
|
||||
outline-offset: 4px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.fade {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 12px;
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.fade.visible {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.fade-left {
|
||||
left: 0;
|
||||
width: 48px;
|
||||
background: linear-gradient(to left, transparent, var(--vp-c-bg));
|
||||
}
|
||||
|
||||
.fade-right {
|
||||
right: 0;
|
||||
width: 48px;
|
||||
background: linear-gradient(to right, transparent, var(--vp-c-bg) 85%);
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.fade-left {
|
||||
width: 96px;
|
||||
}
|
||||
|
||||
.fade-right {
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
.track {
|
||||
scroll-snap-align: start;
|
||||
}
|
||||
}
|
||||
|
||||
.partner {
|
||||
padding: 24px 28px;
|
||||
border-radius: 16px;
|
||||
background-color: var(--vp-c-bg-soft);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 24px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.partner-text {
|
||||
flex: 999 1 480px;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.partner-title {
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.partner-description,
|
||||
.disclaimer {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.disclaimer {
|
||||
margin-top: -8px;
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,42 @@
|
||||
<script setup lang="ts">
|
||||
import SponsorSection from './SponsorSection.vue';
|
||||
import SponsorSectionHeader from './SponsorSectionHeader.vue';
|
||||
import SponsorLogoGrid from './SponsorLogoGrid.vue';
|
||||
import SponsorAvatars from './SponsorAvatars.vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SponsorSection
|
||||
id="sponsors"
|
||||
class="wall"
|
||||
>
|
||||
<SponsorSectionHeader
|
||||
title="Our sponsors"
|
||||
description="Thank you to everyone who keeps Lucide going."
|
||||
class="wall-header"
|
||||
/>
|
||||
<SponsorLogoGrid
|
||||
tier="platinum"
|
||||
:placeholders="1"
|
||||
/>
|
||||
<SponsorLogoGrid
|
||||
tier="gold"
|
||||
:placeholders="2"
|
||||
/>
|
||||
<SponsorLogoGrid tier="bronze" />
|
||||
<SponsorAvatars />
|
||||
</SponsorSection>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.wall {
|
||||
padding-top: 48px;
|
||||
padding-bottom: 96px;
|
||||
}
|
||||
|
||||
.wall-header {
|
||||
max-width: none;
|
||||
padding-top: 24px;
|
||||
border-top: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,26 @@
|
||||
import sponsorsData from '../../../data/sponsorsData.json';
|
||||
import type { TierId } from './tiers';
|
||||
|
||||
export interface Sponsor {
|
||||
slug: string;
|
||||
name: string;
|
||||
website: string;
|
||||
image: string;
|
||||
imageDark?: string;
|
||||
avatar: string;
|
||||
monthlyAmount: number;
|
||||
tier: TierId | null;
|
||||
}
|
||||
|
||||
export const sponsors = sponsorsData.sponsors as Sponsor[];
|
||||
|
||||
/** Everyone who ever contributed on Open Collective, `null` when the build couldn't fetch it. */
|
||||
export const allTimeSponsorCount: number | null = sponsorsData.allTimeCount;
|
||||
|
||||
export const getSponsorsByTier = (tierId: TierId) => sponsors.filter(({ tier }) => tier === tierId);
|
||||
|
||||
const LOGO_TIERS: TierId[] = ['bronze', 'gold', 'platinum'];
|
||||
|
||||
/** Everyone below the first logo tier, shown as avatars. */
|
||||
export const getSupporters = () =>
|
||||
sponsors.filter(({ tier }) => !tier || !LOGO_TIERS.includes(tier));
|
||||
@@ -0,0 +1,170 @@
|
||||
export const OPEN_COLLECTIVE_URL = 'https://opencollective.com/lucide-icons';
|
||||
export const GITHUB_SPONSORS_URL = 'https://github.com/sponsors/lucide-icons';
|
||||
export const PARTNER_EMAIL = 'info@lucide.dev';
|
||||
|
||||
export type TierId = 'supporter' | 'backer' | 'bronze' | 'gold' | 'platinum';
|
||||
|
||||
export interface Tier {
|
||||
id: TierId;
|
||||
name: string;
|
||||
/** Monthly amount in euros. */
|
||||
monthly: number;
|
||||
/** Yearly amount in euros, for tiers that can be paid yearly. */
|
||||
yearly?: number;
|
||||
audience: string;
|
||||
/** One-sentence summary of the perks, used in the amount picker. */
|
||||
summary: string;
|
||||
/** Perks this tier adds on top of the previous one. */
|
||||
perks: string[];
|
||||
featured?: boolean;
|
||||
/**
|
||||
* Tier slug on Open Collective. Open the tier on opencollective.com/lucide-icons and copy
|
||||
* it from the "Contribute" link, e.g. `basic-73095` from
|
||||
* https://opencollective.com/lucide-icons/contribute/basic-73095/checkout
|
||||
*/
|
||||
openCollectiveTier: string;
|
||||
/**
|
||||
* Tier id on GitHub Sponsors. Open the tier on github.com/sponsors/lucide-icons and copy
|
||||
* the `tier_id` from its link, e.g. `671689` from
|
||||
* https://github.com/sponsors/lucide-icons/sponsorships?tier_id=671689
|
||||
*/
|
||||
githubSponsorsTierId: number;
|
||||
}
|
||||
|
||||
export const TIERS: Tier[] = [
|
||||
{
|
||||
id: 'supporter',
|
||||
name: 'Supporter',
|
||||
monthly: 5,
|
||||
audience: 'For individuals',
|
||||
summary:
|
||||
'Sponsor badge on GitHub, a Discord sponsor role and your avatar on lucide.dev/sponsor.',
|
||||
perks: [
|
||||
'Sponsor badge on GitHub',
|
||||
'Discord sponsor role',
|
||||
'Small avatar on lucide.dev/sponsor',
|
||||
],
|
||||
openCollectiveTier: 'basic-73095',
|
||||
githubSponsorsTierId: 671689,
|
||||
},
|
||||
{
|
||||
id: 'backer',
|
||||
name: 'Backer',
|
||||
monthly: 20,
|
||||
audience: 'For individuals and freelancers',
|
||||
summary: 'Everything in Supporter, plus a small avatar or logo in the main README.',
|
||||
perks: ['Small avatar or logo in the main README on GitHub'],
|
||||
openCollectiveTier: 'awesome-backer-73677',
|
||||
githubSponsorsTierId: 671692,
|
||||
},
|
||||
{
|
||||
id: 'bronze',
|
||||
name: 'Bronze',
|
||||
monthly: 100,
|
||||
audience: 'For small companies and startups',
|
||||
summary: 'Small logo on lucide.dev/sponsor and in all npm READMEs, plus a sticker sheet.',
|
||||
perks: [
|
||||
'Small logo on lucide.dev/sponsor',
|
||||
'Small logo in all npm package READMEs',
|
||||
'Free Lucide sticker sheet',
|
||||
],
|
||||
openCollectiveTier: 'donation-55643',
|
||||
githubSponsorsTierId: 675253,
|
||||
},
|
||||
{
|
||||
id: 'gold',
|
||||
name: 'Gold',
|
||||
monthly: 500,
|
||||
yearly: 5000,
|
||||
audience: 'For companies shipping Lucide',
|
||||
summary: 'Medium logo on the lucide.dev homepage and docs sidebar, plus a welcome t-shirt.',
|
||||
perks: [
|
||||
'Medium logo in the main README on GitHub',
|
||||
'Medium logo in all npm package READMEs',
|
||||
'Medium logo in the lucide.dev homepage sponsor strip and docs sidebar',
|
||||
'Medium logo on lucide.dev/sponsor',
|
||||
'One-time welcome t-shirt',
|
||||
],
|
||||
featured: true,
|
||||
openCollectiveTier: 'champions-circle-73678',
|
||||
githubSponsorsTierId: 675254,
|
||||
},
|
||||
{
|
||||
id: 'platinum',
|
||||
name: 'Platinum',
|
||||
monthly: 1000,
|
||||
yearly: 10000,
|
||||
audience: 'For large companies and dev tools',
|
||||
summary:
|
||||
'Top homepage placement, logo on icon search, a private maintainer channel and a $100 merch voucher.',
|
||||
perks: [
|
||||
'Top placement on the homepage',
|
||||
'Large logo at the top of the main README on GitHub',
|
||||
'Large logo at the top of all npm package READMEs',
|
||||
'Large logo in the lucide.dev homepage sponsor strip and docs sidebar',
|
||||
'Logo on the icon search page',
|
||||
'Dedicated welcome post on X and Bluesky',
|
||||
'Private channel with maintainers',
|
||||
'$100 merch shop voucher',
|
||||
],
|
||||
openCollectiveTier: 'platinum-107266',
|
||||
githubSponsorsTierId: 675255,
|
||||
},
|
||||
];
|
||||
|
||||
export const getTier = (id: TierId) => TIERS.find((tier) => tier.id === id)!;
|
||||
|
||||
/** The tier before this one, whose perks it inherits. */
|
||||
export const getPreviousTier = (id: TierId) => TIERS[TIERS.findIndex((tier) => tier.id === id) - 1];
|
||||
|
||||
/** The highest tier a monthly amount qualifies for, or `null` below the lowest tier. */
|
||||
export function getTierForAmount(monthly: number) {
|
||||
return [...TIERS].reverse().find((tier) => monthly >= tier.monthly) ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The highest tier a yearly total qualifies for, or `null` below the lowest tier. Tiers with a
|
||||
* yearly price (two months free) count that price, so a yearly Gold sponsor isn't listed as Bronze.
|
||||
*/
|
||||
export function getTierForYearlyAmount(yearly: number) {
|
||||
return [...TIERS].reverse().find((tier) => yearly >= (tier.yearly ?? tier.monthly * 12)) ?? null;
|
||||
}
|
||||
|
||||
export type Frequency = 'monthly' | 'once';
|
||||
|
||||
const getOpenCollectiveParams = (amount: number, frequency: Frequency) =>
|
||||
new URLSearchParams({
|
||||
interval: frequency === 'monthly' ? 'month' : 'oneTime',
|
||||
amount: String(amount),
|
||||
contributeAs: 'me',
|
||||
});
|
||||
|
||||
/** Checkout links for a tier, or the general sponsor pages without a tier. */
|
||||
export function getTierLinks(tier?: Tier) {
|
||||
if (!tier) {
|
||||
return { openCollective: OPEN_COLLECTIVE_URL, githubSponsors: GITHUB_SPONSORS_URL };
|
||||
}
|
||||
|
||||
const openCollectiveParams = getOpenCollectiveParams(tier.monthly, 'monthly');
|
||||
|
||||
return {
|
||||
openCollective: `${OPEN_COLLECTIVE_URL}/contribute/${tier.openCollectiveTier}/checkout?${openCollectiveParams}`,
|
||||
githubSponsors: `${GITHUB_SPONSORS_URL}/sponsorships?tier_id=${tier.githubSponsorsTierId}`,
|
||||
};
|
||||
}
|
||||
|
||||
/** Checkout links prefilled with a custom amount and frequency. */
|
||||
export function getAmountLinks(amount: number, frequency: Frequency) {
|
||||
const openCollectiveParams = getOpenCollectiveParams(amount, frequency);
|
||||
const githubSponsorsParams = new URLSearchParams({
|
||||
frequency: frequency === 'monthly' ? 'recurring' : 'one-time',
|
||||
amount: String(amount),
|
||||
});
|
||||
|
||||
return {
|
||||
openCollective: `${OPEN_COLLECTIVE_URL}/donate?${openCollectiveParams}`,
|
||||
githubSponsors: `${GITHUB_SPONSORS_URL}/sponsorships?${githubSponsorsParams}`,
|
||||
};
|
||||
}
|
||||
|
||||
export const formatEuro = (amount: number) => `€${amount.toLocaleString('en-US')}`;
|
||||
@@ -11,8 +11,11 @@ import { ICON_STYLE_CONTEXT, iconStyleContext } from './composables/useIconStyle
|
||||
import { CATEGORY_VIEW_CONTEXT, categoryViewContext } from './composables/useCategoryView';
|
||||
import { EXTERNAL_LIBS_CONTEXT, externalLibContext } from './composables/useExternalLibs';
|
||||
import SideBarNavBefore from './layouts/SideBarNavBefore.vue';
|
||||
import NavBarSponsorButton from './layouts/NavBarSponsorButton.vue';
|
||||
import SnackPlayer from './components/editors/SnackPlayer.vue';
|
||||
import Toaster from './components/base/Toaster.vue';
|
||||
import { setupTracking } from './utils/openpanel';
|
||||
import trackUnderlineDirection from './utils/underlineDirection';
|
||||
|
||||
const theme: Partial<Theme> = {
|
||||
extends: DefaultTheme,
|
||||
@@ -23,6 +26,8 @@ const theme: Partial<Theme> = {
|
||||
'sidebar-nav-after': () => h(IconsSidebarNavAfter),
|
||||
'home-hero-image': () => h(HomeHeroIconsCard),
|
||||
'home-hero-actions-after': () => h(HomeHeroAfter),
|
||||
'layout-bottom': () => h(Toaster),
|
||||
'nav-bar-content-after': () => h(NavBarSponsorButton),
|
||||
// Keep this here for now, we might want to add more things to the top bar in the future
|
||||
// 'layout-top': () => h(LayoutTop),
|
||||
});
|
||||
@@ -34,6 +39,10 @@ const theme: Partial<Theme> = {
|
||||
app.component('SnackPlayer', SnackPlayer);
|
||||
|
||||
setupTracking(router);
|
||||
|
||||
if (!import.meta.env.SSR) {
|
||||
trackUnderlineDirection();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
<script setup>
|
||||
import { useData } from 'vitepress'
|
||||
import { computed } from 'vue';
|
||||
import { useData } from 'vitepress';
|
||||
import SidebarIconCustomizer from '../components/icons/SidebarIconCustomizer.vue';
|
||||
import ExternalLibrarySelect from '../components/icons/SidebarExternalLibrarySelect.vue';
|
||||
import CategoryList from '../components/icons/CategoryList.vue';
|
||||
import KeyboardShortcuts from '../components/icons/KeyboardShortcuts.vue';
|
||||
|
||||
import CategoryList from '../components/icons/CategoryList.vue'
|
||||
import SidebarIconCustomizer from '../components/icons/SidebarIconCustomizer.vue'
|
||||
import ExternalLibrarySelect from '../components/icons/SidebarExternalLibrarySelect.vue'
|
||||
|
||||
const { page } = useData()
|
||||
const { page } = useData();
|
||||
|
||||
const isIconsPage = computed(() => page.value?.relativePath?.startsWith?.('icons'));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<SidebarIconCustomizer v-if="page?.relativePath?.startsWith?.('icons')"/>
|
||||
<ExternalLibrarySelect v-if="page?.relativePath?.startsWith?.('icons')"/>
|
||||
<CategoryList v-if="page?.relativePath?.startsWith?.('icons')"/>
|
||||
<div v-if="isIconsPage">
|
||||
<SidebarIconCustomizer />
|
||||
<ExternalLibrarySelect />
|
||||
<CategoryList />
|
||||
<KeyboardShortcuts />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { useData } from 'vitepress';
|
||||
import Icon from '@lucide/vue/src/Icon';
|
||||
import { heart } from '../../data/iconNodes';
|
||||
|
||||
const { page } = useData();
|
||||
|
||||
const isActive = computed(() => page.value.relativePath === 'sponsor.md');
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a
|
||||
href="/sponsor"
|
||||
class="sponsor-button"
|
||||
:class="{ active: isActive }"
|
||||
:aria-current="isActive ? 'page' : undefined"
|
||||
>
|
||||
<Icon
|
||||
:iconNode="heart"
|
||||
name="heart"
|
||||
:size="16"
|
||||
class="heart"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span class="label">Sponsor</span>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.sponsor-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
height: 32px;
|
||||
padding: 0 12px;
|
||||
border-radius: 16px;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
background-color: var(--vp-c-bg-soft);
|
||||
color: var(--vp-c-text-1);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
text-decoration: none;
|
||||
transition:
|
||||
border-color 0.25s,
|
||||
color 0.25s;
|
||||
}
|
||||
|
||||
.heart {
|
||||
color: var(--vp-c-brand-3);
|
||||
transition:
|
||||
fill 0.25s,
|
||||
transform 0.25s;
|
||||
}
|
||||
|
||||
.sponsor-button:hover,
|
||||
.sponsor-button.active {
|
||||
border-color: var(--vp-c-brand-3);
|
||||
}
|
||||
|
||||
.sponsor-button:hover .heart,
|
||||
.sponsor-button.active .heart {
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.sponsor-button:hover .heart {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.sponsor-button {
|
||||
margin-left: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
/* The nav menu leaves no room for the label between tablet and desktop widths. */
|
||||
@media (min-width: 768px) and (max-width: 959px) {
|
||||
.sponsor-button {
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
padding: 0;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.label {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
overflow: hidden;
|
||||
clip-path: inset(50%);
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+391
-10
@@ -62,6 +62,24 @@
|
||||
navigation: auto;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0s !important;
|
||||
transition-delay: 0s !important;
|
||||
scroll-behavior: auto !important;
|
||||
}
|
||||
|
||||
::view-transition-group(*),
|
||||
::view-transition-old(*),
|
||||
::view-transition-new(*) {
|
||||
animation: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* ::view-transition-old(icons-search-box),
|
||||
::view-transition-new(icons-search-box) {
|
||||
animation: .3s transition 0s ease;
|
||||
@@ -71,6 +89,40 @@ html {
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
|
||||
/* Page scrollbar in the site's colours: a slim rounded thumb on the page background */
|
||||
html::-webkit-scrollbar {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
html::-webkit-scrollbar-track,
|
||||
html::-webkit-scrollbar-corner {
|
||||
background-color: var(--vp-c-bg);
|
||||
}
|
||||
|
||||
html::-webkit-scrollbar-thumb {
|
||||
background-color: var(--vp-c-text-3);
|
||||
background-clip: padding-box;
|
||||
border: 3px solid transparent;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
html::-webkit-scrollbar-thumb:hover {
|
||||
background-color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
html::-webkit-scrollbar-thumb:active {
|
||||
background-color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
/* Browsers without the scrollbar pseudo-elements, such as Firefox */
|
||||
@supports not selector(::-webkit-scrollbar) {
|
||||
html {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--vp-c-text-3) var(--vp-c-bg);
|
||||
}
|
||||
}
|
||||
|
||||
.VPHome {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
@@ -108,6 +160,10 @@ html {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.VPFeature {
|
||||
border: 1px solid var(--vp-c-divider) !important;
|
||||
}
|
||||
|
||||
.VPFeature .icon {
|
||||
background-color: var(--vp-c-bg);
|
||||
}
|
||||
@@ -166,21 +222,327 @@ html {
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
/* Nav bar links: on hover an underline draws in from the side the mouse enters and leaves
|
||||
towards the side it exits (see utils/underlineDirection.ts). Without that script it
|
||||
draws in from the left and leaves to the right. It stays under the link of the current page. */
|
||||
.VPNav .VPNavBarMenuLink,
|
||||
.VPNav .VPNavBarMenuGroup .button {
|
||||
position: relative;
|
||||
transition: opacity 0.25s;
|
||||
}
|
||||
|
||||
.VPNav .VPNavBarMenuLink::before,
|
||||
.VPNav .VPNavBarMenuGroup .button::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
bottom: 16px;
|
||||
left: 12px;
|
||||
height: 2px;
|
||||
border-radius: 2px;
|
||||
background-color: var(--vp-c-brand-1);
|
||||
transform: scaleX(0);
|
||||
transform-origin: var(--underline-origin, right);
|
||||
transition: transform 0.35s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.VPNav .VPNavBarMenuLink:hover::before,
|
||||
.VPNav .VPNavBarMenuLink:focus-visible::before,
|
||||
.VPNav .VPNavBarMenuLink.active::before,
|
||||
.VPNav .VPNavBarMenuGroup:hover .button::before,
|
||||
.VPNav .VPNavBarMenuGroup .button[aria-expanded='true']::before,
|
||||
.VPNav .VPNavBarMenuGroup.active .button::before {
|
||||
transform: scaleX(1);
|
||||
transform-origin: var(--underline-origin, left);
|
||||
}
|
||||
|
||||
/* On hover the link text dims, like the dropdown buttons do; the current page keeps its brand colour */
|
||||
.VPNav .VPNavBarMenuLink:not(.active):hover {
|
||||
color: var(--vp-c-text-1);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* The chevron turns up while the dropdown is open, and the menu slides down into place.
|
||||
VitePress draws the down chevron as a right chevron turned by 90deg. */
|
||||
.VPNav .VPNavBarMenuGroup .text-icon {
|
||||
transition: transform 0.35s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.VPNav .VPNavBarMenuGroup:hover .text-icon,
|
||||
.VPNav .VPNavBarMenuGroup .button[aria-expanded='true'] .text-icon {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.VPNav .VPNavBarMenuGroup .button[aria-expanded='false'] + .menu {
|
||||
transform: translateY(-8px);
|
||||
}
|
||||
|
||||
.VPNav .VPNavBarMenuGroup:hover .button + .menu,
|
||||
.VPNav .VPNavBarMenuGroup .button[aria-expanded='true'] + .menu {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* Appearance switch: the thumb sits 2px inside the track and slides over with a small bounce,
|
||||
while the sun and the moon turn and grow as they swap places */
|
||||
.VPNav .VPSwitchAppearance .check {
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
}
|
||||
|
||||
html.dark .VPNav .VPSwitchAppearance .check {
|
||||
transform: translateX(18px);
|
||||
}
|
||||
|
||||
.VPNav .VPSwitchAppearance:hover .check,
|
||||
.VPNav .VPSwitchAppearance:focus-visible .check {
|
||||
box-shadow:
|
||||
var(--vp-shadow-1),
|
||||
0 0 0 3px color-mix(in srgb, var(--vp-c-brand) 25%, transparent);
|
||||
}
|
||||
|
||||
.VPNav .VPSwitchAppearance .icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.VPNav .VPSwitchAppearance .icon .sun,
|
||||
.VPNav .VPSwitchAppearance .icon .moon {
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
transition:
|
||||
opacity 0.3s ease,
|
||||
transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
}
|
||||
|
||||
.VPNav .VPSwitchAppearance .icon .moon,
|
||||
html.dark .VPNav .VPSwitchAppearance .icon .sun {
|
||||
opacity: 0;
|
||||
transform: rotate(-90deg) scale(0.4);
|
||||
}
|
||||
|
||||
.VPNav .VPSwitchAppearance .icon .sun,
|
||||
html.dark .VPNav .VPSwitchAppearance .icon .moon {
|
||||
opacity: 1;
|
||||
transform: rotate(0) scale(1);
|
||||
}
|
||||
|
||||
/* Nav bar social icons get a soft rounded background and lift a little on hover */
|
||||
.VPNav .VPSocialLinks .VPSocialLink {
|
||||
border-radius: 8px;
|
||||
transition:
|
||||
color 0.25s,
|
||||
background-color 0.25s,
|
||||
transform 0.25s;
|
||||
}
|
||||
|
||||
.VPNav .VPSocialLinks .VPSocialLink:hover,
|
||||
.VPNav .VPSocialLinks .VPSocialLink:focus-visible {
|
||||
outline: none;
|
||||
color: var(--vp-c-text-1);
|
||||
background-color: var(--vp-c-default-soft);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.package-lucide-logo {
|
||||
margin: 0 auto 48px;
|
||||
max-width: 480px;
|
||||
}
|
||||
|
||||
.VPSidebar {
|
||||
/* Hide Scrollbar */
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
scrollbar-width: thin; /* can also be normal, or none, to not render scrollbar */
|
||||
scrollbar-color: var(--vp-c-text-4) transparent; /* foreground background */
|
||||
/* Sidebar links: a soft pill on hover with the text sliding in a little, a light brand pill
|
||||
for the current page, and a smooth turn of the section chevrons */
|
||||
.VPSidebar .VPSidebarItem.is-link > .item > .link {
|
||||
margin: 1px -8px;
|
||||
padding: 0 8px;
|
||||
border-radius: 6px;
|
||||
transition: background-color 0.25s;
|
||||
}
|
||||
|
||||
.VPSidebar .VPSidebarItem.is-link > .item > .link > .text {
|
||||
transition:
|
||||
color 0.25s,
|
||||
transform 0.25s ease;
|
||||
}
|
||||
|
||||
.VPSidebar .VPSidebarItem.is-link > .item > .link:hover,
|
||||
.VPSidebar .VPSidebarItem.is-link > .item > .link:focus-visible {
|
||||
outline: none;
|
||||
background-color: var(--vp-c-default-soft);
|
||||
}
|
||||
|
||||
.VPSidebar .VPSidebarItem.is-link > .item > .link:hover > .text,
|
||||
.VPSidebar .VPSidebarItem.is-link > .item > .link:focus-visible > .text {
|
||||
transform: translateX(3px);
|
||||
}
|
||||
|
||||
.VPSidebar .VPSidebarItem.is-active > .item > .link {
|
||||
background-color: color-mix(in srgb, var(--vp-c-brand) 10%, transparent);
|
||||
}
|
||||
|
||||
.VPSidebar .VPSidebarItem .caret-icon {
|
||||
transition: transform 0.3s cubic-bezier(0.34, 1.4, 0.64, 1);
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.VPSidebar {
|
||||
padding-right: 24px !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Links in the page text: a faint underline shows they are links, and on hover a solid
|
||||
underline draws in from the left. Drawn with backgrounds so it also follows links that
|
||||
wrap onto a second line. */
|
||||
.vp-doc a:not([class]) {
|
||||
text-decoration: none;
|
||||
padding-bottom: 1px;
|
||||
background-image:
|
||||
linear-gradient(currentColor, currentColor),
|
||||
linear-gradient(
|
||||
color-mix(in srgb, var(--vp-c-brand) 35%, transparent),
|
||||
color-mix(in srgb, var(--vp-c-brand) 35%, transparent)
|
||||
);
|
||||
background-position:
|
||||
0 100%,
|
||||
0 100%;
|
||||
background-repeat: no-repeat;
|
||||
background-size:
|
||||
0% 2px,
|
||||
100% 1px;
|
||||
transition:
|
||||
color 0.25s,
|
||||
background-size 0.35s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.vp-doc a:not([class]):hover,
|
||||
.vp-doc a:not([class]):focus-visible {
|
||||
background-size:
|
||||
100% 2px,
|
||||
100% 1px;
|
||||
}
|
||||
|
||||
.vp-doc a:not([class]):has(img) {
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
/* "Edit this page": a soft brand pill on hover, with the pen tilting as if it starts writing */
|
||||
.VPDocFooter .edit-link-button {
|
||||
margin-left: -10px;
|
||||
padding: 0 12px 0 10px;
|
||||
border-radius: 8px;
|
||||
transition:
|
||||
color 0.25s,
|
||||
background-color 0.25s;
|
||||
}
|
||||
|
||||
.VPDocFooter .edit-link-button:hover,
|
||||
.VPDocFooter .edit-link-button:focus-visible {
|
||||
outline: none;
|
||||
background-color: color-mix(in srgb, var(--vp-c-brand) 10%, transparent);
|
||||
}
|
||||
|
||||
.VPDocFooter .edit-link-icon {
|
||||
transform-origin: 30% 70%;
|
||||
transition: transform 0.35s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
}
|
||||
|
||||
.VPDocFooter .edit-link-button:hover .edit-link-icon,
|
||||
.VPDocFooter .edit-link-button:focus-visible .edit-link-icon {
|
||||
transform: rotate(-14deg) scale(1.1);
|
||||
}
|
||||
|
||||
/* Previous / next page links: Lucide's arrow-left and arrow-right icons point the way, and
|
||||
on hover the card lifts with a light brand tint while its arrow moves on */
|
||||
.VPDocFooter .pager-link {
|
||||
transition:
|
||||
border-color 0.25s,
|
||||
background-color 0.25s,
|
||||
transform 0.25s ease,
|
||||
box-shadow 0.25s;
|
||||
}
|
||||
|
||||
.VPDocFooter .pager-link:hover,
|
||||
.VPDocFooter .pager-link:focus-visible {
|
||||
outline: none;
|
||||
transform: translateY(-2px);
|
||||
background-color: color-mix(in srgb, var(--vp-c-brand) 6%, transparent);
|
||||
box-shadow: var(--vp-shadow-2);
|
||||
}
|
||||
|
||||
.VPDocFooter .pager-link .title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.VPDocFooter .pager-link.next .title {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.VPDocFooter .pager-link.prev .title::before,
|
||||
.VPDocFooter .pager-link.next .title::after {
|
||||
content: '';
|
||||
flex-shrink: 0;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-color: currentColor;
|
||||
transition: transform 0.25s ease;
|
||||
}
|
||||
|
||||
.VPDocFooter .pager-link.prev .title::before {
|
||||
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m12 19-7-7 7-7'/%3E%3Cpath d='M19 12H5'/%3E%3C/svg%3E")
|
||||
center / contain no-repeat;
|
||||
}
|
||||
|
||||
.VPDocFooter .pager-link.next .title::after {
|
||||
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12h14'/%3E%3Cpath d='m12 5 7 7-7 7'/%3E%3C/svg%3E")
|
||||
center / contain no-repeat;
|
||||
}
|
||||
|
||||
.VPDocFooter .pager-link.prev:hover .title::before {
|
||||
transform: translateX(-4px);
|
||||
}
|
||||
|
||||
.VPDocFooter .pager-link.next:hover .title::after {
|
||||
transform: translateX(4px);
|
||||
}
|
||||
|
||||
/* "On this page" outline: each heading is a soft pill on hover and slides in a little,
|
||||
the current heading takes the brand colour and the marker glides to it */
|
||||
.VPDoc .VPDocAsideOutline .outline-link {
|
||||
margin: 0 -8px;
|
||||
padding: 0 8px;
|
||||
border-radius: 6px;
|
||||
transition:
|
||||
color 0.25s,
|
||||
background-color 0.25s,
|
||||
padding-left 0.25s ease;
|
||||
}
|
||||
|
||||
.VPDoc .VPDocAsideOutline .outline-link:hover,
|
||||
.VPDoc .VPDocAsideOutline .outline-link:focus-visible {
|
||||
outline: none;
|
||||
padding-left: 12px;
|
||||
color: var(--vp-c-text-1);
|
||||
background-color: var(--vp-c-default-soft);
|
||||
}
|
||||
|
||||
.VPDoc .VPDocAsideOutline .outline-link.active {
|
||||
color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.VPDoc .VPDocAsideOutline .outline-marker {
|
||||
transition:
|
||||
top 0.35s cubic-bezier(0.34, 1.3, 0.64, 1),
|
||||
opacity 0.25s;
|
||||
}
|
||||
|
||||
/* Sidebar scrollbar: a slim rounded thumb that stays quiet until the sidebar is hovered */
|
||||
.VPSidebar::-webkit-scrollbar {
|
||||
width: 0;
|
||||
display: none;
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.VPSidebar::-webkit-scrollbar-track {
|
||||
@@ -188,8 +550,27 @@ html {
|
||||
}
|
||||
|
||||
.VPSidebar::-webkit-scrollbar-thumb {
|
||||
background: transparent;
|
||||
border: none;
|
||||
background-color: var(--vp-c-divider);
|
||||
background-clip: padding-box;
|
||||
border: 2px solid transparent;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.VPSidebar:hover::-webkit-scrollbar-thumb {
|
||||
background-color: var(--vp-c-text-3);
|
||||
}
|
||||
|
||||
.VPSidebar::-webkit-scrollbar-thumb:hover,
|
||||
.VPSidebar::-webkit-scrollbar-thumb:active {
|
||||
background-color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
/* Browsers without the scrollbar pseudo-elements, such as Firefox */
|
||||
@supports not selector(::-webkit-scrollbar) {
|
||||
.VPSidebar {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--vp-c-text-3) transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.custom-block.success {
|
||||
|
||||
@@ -11,7 +11,7 @@ const allowedAttrs = [
|
||||
'class',
|
||||
];
|
||||
|
||||
export default function getSVGIcon(element?: HTMLElement, attrs?: Record<string, string>) {
|
||||
export default function getSVGIcon(element?: Element, attrs?: Record<string, string>) {
|
||||
const svg = element ?? document.querySelector('#previewer svg');
|
||||
if (!svg) return;
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
// Links with a hover underline: the nav bar menu
|
||||
const UNDERLINED_LINKS = '.VPNav .VPNavBarMenuLink, .VPNav .VPNavBarMenuGroup';
|
||||
|
||||
// Makes the hover underline follow the mouse: it draws in from the side the pointer enters
|
||||
// and leaves towards the side the pointer exits. The side is passed to the CSS as a variable.
|
||||
function setUnderlineOrigin(event: PointerEvent) {
|
||||
const link = event.target;
|
||||
if (!(link instanceof HTMLElement) || !link.matches(UNDERLINED_LINKS)) return;
|
||||
|
||||
const { left, width } = link.getBoundingClientRect();
|
||||
const side = event.clientX < left + width / 2 ? 'left' : 'right';
|
||||
|
||||
link.style.setProperty('--underline-origin', side);
|
||||
}
|
||||
|
||||
export default function trackUnderlineDirection() {
|
||||
// pointerenter and pointerleave do not bubble, so they are caught on their way down instead
|
||||
document.addEventListener('pointerenter', setUnderlineOrigin, true);
|
||||
document.addEventListener('pointerleave', setUnderlineOrigin, true);
|
||||
}
|
||||
@@ -14,6 +14,8 @@ export default function useSearchShortcut(callback: () => void) {
|
||||
// Check for Cmd+K (Mac), Ctrl+K (Windows/Linux), or forward slash
|
||||
if (((event.metaKey || event.ctrlKey) && event.key === 'k') || event.key === '/') {
|
||||
event.preventDefault();
|
||||
// Algolia listens for the same keys on window, so claim them before it sees them
|
||||
event.stopImmediatePropagation();
|
||||
callback();
|
||||
}
|
||||
}
|
||||
@@ -24,11 +26,11 @@ export default function useSearchShortcut(callback: () => void) {
|
||||
shortcutText.value = isMac ? '⌘K' : 'Ctrl+K';
|
||||
|
||||
// Add keyboard shortcut listener
|
||||
window.addEventListener('keydown', handleKeydown);
|
||||
window.addEventListener('keydown', handleKeydown, { capture: true });
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('keydown', handleKeydown);
|
||||
window.removeEventListener('keydown', handleKeydown, { capture: true });
|
||||
});
|
||||
|
||||
return { shortcutText };
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { type PageData } from 'vitepress';
|
||||
import getStructuredData from './getStructuredData';
|
||||
import getStructuredData, { getFaqStructuredData } from './getStructuredData';
|
||||
import { createGeneralOGImage, createIconOGImage } from './createOGImage';
|
||||
|
||||
export async function transformPageData(pageData: PageData) {
|
||||
@@ -53,6 +53,15 @@ export async function transformPageData(pageData: PageData) {
|
||||
]);
|
||||
}
|
||||
|
||||
// Pages can list their FAQ in the `faq` frontmatter, rendered by the `Faq` component.
|
||||
if (Array.isArray(pageData.frontmatter.faq)) {
|
||||
pageData.frontmatter.head.push([
|
||||
'script',
|
||||
{ type: 'application/ld+json' },
|
||||
JSON.stringify(getFaqStructuredData(pageData.frontmatter.faq)),
|
||||
]);
|
||||
}
|
||||
|
||||
if (pageData.relativePath.startsWith('guide/')) {
|
||||
const ogPath = await createGeneralOGImage(pageData);
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ description: Learn the best practices for writing use cases for Lucide icons.
|
||||
---
|
||||
|
||||
<script setup>
|
||||
import { batteryLow, creditCard, crop, handshake, heading2, microchip, search, squareLetterP, triangleAlert, wifiOff } from '~/.vitepress/data/iconNodes';
|
||||
import { batteryLow, creditCard, crop, handshake, heading2, microchip, search, squareLetterP, triangleExclamationPoint, wifiOff } from '~/.vitepress/data/iconNodes';
|
||||
import LucideIcon from '~/.vitepress/theme/components/base/LucideIcon.vue';
|
||||
</script>
|
||||
|
||||
@@ -120,10 +120,10 @@ The period adds **unnecessary punctuation** and makes entries inconsistent.
|
||||
Use plain text only. Formatting belongs in documentation, not metadata values.
|
||||
|
||||
:::: example
|
||||
::: do <LucideIcon name="triangle-alert" :iconNode="triangleAlert" /> Warning users about a destructive action
|
||||
::: do <LucideIcon name="triangle-exclamation-point" :iconNode="triangleExclamationPoint" /> Warning users about a destructive action
|
||||
This works in search, generated pages, and other places that use metadata.
|
||||
:::
|
||||
::: dont <LucideIcon name="triangle-alert" :iconNode="triangleAlert" /> <span>**Warning** users about a destructive action ⚠️</span>
|
||||
::: dont <LucideIcon name="triangle-exclamation-point" :iconNode="triangleExclamationPoint" /> <span>**Warning** users about a destructive action ⚠️</span>
|
||||
Markdown and emoji can **show up in generated UI** and make metadata **harder to reuse**.
|
||||
:::
|
||||
::::
|
||||
|
||||
@@ -31,7 +31,7 @@ You can then reference the registered icons by name:
|
||||
|
||||
```html [app.html]
|
||||
<svg lucideIcon="square-check"></svg>
|
||||
<svg lucideIcon="circle-alert"></svg>
|
||||
<svg lucideIcon="circle-exclamation-point"></svg>
|
||||
```
|
||||
|
||||
## How names are resolved
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
---
|
||||
title: Comparison
|
||||
description: A comparison between Lucide and Feather Icons, highlighting the differences and benefits of each.
|
||||
---
|
||||
|
||||
# Comparison
|
||||
|
||||
## Lucide vs Feather Icons
|
||||
|
||||
Lucide is a community-driven fork of [Feather Icons](https://github.com/feathericons/feather).
|
||||
|
||||
The decision to create Lucide arose from growing dissatisfaction with the moderation of the Feather Icons project. With more than 300 open issues and over 100 open PRs, the Feather Icons project has been abandoned and is no longer actively maintained. Unfortunately, this means that numerous developers and designers have invested their time in contributing to Feather Icons without the possibility of their PRs being accepted.
|
||||
|
||||
In an effort to expand the icon set while remaining true to the original minimalist design language, Lucide is driven by a community of developers and designers. We strive to grow together and maintain a faithful continuation of the project.
|
||||
|
||||
### Why should I choose Lucide over Feather Icons?
|
||||
|
||||
- Lucide has expanded its icon set by 1600+ in the last few years. Lucide now has over 1800 icons, while Feather has around 287 icons.
|
||||
- Well maintained code base.
|
||||
- Active community.
|
||||
|
||||
### Should I migrate to Lucide?
|
||||
|
||||
That depends on whether you're satisfied with the icons from Feather Icons. If that is the case, it may not be worth the effort.
|
||||
However, if you find yourself struggling and feeling limited by the icons provided by Feather, you can consider migrating.
|
||||
When we forked, we didn't remove any icons, but some icons have been renamed.
|
||||
@@ -2,7 +2,6 @@
|
||||
title: What is Lucide?
|
||||
description: An introduction to Lucide, an open-source icon library that provides 1600+ vector (svg) files for displaying icons and symbols in digital and non-digital projects.
|
||||
nextPage:
|
||||
- comparison
|
||||
- installation
|
||||
---
|
||||
|
||||
|
||||
+7
-5
@@ -17,6 +17,7 @@
|
||||
"prebuild:iconDetails": "node ./scripts/writeIconDetails.mts",
|
||||
"prebuild:iconPopularity": "node --env-file-if-exists=.env ./scripts/writeiconPopularity.mts && node ./scripts/writeiconPopularityIndex.mts",
|
||||
"prebuild:brandStopwords": "node ./scripts/writeBrandStopwords.mts",
|
||||
"prebuild:sponsors": "node --env-file-if-exists=.env ./scripts/writeSponsorsData.mts",
|
||||
"prebuild:writeLabIconNodes": "node ./scripts/writeLabIconNodes.mts",
|
||||
"prebuild:writeLabIconMetaIndex": "node ./scripts/writeLabIconMetaIndex.mts",
|
||||
"postbuild:vercelJson": "node ./scripts/writeVercelOutput.mts",
|
||||
@@ -41,12 +42,12 @@
|
||||
"h3": "^1.15.11",
|
||||
"markdown-it": "^15.0.3",
|
||||
"markdown-it-container": "^4.0.0",
|
||||
"nitropack": "^2.13.4",
|
||||
"nitropack": "^2.13.5",
|
||||
"rollup-plugin-copy": "^3.5.0",
|
||||
"semver": "^7.8.5",
|
||||
"sharp": "^0.35.5",
|
||||
"shiki": "^4.4.3",
|
||||
"simple-git": "^4.0.1",
|
||||
"shiki": "^4.5.0",
|
||||
"simple-git": "^4.0.2",
|
||||
"sitemap": "^9.0.1",
|
||||
"svg-path-commander": "^2.3.3",
|
||||
"svg-pathdata": "^9.0.0",
|
||||
@@ -72,10 +73,11 @@
|
||||
"lodash-es": "^4.18.1",
|
||||
"lucide": "workspace:*",
|
||||
"lucide-react": "workspace:*",
|
||||
"motion-v": "2.4.0",
|
||||
"motion-v": "2.11.0",
|
||||
"react": "19.3.1",
|
||||
"react-dom": "19.3.1",
|
||||
"sandpack-vue3": "3.1.12",
|
||||
"vue": "^3.5.44"
|
||||
"vue": "^3.5.44",
|
||||
"vue-sonner": "^2.0.9"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
{
|
||||
"$schema": "https://fundingjson.org/schema/v1.0.0.json",
|
||||
"version": "v1.0.0",
|
||||
"entity": {
|
||||
"type": "organisation",
|
||||
"role": "steward",
|
||||
"name": "Lucide Icons",
|
||||
"email": "info@lucide.dev",
|
||||
"description": "Lucide Icons is the team that maintains Lucide, a community-driven, open-source icon library. The project is led by Eric Fennis together with co-maintainer Karsa Mistmere.",
|
||||
"webpageUrl": {
|
||||
"url": "https://lucide.dev"
|
||||
}
|
||||
},
|
||||
"projects": [
|
||||
{
|
||||
"guid": "lucide",
|
||||
"name": "Lucide",
|
||||
"description": "Beautiful and consistent icon toolkit made by the community. Lucide provides a large set of open-source SVG icons plus official packages for React, Vue, Svelte, Solid, Preact, Angular, React Native and vanilla JavaScript, along with a Figma plugin. It is free for personal and commercial use under the ISC license.",
|
||||
"webpageUrl": {
|
||||
"url": "https://lucide.dev"
|
||||
},
|
||||
"repositoryUrl": {
|
||||
"url": "https://github.com/lucide-icons/lucide",
|
||||
"wellKnown": "https://github.com/lucide-icons/lucide/blob/main/.well-known/funding-manifest-urls"
|
||||
},
|
||||
"licenses": ["spdx:ISC"],
|
||||
"tags": ["icons", "svg", "design", "user-interface", "react", "vue", "svelte", "figma"]
|
||||
}
|
||||
],
|
||||
"funding": {
|
||||
"channels": [
|
||||
{
|
||||
"guid": "opencollective",
|
||||
"type": "payment-provider",
|
||||
"address": "https://opencollective.com/lucide-icons",
|
||||
"description": "Recurring or one-time contributions via Open Collective."
|
||||
},
|
||||
{
|
||||
"guid": "github-sponsors-lucide",
|
||||
"type": "payment-provider",
|
||||
"address": "https://github.com/sponsors/lucide-icons",
|
||||
"description": "Sponsor the Lucide project via GitHub Sponsors."
|
||||
},
|
||||
{
|
||||
"guid": "github-sponsors-ericfennis",
|
||||
"type": "payment-provider",
|
||||
"address": "https://github.com/sponsors/ericfennis",
|
||||
"description": "Sponsor the lead maintainer via GitHub Sponsors."
|
||||
}
|
||||
],
|
||||
"plans": [
|
||||
{
|
||||
"guid": "maintainer-time",
|
||||
"status": "active",
|
||||
"name": "Maintainer time",
|
||||
"description": "Funds 44 hours of maintenance per week: 32 hours for Eric Fennis and 12 hours for Karsa Mistmere at EUR 40 per hour. Covers reviewing icon contributions, maintaining the framework packages, releases, documentation and community support.",
|
||||
"amount": 91520,
|
||||
"currency": "EUR",
|
||||
"frequency": "yearly",
|
||||
"channels": ["opencollective", "github-sponsors-lucide", "github-sponsors-ericfennis"]
|
||||
},
|
||||
{
|
||||
"guid": "infrastructure-tooling",
|
||||
"status": "active",
|
||||
"name": "Infrastructure and tooling",
|
||||
"description": "Covers hosting on DigitalOcean (EUR 1,200/year), the domain (EUR 10/year), design and project tools Figma, Notion and Linear (EUR 900/year) and AI coding assistance (EUR 240/year).",
|
||||
"amount": 2350,
|
||||
"currency": "EUR",
|
||||
"frequency": "yearly",
|
||||
"channels": ["opencollective", "github-sponsors-lucide"]
|
||||
},
|
||||
{
|
||||
"guid": "custom-contribution",
|
||||
"status": "active",
|
||||
"name": "Custom contribution",
|
||||
"description": "Contribute any amount to support Lucide.",
|
||||
"amount": 0,
|
||||
"currency": "EUR",
|
||||
"frequency": "one-time",
|
||||
"channels": ["opencollective", "github-sponsors-lucide", "github-sponsors-ericfennis"]
|
||||
}
|
||||
],
|
||||
"history": []
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="164" height="23" fill="none" viewBox="0 0 164 23"><g clip-path="url(#a)"><g fill="#f34e3f" clip-path="url(#b)"><path d="m8.47 6 2.55.69L9.3.19 6.35 1l1.04 3.93c.14.53.56.95 1.08 1.09M4.16 19.22l5.56-5.55 2.03 7.56 2.94-.79-2.93-10.98L.78 6.54 0 9.49l7.55 2.02-5.53 5.54zM14.53 10.2l.68 2.56c.14.53.56.94 1.08 1.08l3.96 1.05.78-2.95zM16.87 2.2l-2.53 2.54c-.28.27-.44.65-.45 1.04l-.04 1.6 1.48.06c-.02 0 .02 0 0 0 .4 0 .79-.16 1.07-.45l2.62-2.61z"/></g></g><path fill="#f8f8f8" d="M35.86 10.13q-1.35-.57-3.37-.94-1.37-.3-2.07-.51T29.4 8.1a1.3 1.3 0 0 1-.33-.94q0-.7.39-1.1.38-.39 1.05-.54t1.67-.16q1.2 0 2.4.3 1.18.28 2.56.9l.5-2.75q-1.26-.6-2.62-.89-1.37-.29-2.84-.29-1.76 0-3.2.55-1.45.54-2.32 1.57a3.8 3.8 0 0 0-.86 2.52q0 1.3.66 2.2.67.89 1.96 1.43t3.3.9q1.41.29 2.13.52.7.23 1.11.64.4.41.4 1.2 0 .7-.45 1.11t-1.2.56-1.8.14a15 15 0 0 1-6.13-1.4l-.3 2.73q1.2.69 2.89 1.05 1.67.35 3.59.35c1.27 0 2.33-.18 3.34-.54q1.5-.55 2.4-1.6t.91-2.56q0-1.4-.7-2.36a5 5 0 0 0-2.05-1.52M49.71 6.82a7 7 0 0 0-3.43-.8c-1.26 0-2.44.26-3.44.8q-1.52.81-2.37 2.27a6.3 6.3 0 0 0-.86 3.27q0 1.8.85 3.24a6 6 0 0 0 2.37 2.28 7 7 0 0 0 3.45.83c1.3 0 2.4-.28 3.43-.83a6.1 6.1 0 0 0 3.25-5.54q0-1.81-.86-3.26a6 6 0 0 0-2.39-2.25m-.2 7.47q-.47.84-1.31 1.32t-1.9.48c-.69 0-1.37-.16-1.93-.48q-.84-.48-1.33-1.33a4 4 0 0 1-.5-1.96q0-1.12.5-1.93a3.5 3.5 0 0 1 1.33-1.3 4 4 0 0 1 1.91-.46q1.07 0 1.92.47t1.3 1.3.48 1.96-.47 1.93M63.34 13.5q-.07.23-.19.44a4 4 0 0 1-1.32 1.5q-.85.56-1.89.56-1.37 0-2.06-.74-.67-.74-.67-2.12V6.26h-2.96v7.32q0 1.56.62 2.7.63 1.17 1.74 1.78 1.12.63 2.55.63 2.18 0 3.45-1.11.42-.37.74-.8v1.66h2.95V6.26h-2.95zM74.49 6.04q-2.1 0-3.14 1.37a5 5 0 0 0-.44.73V6.26h-2.96v12.18h2.96v-5.01q0-2.04.88-3.28a2.8 2.8 0 0 1 2.39-1.23h.73q.22.02.4.02h.36L76 6.2a8 8 0 0 0-1.51-.15M80.82 9.07a4 4 0 0 1 1.94-.46 5 5 0 0 1 3.25 1.13l1.22-2.2a8 8 0 0 0-2.22-1.16 8 8 0 0 0-2.5-.37q-1.8 0-3.28.8a6 6 0 0 0-2.36 2.23 6.4 6.4 0 0 0-.87 3.32q0 1.86.85 3.3.84 1.43 2.34 2.22a7 7 0 0 0 3.3.78 8 8 0 0 0 2.47-.34 9 9 0 0 0 2.27-1.14l-1.17-2.15a7 7 0 0 1-1.62.8 5 5 0 0 1-1.66.28q-1.08 0-1.95-.44a3.3 3.3 0 0 1-1.37-1.3 4 4 0 0 1-.5-2q0-1.18.5-2.02.5-.83 1.36-1.28M96.8 6.74q-1.36-.7-2.98-.7a7 7 0 0 0-3.42.8 5.7 5.7 0 0 0-2.28 2.26 6.6 6.6 0 0 0-.8 3.26c0 1.21.28 2.26.85 3.23a6 6 0 0 0 2.38 2.27q1.53.82 3.45.82 1.33 0 2.38-.27 1.05-.29 2-.92l-.76-2.42q-.7.51-1.65.78a6 6 0 0 1-1.82.28q-1.2 0-2.07-.43a3 3 0 0 1-1.33-1.28q-.26-.45-.37-1.04h8.95a5.3 5.3 0 0 0-.41-4.73 5.3 5.3 0 0 0-2.12-1.92m-6.1 3.6q.4-.85 1.2-1.3a4 4 0 0 1 1.87-.45q.98 0 1.7.39.7.39 1.04 1.18.19.48.23 1.07h-6.35q.1-.48.3-.89M110.92 7.92a4.6 4.6 0 0 0-1.84-1.43 7 7 0 0 0-2.63-.48q-1.65 0-2.94.78a5.5 5.5 0 0 0-2.05 2.15q-.75 1.38-.75 3.09c0 1.14.25 2.1.75 2.98q.75 1.32 2.07 2.09t2.97.77q1.47 0 2.57-.47a4.3 4.3 0 0 0 1.81-1.42v.58q0 1.64-1.06 2.6a4.2 4.2 0 0 1-2.92.95 8 8 0 0 1-4.45-1.38l-1.16 2.09q1.2.81 2.7 1.25t3.24.43q3.12 0 4.87-1.66 1.77-1.67 1.76-4.64V6.26h-2.93zm-.5 5.98q-.47.81-1.3 1.25t-1.91.43q-1 0-1.81-.46a3.3 3.3 0 0 1-1.28-1.27 3.84 3.84 0 0 1 0-3.7q.46-.82 1.28-1.3a4.05 4.05 0 0 1 3.73-.03q.8.44 1.3 1.25.47.83.47 1.91 0 1.1-.48 1.92M118.95 7.41a5 5 0 0 0-.44.73V6.26h-2.96v12.18h2.96v-5.01q0-2.04.88-3.28a2.8 2.8 0 0 1 2.39-1.23h.73l.4.03h.36l.33-2.76a8 8 0 0 0-1.51-.15q-2.1 0-3.14 1.37M133.69 7.36Q132.26 6 129.34 6q-2.22 0-4.47.74l.36 2.6a9 9 0 0 1 1.8-.55q.98-.2 2-.19 1.56 0 2.35.6.78.6.79 1.77v.1a57 57 0 0 0-3.76-.19q-2.41 0-3.56 1.1a3.7 3.7 0 0 0-1.16 2.79q0 1.12.51 2.02.52.89 1.48 1.38.98.5 2.24.5 1.25 0 2.22-.36a4.4 4.4 0 0 0 2.03-1.47v1.59h2.96v-7.26q0-2.48-1.44-3.83m-2.95 8.38q-.73.42-1.84.42a3.5 3.5 0 0 1-1.8-.4 1.3 1.3 0 0 1-.63-1.14q0-.8.65-1.22.64-.42 2.18-.42.84 0 1.53.03.68.04 1.34.12a3.5 3.5 0 0 1-.35 1.5q-.34.7-1.08 1.11M146.9 6.84a5.7 5.7 0 0 0-2.99-.83q-1.26 0-2.36.44a5 5 0 0 0-1.89 1.25V6.26h-2.96v16h2.96v-5.22q.51.56 1.2.91.66.36 1.42.56t1.61.2q1.67 0 3.03-.79a5.7 5.7 0 0 0 2.14-2.23q.8-1.44.8-3.3c0-1.25-.27-2.31-.8-3.28a6 6 0 0 0-2.16-2.28m-.44 7.44q-.46.84-1.27 1.3-.8.46-1.84.46a4 4 0 0 1-1.93-.44q-.84-.44-1.3-1.29a4 4 0 0 1-.46-1.95q0-1.12.47-1.95t1.3-1.3 1.92-.46q1.03 0 1.84.47t1.27 1.32q.45.86.45 1.94 0 1.07-.45 1.9M162.78 8.41q-.63-1.15-1.74-1.77a5 5 0 0 0-2.56-.63q-2.17 0-3.45 1.11-.41.37-.73.8V2.06h-2.96v16.38h2.96V11.2q.08-.23.19-.44a4 4 0 0 1 1.32-1.5q.84-.57 1.9-.56 1.37 0 2.05.74t.68 2.12v6.88h2.96v-7.32a5.6 5.6 0 0 0-.63-2.7"/><defs><clipPath id="a"><path fill="#fff" d="M0 .2h21.03v21.03H0z"/></clipPath><clipPath id="b"><path fill="#fff" d="M0 .2h21.03v21.03H0z"/></clipPath></defs></svg>
|
||||
|
After Width: | Height: | Size: 4.5 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="175" height="31" fill="none" viewBox="0 0 175 31"><path fill="#fff" fill-rule="evenodd" d="M15.48 24.45a8.96 8.96 0 0 0 7.39-14.05l5.82-5.82c.5-.5.21-1.34-.47-1.47a39 39 0 0 1-6.55-1.83l-.13-.05-.77-.3a15.5 15.5 0 0 0-16.23 3.6A15.4 15.4 0 0 0 .76 20.28c.26.81 1.22 1.12 1.96.7.63-.37.88-1.14.67-1.83a12.63 12.63 0 0 1 19.8-13.68L20.56 8.1A8.96 8.96 0 0 0 8.1 20.57l-5.82 5.82c-.49.5-.21 1.34.47 1.47a39 39 0 0 1 6.55 1.83l.13.05.78.3a15.5 15.5 0 0 0 16.17-3.54l.06-.07a15.4 15.4 0 0 0 3.78-15.74c-.27-.81-1.22-1.12-1.97-.7a1.6 1.6 0 0 0-.67 1.84 12.63 12.63 0 0 1-19.8 13.66l2.63-2.62c1.44 1 3.2 1.58 5.08 1.58m5.95-8.97a6.1 6.1 0 0 1-9.02 5.38l8.29-8.29q.72 1.32.73 2.91m-12.22 0a6.11 6.11 0 0 1 9.23-5.25l-8.38 8.37a6 6 0 0 1-.85-3.12" clip-rule="evenodd"/><path fill="#fff" d="M80.9 23.48H79v-15h1.9v5.48l-.25 1.62h.4c.44-1.62 1.54-2.4 3.56-2.4 2.58 0 3.76 1.32 3.76 4v6.3h-2.1l.4-5.9c.12-1.98-.82-2.72-2.62-2.72-2.12 0-3.14 1.28-3.14 3.38zM42.5 11.12V9.48h10.12v4.14l-10.18 7.8v.4h10.54v1.66H42.14v-4.16l10.18-7.8v-.4z"/><path fill="#fff" fill-rule="evenodd" d="M62.29 19.4h1.88c-.3 2.74-1.86 4.38-4.76 4.38-3.34 0-5.16-2.04-5.16-5.3s1.76-5.3 4.96-5.3c3 0 4.52 1.8 4.52 4.98h-7.6c0 2.36.6 4.02 3.28 4.02 1.9 0 2.7-.96 2.88-2.78m-5.98-2.54h5.46c-.02-1.24-.84-2.1-2.52-2.1s-2.6.7-2.94 2.1M66.1 28.08H68V23l-.2-1.82h.4c.5 1.62 1.61 2.6 3.8 2.6 3.05 0 4.73-2.04 4.73-5.3s-1.68-5.3-4.74-5.3c-2.1 0-3.3.98-3.8 2.6h-.4l.2-2.3h-1.9zm5.51-5.94c-2.22 0-3.52-1.5-3.52-3.68s1.12-3.68 3.52-3.68c2.14 0 3.22 1.32 3.22 3.68s-1.08 3.68-3.22 3.68" clip-rule="evenodd"/><path fill="#fff" d="M91.62 13.48h-1.9l2.92 10h2.08l-.66 2.08c-.14.54-.2.58-.76.56l-3.38-.04v1.9l2.98-.26c1.92-.16 2.82-1.36 3.3-3.08l3.3-11.16h-1.9l-2.8 9.7h-.4zM103.13 23.48h-1.9v-10h1.9l-.2 2.1h.4c.42-1.62 1.32-2.1 3.04-2.1h1.78v1.86l-2.18-.2c-1.88-.16-2.84.98-2.84 3.1zM121.92 22.14c-3 0-4.5-1.86-4.5-5.66s1.42-5.66 4.2-5.66c2.4 0 3.72 1.22 4 4.04h1.96c-.32-3.88-2.34-5.68-5.94-5.68-4.06 0-6.16 2.44-6.16 7.3s2.18 7.3 6.46 7.3c3.78 0 5.92-1.8 6.24-5.7h-1.96c-.28 2.84-1.72 4.06-4.3 4.06M129.47 8.48h1.74q2.66-.02 2.64 2.64v10.74h3.14v1.62h-8.12v-1.62h3.16V10.52c0-.56-.06-.7-.6-.6l-1.96.36z"/><path fill="#fff" fill-rule="evenodd" d="M143.18 13.18c-3.2 0-5.2 2.04-5.2 5.3s2 5.3 5.2 5.3c3.22 0 5.22-2.04 5.22-5.3s-2-5.3-5.22-5.3m0 9c-2.18 0-3.32-1.32-3.32-3.7s1.14-3.7 3.32-3.7c2.2 0 3.34 1.32 3.34 3.7s-1.14 3.7-3.34 3.7" clip-rule="evenodd"/><path fill="#fff" d="M150.33 13.48h2.1l-.4 5.9c-.12 1.94.82 2.7 2.62 2.7 2.08 0 3.12-1.28 3.14-3.26v-5.34h1.9v7.8c0 .56.06.7.6.6l1.14-.2v1.8h-1.04c-1.42 0-2.36-.88-2.36-2.16l-.4.02c-.44 1.62-1.68 2.44-3.54 2.44-2.58 0-3.76-1.32-3.76-4z"/><path fill="#fff" fill-rule="evenodd" d="M173.07 8.48h-1.9v5.48l.2 1.82h-.4c-.48-1.62-1.6-2.6-3.8-2.6-3.04 0-4.74 2.04-4.74 5.3s1.7 5.3 4.74 5.3c2.1 0 3.32-.98 3.8-2.6h.4c0 1.32 1.02 2.3 2.4 2.3h1.04v-1.8l-1.14.2c-.56.1-.6-.04-.6-.6zm-5.5 6.34c2.2 0 3.5 1.5 3.5 3.66s-1.1 3.64-3.5 3.64c-2.16 0-3.24-1.3-3.24-3.64s1.08-3.66 3.24-3.66" clip-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="164" height="23" fill="none" viewBox="0 0 164 23"><g clip-path="url(#a)"><g fill="#f34e3f" clip-path="url(#b)"><path d="m8.47 6 2.55.69L9.3.19 6.35 1l1.04 3.93c.14.53.56.95 1.08 1.09M4.16 19.22l5.56-5.55 2.03 7.56 2.94-.79-2.93-10.98L.78 6.54 0 9.49l7.55 2.02-5.53 5.54zM14.53 10.2l.68 2.56c.14.53.56.94 1.08 1.08l3.96 1.05.78-2.95zM16.87 2.2l-2.53 2.54c-.28.27-.44.65-.45 1.04l-.04 1.6 1.48.06c-.02 0 .02 0 0 0 .4 0 .79-.16 1.07-.45l2.62-2.61z"/></g></g><path fill="#000" d="M35.86 10.13q-1.35-.57-3.37-.94-1.37-.3-2.07-.51T29.4 8.1a1.3 1.3 0 0 1-.33-.94q0-.7.39-1.1.38-.39 1.05-.54t1.67-.16q1.2 0 2.4.3 1.18.28 2.56.9l.5-2.75q-1.26-.6-2.62-.89-1.37-.29-2.84-.29-1.76 0-3.2.55-1.45.54-2.32 1.57a3.8 3.8 0 0 0-.86 2.52q0 1.3.66 2.2.67.89 1.96 1.43t3.3.9q1.41.29 2.13.52.7.23 1.11.64.4.41.4 1.2 0 .7-.45 1.11t-1.2.56-1.8.14a15 15 0 0 1-6.13-1.4l-.3 2.73q1.2.69 2.89 1.05 1.67.35 3.59.35c1.27 0 2.33-.18 3.34-.54q1.5-.55 2.4-1.6t.91-2.56q0-1.4-.7-2.36a5 5 0 0 0-2.05-1.52M49.71 6.82a7 7 0 0 0-3.43-.8c-1.26 0-2.44.26-3.44.8q-1.52.81-2.37 2.27a6.3 6.3 0 0 0-.86 3.27q0 1.8.85 3.24a6 6 0 0 0 2.37 2.28 7 7 0 0 0 3.45.83c1.3 0 2.4-.28 3.43-.83a6.1 6.1 0 0 0 3.25-5.54q0-1.81-.86-3.26a6 6 0 0 0-2.39-2.25m-.2 7.47q-.47.84-1.31 1.32t-1.9.48c-.69 0-1.37-.16-1.93-.48q-.84-.48-1.33-1.33a4 4 0 0 1-.5-1.96q0-1.12.5-1.93a3.5 3.5 0 0 1 1.33-1.3 4 4 0 0 1 1.91-.46q1.07 0 1.92.47t1.3 1.3.48 1.96-.47 1.93M63.34 13.5q-.07.23-.19.44a4 4 0 0 1-1.32 1.5q-.85.56-1.89.56-1.37 0-2.06-.74-.67-.74-.67-2.12V6.26h-2.96v7.32q0 1.56.62 2.7.63 1.17 1.74 1.78 1.12.63 2.55.63 2.18 0 3.45-1.11.42-.37.74-.8v1.66h2.95V6.26h-2.95zM74.49 6.04q-2.1 0-3.14 1.37a5 5 0 0 0-.44.73V6.26h-2.96v12.18h2.96v-5.01q0-2.04.88-3.28a2.8 2.8 0 0 1 2.39-1.23h.73q.22.02.4.02h.36L76 6.2a8 8 0 0 0-1.51-.15M80.82 9.07a4 4 0 0 1 1.94-.46 5 5 0 0 1 3.25 1.13l1.22-2.2a8 8 0 0 0-2.22-1.16 8 8 0 0 0-2.5-.37q-1.8 0-3.28.8a6 6 0 0 0-2.36 2.23 6.4 6.4 0 0 0-.87 3.32q0 1.86.85 3.3.84 1.43 2.34 2.22a7 7 0 0 0 3.3.78 8 8 0 0 0 2.47-.34 9 9 0 0 0 2.27-1.14l-1.17-2.15a7 7 0 0 1-1.62.8 5 5 0 0 1-1.66.28q-1.08 0-1.95-.44a3.3 3.3 0 0 1-1.37-1.3 4 4 0 0 1-.5-2q0-1.18.5-2.02.5-.83 1.36-1.28M96.8 6.74q-1.36-.7-2.98-.7a7 7 0 0 0-3.42.8 5.7 5.7 0 0 0-2.28 2.26 6.6 6.6 0 0 0-.8 3.26c0 1.21.28 2.26.85 3.23a6 6 0 0 0 2.38 2.27q1.53.82 3.45.82 1.33 0 2.38-.27 1.05-.29 2-.92l-.76-2.42q-.7.51-1.65.78a6 6 0 0 1-1.82.28q-1.2 0-2.07-.43a3 3 0 0 1-1.33-1.28q-.26-.45-.37-1.04h8.95a5.3 5.3 0 0 0-.41-4.73 5.3 5.3 0 0 0-2.12-1.92m-6.1 3.6q.4-.85 1.2-1.3a4 4 0 0 1 1.87-.45q.98 0 1.7.39.7.39 1.04 1.18.19.48.23 1.07h-6.35q.1-.48.3-.89M110.92 7.92a4.6 4.6 0 0 0-1.84-1.43 7 7 0 0 0-2.63-.48q-1.65 0-2.94.78a5.5 5.5 0 0 0-2.05 2.15q-.75 1.38-.75 3.09c0 1.14.25 2.1.75 2.98q.75 1.32 2.07 2.09t2.97.77q1.47 0 2.57-.47a4.3 4.3 0 0 0 1.81-1.42v.58q0 1.64-1.06 2.6a4.2 4.2 0 0 1-2.92.95 8 8 0 0 1-4.45-1.38l-1.16 2.09q1.2.81 2.7 1.25t3.24.43q3.12 0 4.87-1.66 1.77-1.67 1.76-4.64V6.26h-2.93zm-.5 5.98q-.47.81-1.3 1.25t-1.91.43q-1 0-1.81-.46a3.3 3.3 0 0 1-1.28-1.27 3.84 3.84 0 0 1 0-3.7q.46-.82 1.28-1.3a4.05 4.05 0 0 1 3.73-.03q.8.44 1.3 1.25.47.83.47 1.91 0 1.1-.48 1.92M118.95 7.41a5 5 0 0 0-.44.73V6.26h-2.96v12.18h2.96v-5.01q0-2.04.88-3.28a2.8 2.8 0 0 1 2.39-1.23h.73l.4.03h.36l.33-2.76a8 8 0 0 0-1.51-.15q-2.1 0-3.14 1.37M133.69 7.36Q132.26 6 129.34 6q-2.22 0-4.47.74l.36 2.6a9 9 0 0 1 1.8-.55q.98-.2 2-.19 1.56 0 2.35.6.78.6.79 1.77v.1a57 57 0 0 0-3.76-.19q-2.41 0-3.56 1.1a3.7 3.7 0 0 0-1.16 2.79q0 1.12.51 2.02.52.89 1.48 1.38.98.5 2.24.5 1.25 0 2.22-.36a4.4 4.4 0 0 0 2.03-1.47v1.59h2.96v-7.26q0-2.48-1.44-3.83m-2.95 8.38q-.73.42-1.84.42a3.5 3.5 0 0 1-1.8-.4 1.3 1.3 0 0 1-.63-1.14q0-.8.65-1.22.64-.42 2.18-.42.84 0 1.53.03.68.04 1.34.12a3.5 3.5 0 0 1-.35 1.5q-.34.7-1.08 1.11M146.9 6.84a5.7 5.7 0 0 0-2.99-.83q-1.26 0-2.36.44a5 5 0 0 0-1.89 1.25V6.26h-2.96v16h2.96v-5.22q.51.56 1.2.91.66.36 1.42.56t1.61.2q1.67 0 3.03-.79a5.7 5.7 0 0 0 2.14-2.23q.8-1.44.8-3.3c0-1.25-.27-2.31-.8-3.28a6 6 0 0 0-2.16-2.28m-.44 7.44q-.46.84-1.27 1.3-.8.46-1.84.46a4 4 0 0 1-1.93-.44q-.84-.44-1.3-1.29a4 4 0 0 1-.46-1.95q0-1.12.47-1.95t1.3-1.3 1.92-.46q1.03 0 1.84.47t1.27 1.32q.45.86.45 1.94 0 1.07-.45 1.9M162.78 8.41q-.63-1.15-1.74-1.77a5 5 0 0 0-2.56-.63q-2.17 0-3.45 1.11-.41.37-.73.8V2.06h-2.96v16.38h2.96V11.2q.08-.23.19-.44a4 4 0 0 1 1.32-1.5q.84-.57 1.9-.56 1.37 0 2.05.74t.68 2.12v6.88h2.96v-7.32a5.6 5.6 0 0 0-.63-2.7"/><defs><clipPath id="a"><path fill="#000" d="M0 .2h21.03v21.03H0z"/></clipPath><clipPath id="b"><path fill="#000" d="M0 .2h21.03v21.03H0z"/></clipPath></defs></svg>
|
||||
|
After Width: | Height: | Size: 4.4 KiB |
@@ -1,15 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="138" height="42" fill="none" viewBox="0 0 138 42">
|
||||
<path fill="#fff" fill-rule="evenodd" d="M138 4a4 4 0 0 0-4-4H4a4 4 0 0 0-4 4v34a4 4 0 0 0 4 4h130a4 4 0 0 0 4-4z" clip-rule="evenodd"/>
|
||||
<path fill="url(#a)" fill-rule="evenodd" d="M16 27.6a6.6 6.6 0 0 0 5.5-10.4l4.3-4.3c.3-.3.1-1-.4-1a29 29 0 0 1-4.8-1.4h-.1l-.6-.3A11.4 11.4 0 0 0 5.1 24.5c.2.6 1 .9 1.5.6q.7-.6.5-1.4a9.3 9.3 0 0 1 14.6-10l-2 1.8q-1.6-1-3.7-1.1a6.6 6.6 0 0 0-5.4 10.4L6.3 29c-.4.3-.2 1 .3 1a29 29 0 0 1 4.8 1.4h.1l.6.3a11 11 0 0 0 12-2.7 11 11 0 0 0 2.8-11.6c-.2-.6-1-.9-1.5-.6q-.6.6-.5 1.4a9.3 9.3 0 0 1-14.6 10l2-1.8q1.6 1 3.7 1.1m4.4-6.6a4.5 4.5 0 0 1-6.7 4l6.2-6.1q.5.9.5 2.1m-9 0a4.5 4.5 0 0 1 6.8-3.9L12 23.3q-.6-1-.6-2.3" clip-rule="evenodd"/>
|
||||
<path fill="url(#b)" d="M57.7 19.3q3.5.1 3.5 4 0 3.6-3.5 3.8-2.3 0-2.8-1.9h-.3l.2 1.4v3.7h-1.4V19.5h1.4l-.2 1.7h.3q.6-1.9 2.8-1.9m16.6 7.4h.3l2-7.2h1.5l-2.5 8.3q-.4 2-2.4 2.2l-2.2.2v-1.4h2.5q.5.1.5-.4l.5-1.5H73l-2.2-7.4h1.4zm-26-7.4q3.3 0 3.3 3.7H46c0 1.7.5 3 2.5 3q2 0 2-2.1H52q-.4 3.1-3.5 3.2-3.8-.1-3.9-3.9 0-3.7 3.7-3.9m46.1-3q4 0 4.4 4.2h-1.5q-.2-3-3-3-3 0-3 4.2t3.3 4.2q3 0 3.2-3h1.4q-.3 4.3-4.6 4.2-4.7 0-4.7-5.4t4.5-5.4m16 3q3.6.1 3.8 4-.2 3.6-3.9 3.8-3.7-.1-3.8-3.9.1-3.7 3.8-3.9m6.4 4.6q0 2 2 2 2.3-.2 2.3-2.4v-4h1.4v5.8q-.1.6.4.4l.9-.1v1.3h-.8q-1.6 0-1.7-1.6h-.3q-.5 1.8-2.6 1.8-2.9 0-2.8-3v-4.6h1.5zm15.6 1.4q-.1.6.4.4l.9-.1v1.3h-.8q-1.7-.1-1.8-1.7h-.3q-.4 1.9-2.8 2-3.4-.2-3.5-4 .1-3.7 3.5-3.9 2.4 0 2.8 2h.3l-.1-1.4v-4h1.4zm-89-5.7L36 25.4v.3h7.8v1.2h-8v-3l7.5-5.8v-.3H36v-1.2h7.4zm21 .3L64 21h.3q.5-1.8 2.7-1.8 2.8 0 2.7 3v4.6h-1.5l.3-4.4q0-2-2-2-2.3.1-2.3 2.5v4H63V15.7h1.4zM80.5 21h.3q.4-1.7 2.2-1.6h1.3V21l-1.6-.2q-2 0-2 2.3v4h-1.5v-7.5h1.4zm20.9-5.3q2 0 2 2v8h2.2v1.1h-6v-1.2h2.4v-8.4q.1-.6-.5-.4l-1.4.3v-1.4zm8.8 4.7q-2.4 0-2.4 2.7t2.4 2.7q2.4 0 2.5-2.7 0-2.6-2.5-2.7m-52.8 0q-2.6 0-2.6 2.7.1 2.6 2.6 2.7 2.3 0 2.3-2.7 0-2.6-2.3-2.7m70.8 0q-2.4 0-2.4 2.7 0 2.6 2.4 2.7 2.7 0 2.6-2.7-.1-2.6-2.6-2.7m-80 0q-1.8 0-2.1 1.5h4q0-1.4-1.9-1.5"/>
|
||||
<defs>
|
||||
<linearGradient id="a" x1="4.6" x2="27.4" y1="21" y2="21" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#010101"/>
|
||||
<stop offset="1" stop-color="#949494"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="b" x1="35.7" x2="133.7" y1="23.1" y2="23.1" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#010101"/>
|
||||
<stop offset="1" stop-color="#646464"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="175" height="31" fill="none" viewBox="0 0 175 31"><path fill="#010101" fill-rule="evenodd" d="M15.48 24.45a8.96 8.96 0 0 0 7.39-14.05l5.82-5.82c.5-.5.21-1.34-.47-1.47a39 39 0 0 1-6.55-1.83l-.13-.05-.77-.3a15.5 15.5 0 0 0-16.23 3.6A15.4 15.4 0 0 0 .76 20.28c.26.81 1.22 1.12 1.96.7.63-.37.88-1.14.67-1.83a12.63 12.63 0 0 1 19.8-13.68L20.56 8.1A8.96 8.96 0 0 0 8.1 20.57l-5.82 5.82c-.49.5-.21 1.34.47 1.47a39 39 0 0 1 6.55 1.83l.13.05.78.3a15.5 15.5 0 0 0 16.17-3.54l.06-.07a15.4 15.4 0 0 0 3.78-15.74c-.27-.81-1.22-1.12-1.97-.7a1.6 1.6 0 0 0-.67 1.84 12.63 12.63 0 0 1-19.8 13.66l2.63-2.62c1.44 1 3.2 1.58 5.08 1.58m5.95-8.97a6.1 6.1 0 0 1-9.02 5.38l8.29-8.29q.72 1.32.73 2.91m-12.22 0a6.11 6.11 0 0 1 9.23-5.25l-8.38 8.37a6 6 0 0 1-.85-3.12" clip-rule="evenodd"/><path fill="#010101" d="M80.9 23.48H79v-15h1.9v5.48l-.25 1.62h.4c.44-1.62 1.54-2.4 3.56-2.4 2.58 0 3.76 1.32 3.76 4v6.3h-2.1l.4-5.9c.12-1.98-.82-2.72-2.62-2.72-2.12 0-3.14 1.28-3.14 3.38zM42.5 11.12V9.48h10.12v4.14l-10.18 7.8v.4h10.54v1.66H42.14v-4.16l10.18-7.8v-.4z"/><path fill="#010101" fill-rule="evenodd" d="M62.29 19.4h1.88c-.3 2.74-1.86 4.38-4.76 4.38-3.34 0-5.16-2.04-5.16-5.3s1.76-5.3 4.96-5.3c3 0 4.52 1.8 4.52 4.98h-7.6c0 2.36.6 4.02 3.28 4.02 1.9 0 2.7-.96 2.88-2.78m-5.98-2.54h5.46c-.02-1.24-.84-2.1-2.52-2.1s-2.6.7-2.94 2.1M66.1 28.08H68V23l-.2-1.82h.4c.5 1.62 1.61 2.6 3.8 2.6 3.05 0 4.73-2.04 4.73-5.3s-1.68-5.3-4.74-5.3c-2.1 0-3.3.98-3.8 2.6h-.4l.2-2.3h-1.9zm5.51-5.94c-2.22 0-3.52-1.5-3.52-3.68s1.12-3.68 3.52-3.68c2.14 0 3.22 1.32 3.22 3.68s-1.08 3.68-3.22 3.68" clip-rule="evenodd"/><path fill="#010101" d="M91.62 13.48h-1.9l2.92 10h2.08l-.66 2.08c-.14.54-.2.58-.76.56l-3.38-.04v1.9l2.98-.26c1.92-.16 2.82-1.36 3.3-3.08l3.3-11.16h-1.9l-2.8 9.7h-.4zM103.13 23.48h-1.9v-10h1.9l-.2 2.1h.4c.42-1.62 1.32-2.1 3.04-2.1h1.78v1.86l-2.18-.2c-1.88-.16-2.84.98-2.84 3.1zM121.92 22.14c-3 0-4.5-1.86-4.5-5.66s1.42-5.66 4.2-5.66c2.4 0 3.72 1.22 4 4.04h1.96c-.32-3.88-2.34-5.68-5.94-5.68-4.06 0-6.16 2.44-6.16 7.3s2.18 7.3 6.46 7.3c3.78 0 5.92-1.8 6.24-5.7h-1.96c-.28 2.84-1.72 4.06-4.3 4.06M129.47 8.48h1.74q2.66-.02 2.64 2.64v10.74h3.14v1.62h-8.12v-1.62h3.16V10.52c0-.56-.06-.7-.6-.6l-1.96.36z"/><path fill="#010101" fill-rule="evenodd" d="M143.18 13.18c-3.2 0-5.2 2.04-5.2 5.3s2 5.3 5.2 5.3c3.22 0 5.22-2.04 5.22-5.3s-2-5.3-5.22-5.3m0 9c-2.18 0-3.32-1.32-3.32-3.7s1.14-3.7 3.32-3.7c2.2 0 3.34 1.32 3.34 3.7s-1.14 3.7-3.34 3.7" clip-rule="evenodd"/><path fill="#010101" d="M150.33 13.48h2.1l-.4 5.9c-.12 1.94.82 2.7 2.62 2.7 2.08 0 3.12-1.28 3.14-3.26v-5.34h1.9v7.8c0 .56.06.7.6.6l1.14-.2v1.8h-1.04c-1.42 0-2.36-.88-2.36-2.16l-.4.02c-.44 1.62-1.68 2.44-3.54 2.44-2.58 0-3.76-1.32-3.76-4z"/><path fill="#010101" fill-rule="evenodd" d="M173.07 8.48h-1.9v5.48l.2 1.82h-.4c-.48-1.62-1.6-2.6-3.8-2.6-3.04 0-4.74 2.04-4.74 5.3s1.7 5.3 4.74 5.3c2.1 0 3.32-.98 3.8-2.6h.4c0 1.32 1.02 2.3 2.4 2.3h1.04v-1.8l-1.14.2c-.56.1-.6-.04-.6-.6zm-5.5 6.34c2.2 0 3.5 1.5 3.5 3.66s-1.1 3.64-3.5 3.64c-2.16 0-3.24-1.3-3.24-3.64s1.08-3.66 3.24-3.66" clip-rule="evenodd"/></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 3.0 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user