From 16528dbe495e2a31f3bd811859bc8bf77cd5ab07 Mon Sep 17 00:00:00 2001 From: ak Date: Mon, 2 Feb 2026 21:22:35 +0300 Subject: [PATCH 1/2] feat: add privacy policy page --- apps/website/src/app/privacy/page.tsx | 67 +++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 apps/website/src/app/privacy/page.tsx diff --git a/apps/website/src/app/privacy/page.tsx b/apps/website/src/app/privacy/page.tsx new file mode 100644 index 000000000..1c5ba96b8 --- /dev/null +++ b/apps/website/src/app/privacy/page.tsx @@ -0,0 +1,67 @@ +import { Metadata } from 'next' + +export const metadata: Metadata = { + title: 'Privacy Policy | Web3 Icons', + description: 'Privacy policy for Web3 Icons and the Web3 Icons Chrome extension.', +} + +export default function PrivacyPage() { + return ( +
+

Privacy Policy

+

Last updated: February 2, 2026

+ +

Overview

+

+ Web3 Icons is an open-source icon library for cryptocurrency tokens, + networks, wallets, and exchanges. This policy covers the Web3 Icons + website (web3icons.io) and the Web3 Icons Chrome extension. +

+ +

Data We Collect

+

+ We do not collect any personal data. The Chrome + extension runs entirely on your device. It does not track you, send + analytics, or communicate with any server. +

+

+ The website uses Vercel Analytics for anonymous, aggregated page view + statistics. No cookies are used for tracking. No personal information is + collected or stored. +

+ +

Permissions

+

+ The Chrome extension requests the clipboardWrite permission + solely to copy SVG icon code to your clipboard when you click the copy + button. No clipboard data is read or transmitted. +

+ +

Third Parties

+

+ We do not share any data with third parties because we do not collect + any. +

+ +

Open Source

+

+ The full source code is available at{' '} + + github.com/0xa3k5/web3icons + + . You can verify everything described in this policy by reviewing the + code. +

+ +

Contact

+

+ Questions? Reach out at{' '} + hey@akml.io. +

+
+ ) +} From b22fa35700e68b7f2835947389dd8e8e66f65065 Mon Sep 17 00:00:00 2001 From: AK <58132405+0xa3k5@users.noreply.github.com> Date: Mon, 2 Feb 2026 21:31:06 +0300 Subject: [PATCH 2/2] feat: Chrome extension (#177) --- .github/workflows/chrome-extension.yml | 87 +++++++++ apps/chrome-extension/.gitignore | 2 + apps/chrome-extension/icons/icon-128.png | Bin 0 -> 2308 bytes apps/chrome-extension/icons/icon-16.png | Bin 0 -> 675 bytes apps/chrome-extension/icons/icon-32.png | Bin 0 -> 1150 bytes apps/chrome-extension/icons/icon-48.png | Bin 0 -> 1687 bytes apps/chrome-extension/manifest.json | 22 +++ apps/chrome-extension/package.json | 21 +++ apps/chrome-extension/popup.html | 46 +++++ apps/chrome-extension/scripts/build.ts | 31 ++++ apps/chrome-extension/src/css/input.css | 35 ++++ apps/chrome-extension/src/popup.ts | 207 ++++++++++++++++++++++ apps/chrome-extension/tsconfig.json | 14 ++ apps/website/src/app/blog/[slug]/page.tsx | 9 +- apps/website/src/components/footer.tsx | 9 + bun.lock | 43 +++-- 16 files changed, 511 insertions(+), 15 deletions(-) create mode 100644 .github/workflows/chrome-extension.yml create mode 100644 apps/chrome-extension/.gitignore create mode 100644 apps/chrome-extension/icons/icon-128.png create mode 100644 apps/chrome-extension/icons/icon-16.png create mode 100644 apps/chrome-extension/icons/icon-32.png create mode 100644 apps/chrome-extension/icons/icon-48.png create mode 100644 apps/chrome-extension/manifest.json create mode 100644 apps/chrome-extension/package.json create mode 100644 apps/chrome-extension/popup.html create mode 100644 apps/chrome-extension/scripts/build.ts create mode 100644 apps/chrome-extension/src/css/input.css create mode 100644 apps/chrome-extension/src/popup.ts create mode 100644 apps/chrome-extension/tsconfig.json diff --git a/.github/workflows/chrome-extension.yml b/.github/workflows/chrome-extension.yml new file mode 100644 index 000000000..88e775734 --- /dev/null +++ b/.github/workflows/chrome-extension.yml @@ -0,0 +1,87 @@ +name: Chrome Extension + +on: + # Build on PRs that touch extension code + pull_request: + paths: + - "apps/chrome-extension/**" + - "packages/core/**" + - "packages/common/**" + + # Publish when a GitHub release is created (after changeset release) + release: + types: [published] + + # Manual trigger for testing + workflow_dispatch: + inputs: + publish: + description: "Publish to Chrome Web Store" + required: false + default: "false" + type: choice + options: + - "false" + - "true" + +concurrency: + group: chrome-extension-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + name: Build Extension + runs-on: ubuntu-latest + steps: + - name: Check out + uses: actions/checkout@v4 + + - name: Install Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version-file: package.json + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Build packages (core + common) + run: bun run build:packages + + - name: Build Chrome extension + working-directory: apps/chrome-extension + run: bun run build + + - name: Package extension + working-directory: apps/chrome-extension + run: cd dist && zip -r ../web3icons-chrome.zip . + + - name: Upload build artifact + uses: actions/upload-artifact@v4 + with: + name: chrome-extension + path: apps/chrome-extension/web3icons-chrome.zip + retention-days: 30 + + publish: + name: Publish to Chrome Web Store + needs: build + runs-on: ubuntu-latest + # Only publish on release events or manual trigger with publish=true + if: > + github.event_name == 'release' || + (github.event_name == 'workflow_dispatch' && github.event.inputs.publish == 'true') + steps: + - name: Download build artifact + uses: actions/download-artifact@v4 + with: + name: chrome-extension + + - name: Upload to Chrome Web Store + uses: mnao305/chrome-extension-upload@v5.0.0 + with: + file-path: web3icons-chrome.zip + extension-id: ${{ secrets.CHROME_EXTENSION_ID }} + client-id: ${{ secrets.CHROME_CLIENT_ID }} + client-secret: ${{ secrets.CHROME_CLIENT_SECRET }} + refresh-token: ${{ secrets.CHROME_REFRESH_TOKEN }} + publish: true diff --git a/apps/chrome-extension/.gitignore b/apps/chrome-extension/.gitignore new file mode 100644 index 000000000..1eae0cf67 --- /dev/null +++ b/apps/chrome-extension/.gitignore @@ -0,0 +1,2 @@ +dist/ +node_modules/ diff --git a/apps/chrome-extension/icons/icon-128.png b/apps/chrome-extension/icons/icon-128.png new file mode 100644 index 0000000000000000000000000000000000000000..a272c0e9cf66cc8026563712a201aa1fc9e0d150 GIT binary patch literal 2308 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 KT-G@yGywo0gyw+& literal 0 HcmV?d00001 diff --git a/apps/chrome-extension/icons/icon-32.png b/apps/chrome-extension/icons/icon-32.png new file mode 100644 index 0000000000000000000000000000000000000000..67236813780843a24d13b8b5fcacd6487cb47d5e GIT binary patch literal 1150 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzmUKs7M+SzC{oH>NSwWJ?9znhg z3{`3j3=J&|48MRv4KElNN(~qoUL`OvSj}Ky5HFasE6@fgQIQ(qnda-upao=eFt9L6 zF@Q{91Y$czX*k=BQGg$2wA zHb~oi&afjuinG8YvY3H^TNs2H8D`CqU|?V_@N{tuiQs%2=Iap>DDv;#`8|@w%hYra zZHm9V$YVmrMYlt)k{1`bUePMLsCC_S_XaUfmd4o}T24$8&2+P-cOS@g>GtsyKBN<* zy=KnGzhCE?8(V&U7Asr)@ECXbv%SUVZJ%2|pZ9*x>ZO-6?-$Cnw6A;p!A4)FZCPgH zneTrmnNE$F60*(ba*O7?E$tHMsYmRsrs+!Xq~ zEI_v_ZR2BTFd)c!e)l+@icRW34i%TL>UV*D&~DwlI4{-!Nzdz>aL9S zA1}=K@NvVzW1E*%%B=jxJmH;D!;2uB?z3ywFIHo_b3ga-#W?B5k<84S)pou5`B%XG zXQk5HM^FFp>?*fd(VKaq;C@os|FuhlR-Wh=G?6V{*M4nE`@C4QS*vY>_$@5wINYuh z58rm{qUeb^eP>?oFic!jf2+Z}hf{f0;3vaNt>+)UtgyU!)rwvJTk6F##?vp_rzUiX z-q_iC@wrRmo+s^}mbJ9UT|0i+?Op2GluzFd)jf&zR-3FUxj^sIvi{?|e7DSn>g|r& z$MUXyuXAV3_QGA$;zAg@E*eO0K2lYA=g{PQt2F^fStT{D#lMNnV^pipI(u}BQ#Q-| zq`y~=&bW9X=;753IQkO<$|^FV!O+GH%Qrdw)dnou6#b@qHX4*D+Mz7|1U_D&%f(({LU5gvnDG9 z0_;vR;iF_G}>n9qj3>C=xTz4#$;cwS5Q;ZVK2`ezTtbS|IF za68@_$er${v*+Jh#e=mbamO|!-JYnUp}<>za7o9eHGjm)teg9e8%eR<6y3gA_+GNx zyAM0ma(S6{G77T=cqJ~5?mU*Zw5^F9rpArBXB61~%P z_>}kF&8OL%@ARZ^ZHVKvd|q(FTk_O;{r0P=YD@Lkl&pz8Ii)sX>B_Yw|D_s2&ofjz snh1xUbzT=_bcokoRXld8?#I94TWWS+JN9V81yIKHboFyt=akR{07kyu9smFU literal 0 HcmV?d00001 diff --git a/apps/chrome-extension/icons/icon-48.png b/apps/chrome-extension/icons/icon-48.png new file mode 100644 index 0000000000000000000000000000000000000000..afa6e55f81a9c074c2579820ea312751c40f1ef5 GIT binary patch literal 1687 zcmYLKdpy)>AN`pzYJS8BW0LDCmvO(WOU#JGjEvlJjlm)&HYH+QM}&lR8Kg06hDeJU z*D{0^VKL1}v9?X#UP2$U%M@bm*tgHV&*yW#=X}p|&hzh+>+0fo1XKV4062oj*}Dtb z@BoL!gwq?coh>BMOSS}C0ALo1?*_w!yXtp1cLD&AH2@$z699IER{Amk#0iTlK>%>7 z0s!P=Xiwa%g%=57KKO6~0YD4&VL((w0XTR;LPRWZMoaWN=Wb+oNp`uh|3&61gNB;005SYx3~3- zfPM5OL@0)#Qp4lJ&F?+w3E-8r=My>{cp4~7!(n78(sM5FkhFpFu2lj;suvBfqxfYx zC85dEnW~;7x6AMhu*^#q?e!DltXoO_uvvZQ=wB0C*1W1GzATfHP?E&jc3R5OzW{r{K*t`~D_HtNl@{q|P^37lcQ( zFUn=Uy{s29z+Aj1NZU;bkZXvU*Zx*HF~sQHBd}EakwJ?nvHbdrs(82mtdnnGRFSaI zNO(f**ja8jNnqs{kj#3VBZGk{F6(|*^)cjDN&Gxn*N+0pBAuiT6ddhat{VQ$+#*Kn zegBTJ8F$CGFDW(pShYa*kF`p$LFKxJzE3NJ#n2@$frr+A4;*SPFrJi`p!G$)dvRQ8 z?YTEVbWOd%waPZ5#xO36eRjiFQJ0Y7^=XL}Q(bGz-D33GvxPUxW6}z^e4b6^rd?$E z8^f>_zaLtMX6a2q-972AP+m<@3Y6=o$r}qc;^yV!S%KPdr)xA!32L%aN#COvMmtvs z1ufZ{GOvxew*xMNsCn1jS37S;EmJw4)p;6XBT*82k%=$S7xJ_HQe$cC&N`jCqS4TK zE$=Z9+SA{mw;HrRe3b0XVR$+iP7u4&ss|slay|U(G2;$nGY`uP2$iUdKsChCQ0PA~p6&G&iuJ!onE_I#ZHN z^rKeZ-P8Qpui1rtX?pmKrM%KFu>AnUqVGDxB9g{n(zi#42R6`6VqF<2H8#ar8cfs& z!m zV}>4{##~M7Yx%u!!}6BtZ4cTukF}c#KZT|l@;_uQ>?GK3CeyUq%br43;)9jW&w%J! z^18p?oicu=%rxUZ_h?mEHIx98#h!x`q;%^9&w4&k>OKKi!WI^P(3iX_pjbrWKcxX? W3ly_jz*zXB00921i+!_QP{!XVE!Q~! literal 0 HcmV?d00001 diff --git a/apps/chrome-extension/manifest.json b/apps/chrome-extension/manifest.json new file mode 100644 index 000000000..78245a8ac --- /dev/null +++ b/apps/chrome-extension/manifest.json @@ -0,0 +1,22 @@ +{ + "manifest_version": 3, + "name": "Web3 Icons", + "version": "1.0.0", + "description": "Browse and copy 2,500+ crypto icons. Tokens, networks, wallets, and exchanges in branded, mono, and background variants.", + "permissions": ["clipboardWrite"], + "action": { + "default_popup": "popup.html", + "default_icon": { + "16": "icons/icon-16.png", + "32": "icons/icon-32.png", + "48": "icons/icon-48.png", + "128": "icons/icon-128.png" + } + }, + "icons": { + "16": "icons/icon-16.png", + "32": "icons/icon-32.png", + "48": "icons/icon-48.png", + "128": "icons/icon-128.png" + } +} diff --git a/apps/chrome-extension/package.json b/apps/chrome-extension/package.json new file mode 100644 index 000000000..38e84107c --- /dev/null +++ b/apps/chrome-extension/package.json @@ -0,0 +1,21 @@ +{ + "name": "@web3icons/chrome-extension", + "version": "1.0.0", + "private": true, + "description": "Browse and copy 2,500+ crypto icons from any tab. Tokens, networks, wallets, and exchanges in branded, mono, and background variants.", + "author": "AK ", + "scripts": { + "dev": "bun run build:css --watch & bun run build:js --watch", + "build": "bun run build:css && bun run build:js", + "build:css": "npx @tailwindcss/cli --input ./src/css/input.css --output ./dist/popup.css --minify", + "build:js": "bun run scripts/build.ts" + }, + "dependencies": { + "@web3icons/core": "4.0.50", + "@web3icons/common": "0.11.45" + }, + "devDependencies": { + "tailwindcss": "^4.1.11", + "typescript": ">=5" + } +} diff --git a/apps/chrome-extension/popup.html b/apps/chrome-extension/popup.html new file mode 100644 index 000000000..ca37aa1fa --- /dev/null +++ b/apps/chrome-extension/popup.html @@ -0,0 +1,46 @@ + + + + + + + + +
+ +
+
+
+ + +
+ +
+ + +
+ + +
+
+
+ + +
+ Copied! +
+ + + +
+ + + diff --git a/apps/chrome-extension/scripts/build.ts b/apps/chrome-extension/scripts/build.ts new file mode 100644 index 000000000..cd64ab257 --- /dev/null +++ b/apps/chrome-extension/scripts/build.ts @@ -0,0 +1,31 @@ +import { build } from 'bun' +import { cpSync, mkdirSync, existsSync } from 'fs' +import { join } from 'path' + +const ROOT = join(import.meta.dir, '..') +const DIST = join(ROOT, 'dist') + +// Ensure dist exists +mkdirSync(DIST, { recursive: true }) + +// Bundle popup.ts +await build({ + entrypoints: [join(ROOT, 'src/popup.ts')], + outdir: DIST, + naming: 'popup.js', + minify: true, + target: 'browser', + format: 'esm', +}) + +// Copy static files +cpSync(join(ROOT, 'popup.html'), join(DIST, 'popup.html')) +cpSync(join(ROOT, 'manifest.json'), join(DIST, 'manifest.json')) + +// Copy icons if they exist +const iconsDir = join(ROOT, 'icons') +if (existsSync(iconsDir)) { + cpSync(iconsDir, join(DIST, 'icons'), { recursive: true }) +} + +console.log('Build complete: dist/') diff --git a/apps/chrome-extension/src/css/input.css b/apps/chrome-extension/src/css/input.css new file mode 100644 index 000000000..6cba821b1 --- /dev/null +++ b/apps/chrome-extension/src/css/input.css @@ -0,0 +1,35 @@ +@import "tailwindcss"; + +@theme { + --color-gray-lightest: #222222; + --color-gray-light: #131313; + --color-gray: #111111; + --color-gray-dark: #0F0F0F; + --color-gray-darker: #0B0B0B; + --color-gray-darkest: #080808; + --color-primary: #FF3D00; +} + +body { + background-color: var(--color-gray-darkest); + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; + font-size: 0.75rem; +} + +/* Icon grid scrollbar */ +#icon-grid::-webkit-scrollbar { + width: 4px; +} +#icon-grid::-webkit-scrollbar-track { + background: transparent; +} +#icon-grid::-webkit-scrollbar-thumb { + background: #222; + border-radius: 2px; +} + +/* SVG sizing inside icon cards */ +.icon-card svg { + width: 32px; + height: 32px; +} diff --git a/apps/chrome-extension/src/popup.ts b/apps/chrome-extension/src/popup.ts new file mode 100644 index 000000000..508e1c16a --- /dev/null +++ b/apps/chrome-extension/src/popup.ts @@ -0,0 +1,207 @@ +import { svgs } from '@web3icons/core' +import type { TType, TVariant } from '@web3icons/common' + +type IconEntry = { name: string; svg: string } + +let activeType: TType = 'token' +let activeVariant: TVariant = 'branded' +let searchQuery = '' +let toastTimeout: ReturnType | null = null + +const TYPES: TType[] = ['token', 'network', 'wallet', 'exchange'] +const VARIANTS: TVariant[] = ['branded', 'mono', 'background'] + +// --- Icon data (cached per type+variant) --- + +const iconCache = new Map() + +function getIcons(type: TType, variant: TVariant, query: string): IconEntry[] { + const cacheKey = `${type}-${variant}` + let entries = iconCache.get(cacheKey) + + if (!entries) { + const typePlural = `${type}s` as keyof typeof svgs + const variantGroup = variant as keyof (typeof svgs)[typeof typePlural] + const modules = svgs[typePlural][variantGroup] as Record< + string, + { default: string } + > + + entries = Object.entries(modules).map(([exportName, mod]) => { + const name = exportName + .replace( + /^(Network|Token|Exchange|Wallet)(Mono|Branded|Background)/, + '', + ) + .toLowerCase() + return { name, svg: mod.default } + }) + + iconCache.set(cacheKey, entries) + } + + if (!query) return entries + + const q = query.toLowerCase() + return entries.filter((e) => e.name.includes(q)) +} + +// --- Toast --- + +function showToast(message: string) { + const toast = document.getElementById('toast')! + toast.textContent = message + toast.style.opacity = '1' + if (toastTimeout) clearTimeout(toastTimeout) + toastTimeout = setTimeout(() => { + toast.style.opacity = '0' + }, 1200) +} + +// --- Copy to clipboard --- + +async function copyToClipboard(svg: string, name: string) { + try { + await navigator.clipboard.writeText(svg) + showToast(`Copied ${name}`) + } catch { + // Fallback + const textarea = document.createElement('textarea') + textarea.value = svg + document.body.appendChild(textarea) + textarea.select() + document.execCommand('copy') + document.body.removeChild(textarea) + showToast(`Copied ${name}`) + } +} + +// --- Render tabs --- + +function renderTabs() { + const container = document.getElementById('tabs')! + const indicator = document.getElementById('tab-indicator')! + + // Clear existing tabs (keep indicator) + Array.from(container.children).forEach((child) => { + if (child.id !== 'tab-indicator') container.removeChild(child) + }) + + TYPES.forEach((type) => { + const btn = document.createElement('button') + btn.textContent = type.charAt(0).toUpperCase() + type.slice(1) + btn.className = `z-[1] flex items-center justify-center rounded-full px-4 py-2 text-xs text-white duration-150 ${ + activeType === type ? 'opacity-100' : 'opacity-40' + }` + btn.addEventListener('click', () => { + activeType = type + renderTabs() + renderIcons() + }) + container.insertBefore(btn, indicator) + }) + + // Update indicator + requestAnimationFrame(() => { + const activeIndex = TYPES.indexOf(activeType) + const activeBtn = container.children[activeIndex] as HTMLElement + if (activeBtn) { + indicator.style.width = `${activeBtn.offsetWidth}px` + indicator.style.transform = `translateX(${activeBtn.offsetLeft}px)` + } + }) +} + +// --- Render variant toggle --- + +function renderVariants() { + const container = document.getElementById('variants')! + container.innerHTML = '' + + VARIANTS.forEach((variant) => { + const label = document.createElement('button') + label.textContent = variant + label.className = `z-[1] flex w-full items-center justify-center rounded-full px-4 py-1 text-sm ${ + activeVariant === variant ? 'bg-gray-lightest' : '' + }` + label.addEventListener('click', () => { + activeVariant = variant + renderVariants() + renderIcons() + }) + container.appendChild(label) + }) +} + +// --- Render icon grid --- + +function renderIcons() { + const container = document.getElementById('icons')! + container.innerHTML = '' + + const icons = getIcons(activeType, activeVariant, searchQuery) + + if (icons.length === 0) { + container.innerHTML = + '
No icons found
' + return + } + + // Render in batches for performance + const BATCH_SIZE = 100 + let rendered = 0 + + function renderBatch() { + const fragment = document.createDocumentFragment() + const end = Math.min(rendered + BATCH_SIZE, icons.length) + + for (let i = rendered; i < end; i++) { + const icon = icons[i]! + const card = document.createElement('button') + card.className = + 'icon-card flex flex-col items-center justify-center gap-1 px-2 py-4 duration-150 hover:bg-gray-light cursor-pointer' + card.innerHTML = ` +
${icon.svg}
+ ${icon.name} + ` + card.title = `Copy ${icon.name} SVG` + card.addEventListener('click', () => copyToClipboard(icon.svg, icon.name)) + fragment.appendChild(card) + } + + container.appendChild(fragment) + rendered = end + + if (rendered < icons.length) { + requestAnimationFrame(renderBatch) + } + } + + renderBatch() +} + +// --- Search --- + +function setupSearch() { + const input = document.getElementById('search') as HTMLInputElement + let debounceTimer: ReturnType | null = null + + input.addEventListener('input', () => { + if (debounceTimer) clearTimeout(debounceTimer) + debounceTimer = setTimeout(() => { + searchQuery = input.value.trim() + renderIcons() + }, 150) + }) +} + +// --- Init --- + +function init() { + renderTabs() + renderVariants() + renderIcons() + setupSearch() +} + +document.addEventListener('DOMContentLoaded', init) diff --git a/apps/chrome-extension/tsconfig.json b/apps/chrome-extension/tsconfig.json new file mode 100644 index 000000000..0c66ee9cf --- /dev/null +++ b/apps/chrome-extension/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src/**/*.ts"], + "exclude": ["node_modules", "dist"] +} diff --git a/apps/website/src/app/blog/[slug]/page.tsx b/apps/website/src/app/blog/[slug]/page.tsx index 62c28c8fb..77ee3d764 100644 --- a/apps/website/src/app/blog/[slug]/page.tsx +++ b/apps/website/src/app/blog/[slug]/page.tsx @@ -138,7 +138,14 @@ export default async function BlogPost({ {formatDate(frontmatter.date)} ยท - {frontmatter.author} + + {frontmatter.author} +
{frontmatter.tags?.map((tag: string) => ( diff --git a/apps/website/src/components/footer.tsx b/apps/website/src/components/footer.tsx index 43b93f337..2cb8f9bff 100644 --- a/apps/website/src/components/footer.tsx +++ b/apps/website/src/components/footer.tsx @@ -46,6 +46,15 @@ export const Footer = () => { )}
))} + / + + by ak + diff --git a/bun.lock b/bun.lock index 52c3efad5..a5a6ddcaf 100644 --- a/bun.lock +++ b/bun.lock @@ -1,6 +1,5 @@ { "lockfileVersion": 1, - "configVersion": 0, "workspaces": { "": { "name": "web3icons", @@ -33,6 +32,18 @@ "website": "^0.1.1", }, }, + "apps/chrome-extension": { + "name": "@web3icons/chrome-extension", + "version": "1.0.0", + "dependencies": { + "@web3icons/common": "0.11.45", + "@web3icons/core": "4.0.50", + }, + "devDependencies": { + "tailwindcss": "^4.1.11", + "typescript": ">=5", + }, + }, "apps/figma-plugin": { "name": "@web3icons/figma-plugin", "dependencies": { @@ -95,7 +106,7 @@ }, "packages/common": { "name": "@web3icons/common", - "version": "0.11.35", + "version": "0.11.45", "devDependencies": { "bun-types": "latest", }, @@ -105,9 +116,9 @@ }, "packages/core": { "name": "@web3icons/core", - "version": "4.0.40", + "version": "4.0.50", "dependencies": { - "@web3icons/common": "0.11.35", + "@web3icons/common": "0.11.45", }, "devDependencies": { "bun-types": "latest", @@ -137,7 +148,7 @@ }, "packages/react": { "name": "@web3icons/react", - "version": "4.1.6", + "version": "4.1.16", "dependencies": { "@web3icons/common": "0.11.35", }, @@ -834,6 +845,8 @@ "@vercel/style-guide": ["@vercel/style-guide@5.2.0", "", { "dependencies": { "@babel/core": "^7.22.11", "@babel/eslint-parser": "^7.22.11", "@rushstack/eslint-patch": "^1.3.3", "@typescript-eslint/eslint-plugin": "^6.5.0", "@typescript-eslint/parser": "^6.5.0", "eslint-config-prettier": "^9.0.0", "eslint-import-resolver-alias": "^1.1.2", "eslint-import-resolver-typescript": "^3.6.0", "eslint-plugin-eslint-comments": "^3.2.0", "eslint-plugin-import": "^2.28.1", "eslint-plugin-jest": "^27.2.3", "eslint-plugin-jsx-a11y": "^6.7.1", "eslint-plugin-playwright": "^0.16.0", "eslint-plugin-react": "^7.33.2", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-testing-library": "^6.0.1", "eslint-plugin-tsdoc": "^0.2.17", "eslint-plugin-unicorn": "^48.0.1", "prettier-plugin-packagejson": "^2.4.5" }, "peerDependencies": { "@next/eslint-plugin-next": ">=12.3.0 <15", "eslint": ">=8.48.0 <9", "prettier": ">=3.0.0 <4", "typescript": ">=4.8.0 <6" }, "optionalPeers": ["@next/eslint-plugin-next", "eslint", "prettier", "typescript"] }, "sha512-fNSKEaZvSkiBoF6XEefs8CcgAV9K9e+MbcsDZjUsktHycKdA0jvjAzQi1W/FzLS+Nr5zZ6oejCwq/97dHUKe0g=="], + "@web3icons/chrome-extension": ["@web3icons/chrome-extension@workspace:apps/chrome-extension"], + "@web3icons/common": ["@web3icons/common@workspace:packages/common"], "@web3icons/core": ["@web3icons/core@workspace:packages/core"], @@ -936,7 +949,7 @@ "builtin-modules": ["builtin-modules@3.3.0", "", {}, "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw=="], - "bun-types": ["bun-types@1.3.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-5ua817+BZPZOlNaRgGBpZJOSAQ9RQ17pkwPD0yR7CfJg+r8DgIILByFifDTa+IPDDxzf5VNhtNlcKqFzDgJvlQ=="], + "bun-types": ["bun-types@1.3.8", "", { "dependencies": { "@types/node": "*" } }, "sha512-fL99nxdOWvV4LqjmC+8Q9kW3M4QTtTR1eePs94v5ctGqU8OeceWrSUaRw3JYb7tU3FkMIAjkueehrHPPPGKi5Q=="], "busboy": ["busboy@1.6.0", "", { "dependencies": { "streamsearch": "^1.1.0" } }, "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA=="], @@ -2152,7 +2165,7 @@ "synckit": ["synckit@0.11.11", "", { "dependencies": { "@pkgr/core": "^0.2.9" } }, "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw=="], - "tailwindcss": ["tailwindcss@3.4.18", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", "chokidar": "^3.6.0", "didyoumean": "^1.2.2", "dlv": "^1.1.3", "fast-glob": "^3.3.2", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", "jiti": "^1.21.7", "lilconfig": "^3.1.3", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", "picocolors": "^1.1.1", "postcss": "^8.4.47", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", "postcss-nested": "^6.2.0", "postcss-selector-parser": "^6.1.2", "resolve": "^1.22.8", "sucrase": "^3.35.0" }, "bin": { "tailwind": "lib/cli.js", "tailwindcss": "lib/cli.js" } }, "sha512-6A2rnmW5xZMdw11LYjhcI5846rt9pbLSabY5XPxo+XWdxwZaFEn47Go4NzFiHu9sNNmr/kXivP1vStfvMaK1GQ=="], + "tailwindcss": ["tailwindcss@4.1.17", "", {}, "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q=="], "tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="], @@ -2368,8 +2381,6 @@ "@tailwindcss/node/jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], - "@tailwindcss/node/tailwindcss": ["tailwindcss@4.1.17", "", {}, "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q=="], - "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.7.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.7.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA=="], @@ -2384,8 +2395,6 @@ "@tailwindcss/postcss/postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], - "@tailwindcss/postcss/tailwindcss": ["tailwindcss@4.1.17", "", {}, "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q=="], - "@tailwindcss/typography/postcss-selector-parser": ["postcss-selector-parser@6.0.10", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w=="], "@typescript-eslint/typescript-estree/globby": ["globby@11.1.0", "", { "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", "fast-glob": "^3.2.9", "ignore": "^5.2.0", "merge2": "^1.4.1", "slash": "^3.0.0" } }, "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g=="], @@ -2398,7 +2407,9 @@ "@web3icons/figma-plugin/prettier-plugin-tailwindcss": ["prettier-plugin-tailwindcss@0.7.1", "", { "peerDependencies": { "@ianvs/prettier-plugin-sort-imports": "*", "@prettier/plugin-hermes": "*", "@prettier/plugin-oxc": "*", "@prettier/plugin-pug": "*", "@shopify/prettier-plugin-liquid": "*", "@trivago/prettier-plugin-sort-imports": "*", "@zackad/prettier-plugin-twig": "*", "prettier": "^3.0", "prettier-plugin-astro": "*", "prettier-plugin-css-order": "*", "prettier-plugin-jsdoc": "*", "prettier-plugin-marko": "*", "prettier-plugin-multiline-arrays": "*", "prettier-plugin-organize-attributes": "*", "prettier-plugin-organize-imports": "*", "prettier-plugin-sort-imports": "*", "prettier-plugin-svelte": "*" }, "optionalPeers": ["@ianvs/prettier-plugin-sort-imports", "@prettier/plugin-hermes", "@prettier/plugin-oxc", "@prettier/plugin-pug", "@shopify/prettier-plugin-liquid", "@trivago/prettier-plugin-sort-imports", "@zackad/prettier-plugin-twig", "prettier-plugin-astro", "prettier-plugin-css-order", "prettier-plugin-jsdoc", "prettier-plugin-marko", "prettier-plugin-multiline-arrays", "prettier-plugin-organize-attributes", "prettier-plugin-organize-imports", "prettier-plugin-sort-imports", "prettier-plugin-svelte"] }, "sha512-Bzv1LZcuiR1Sk02iJTS1QzlFNp/o5l2p3xkopwOrbPmtMeh3fK9rVW5M3neBQzHq+kGKj/4LGQMTNcTH4NGPtQ=="], - "@web3icons/website/tailwindcss": ["tailwindcss@4.1.17", "", {}, "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q=="], + "@web3icons/figma-plugin/tailwindcss": ["tailwindcss@3.4.18", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", "chokidar": "^3.6.0", "didyoumean": "^1.2.2", "dlv": "^1.1.3", "fast-glob": "^3.3.2", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", "jiti": "^1.21.7", "lilconfig": "^3.1.3", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", "picocolors": "^1.1.1", "postcss": "^8.4.47", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", "postcss-nested": "^6.2.0", "postcss-selector-parser": "^6.1.2", "resolve": "^1.22.8", "sucrase": "^3.35.0" }, "bin": { "tailwind": "lib/cli.js", "tailwindcss": "lib/cli.js" } }, "sha512-6A2rnmW5xZMdw11LYjhcI5846rt9pbLSabY5XPxo+XWdxwZaFEn47Go4NzFiHu9sNNmr/kXivP1vStfvMaK1GQ=="], + + "@web3icons/react/bun-types": ["bun-types@1.3.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-5ua817+BZPZOlNaRgGBpZJOSAQ9RQ17pkwPD0yR7CfJg+r8DgIILByFifDTa+IPDDxzf5VNhtNlcKqFzDgJvlQ=="], "anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], @@ -2444,6 +2455,8 @@ "eslint-plugin-react/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + "eslint-plugin-tailwindcss/tailwindcss": ["tailwindcss@3.4.18", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", "chokidar": "^3.6.0", "didyoumean": "^1.2.2", "dlv": "^1.1.3", "fast-glob": "^3.3.2", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", "jiti": "^1.21.7", "lilconfig": "^3.1.3", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", "picocolors": "^1.1.1", "postcss": "^8.4.47", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", "postcss-nested": "^6.2.0", "postcss-selector-parser": "^6.1.2", "resolve": "^1.22.8", "sucrase": "^3.35.0" }, "bin": { "tailwind": "lib/cli.js", "tailwindcss": "lib/cli.js" } }, "sha512-6A2rnmW5xZMdw11LYjhcI5846rt9pbLSabY5XPxo+XWdxwZaFEn47Go4NzFiHu9sNNmr/kXivP1vStfvMaK1GQ=="], + "eslint-plugin-testing-library/@typescript-eslint/utils": ["@typescript-eslint/utils@5.62.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@types/json-schema": "^7.0.9", "@types/semver": "^7.3.12", "@typescript-eslint/scope-manager": "5.62.0", "@typescript-eslint/types": "5.62.0", "@typescript-eslint/typescript-estree": "5.62.0", "eslint-scope": "^5.1.1", "semver": "^7.3.7" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ=="], "eslint-plugin-unicorn/indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="], @@ -2534,8 +2547,6 @@ "sucrase/commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], - "tailwindcss/postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], - "tempy/is-stream": ["is-stream@3.0.0", "", {}, "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA=="], "tsconfig-paths/json5": ["json5@1.0.2", "", { "dependencies": { "minimist": "^1.2.0" }, "bin": { "json5": "lib/cli.js" } }, "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA=="], @@ -2594,6 +2605,8 @@ "@vercel/style-guide/@typescript-eslint/parser/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@6.21.0", "", { "dependencies": { "@typescript-eslint/types": "6.21.0", "eslint-visitor-keys": "^3.4.1" } }, "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A=="], + "@web3icons/figma-plugin/tailwindcss/postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], + "concurrently/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], "csso/css-tree/mdn-data": ["mdn-data@2.0.28", "", {}, "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g=="], @@ -2614,6 +2627,8 @@ "eslint-plugin-jest/@typescript-eslint/utils/eslint-scope": ["eslint-scope@5.1.1", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" } }, "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw=="], + "eslint-plugin-tailwindcss/tailwindcss/postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], + "eslint-plugin-testing-library/@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@5.62.0", "", { "dependencies": { "@typescript-eslint/types": "5.62.0", "@typescript-eslint/visitor-keys": "5.62.0" } }, "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w=="], "eslint-plugin-testing-library/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@5.62.0", "", {}, "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ=="],