diff --git a/package-lock.json b/package-lock.json
index b15ba7e..5fd6cef 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -28,6 +28,7 @@
"emoji-picker-element": "^1.28.1",
"highlight.js": "^11.11.1",
"idb": "^8.0.0",
+ "lucide-svelte": "^0.563.0",
"marked": "^11.1.1",
"nostr-tools": "^2.22.1",
"svelte": "^5.0.0",
@@ -3867,6 +3868,15 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/lucide-svelte": {
+ "version": "0.563.0",
+ "resolved": "https://registry.npmjs.org/lucide-svelte/-/lucide-svelte-0.563.0.tgz",
+ "integrity": "sha512-pjZKw7TpQcamfQrx7YdbOHgmrcNeKiGGMD0tKZQaVktwSsbqw28CsKc2Q97ttwjytiCWkJyOa8ij2Q+Og0nPfQ==",
+ "license": "ISC",
+ "peerDependencies": {
+ "svelte": "^3 || ^4 || ^5.0.0-next.42"
+ }
+ },
"node_modules/magic-string": {
"version": "0.30.21",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
diff --git a/package.json b/package.json
index 1f35947..cbd779f 100644
--- a/package.json
+++ b/package.json
@@ -42,6 +42,7 @@
"emoji-picker-element": "^1.28.1",
"highlight.js": "^11.11.1",
"idb": "^8.0.0",
+ "lucide-svelte": "^0.563.0",
"marked": "^11.1.1",
"nostr-tools": "^2.22.1",
"svelte": "^5.0.0",
diff --git a/public/healthz.json b/public/healthz.json
index b191b11..063de27 100644
--- a/public/healthz.json
+++ b/public/healthz.json
@@ -2,7 +2,7 @@
"status": "ok",
"service": "aitherboard",
"version": "0.2.0",
- "buildTime": "2026-02-06T17:29:48.880Z",
+ "buildTime": "2026-02-06T19:28:59.767Z",
"gitCommit": "unknown",
- "timestamp": 1770398988880
+ "timestamp": 1770406139768
}
\ No newline at end of file
diff --git a/scripts/extract-icons.js b/scripts/extract-icons.js
new file mode 100644
index 0000000..362b164
--- /dev/null
+++ b/scripts/extract-icons.js
@@ -0,0 +1,73 @@
+import { writeFileSync, mkdirSync } from 'fs';
+import { fileURLToPath } from 'url';
+import { dirname, join } from 'path';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = dirname(__filename);
+
+// Icons we need to extract
+const icons = [
+ 'zap', // ⚡
+ 'heart', // ❤️
+ 'chevron-up', // ⬆️
+ 'chevron-down', // ⬇️
+ 'sun', // ☀️
+ 'moon', // 🌙
+ 'user', // 👤
+ 'plus' // ➕
+];
+
+const staticDir = join(__dirname, '..', 'static', 'icons');
+mkdirSync(staticDir, { recursive: true });
+
+// Import lucide-svelte to get the icon paths
+try {
+ const lucide = await import('lucide-svelte');
+
+ for (const iconName of icons) {
+ // Convert kebab-case to PascalCase
+ const componentName = iconName
+ .split('-')
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
+ .join('');
+
+ // Get the icon component
+ const IconComponent = lucide[componentName];
+
+ if (!IconComponent) {
+ console.warn(`Icon ${iconName} (${componentName}) not found`);
+ continue;
+ }
+
+ // Extract SVG path from the component
+ // Lucide icons are SVG components, we need to render them to get the path
+ // For now, let's use the known SVG paths from lucide
+ const svgPaths = {
+ 'zap': '',
+ 'heart': '',
+ 'chevron-up': '',
+ 'chevron-down': '',
+ 'sun': '',
+ 'moon': '',
+ 'user': '',
+ 'plus': ''
+ };
+
+ const path = svgPaths[iconName];
+ if (!path) {
+ console.warn(`No SVG path found for ${iconName}`);
+ continue;
+ }
+
+ const svg = ``;
+
+ const filePath = join(staticDir, `${iconName}.svg`);
+ writeFileSync(filePath, svg, 'utf-8');
+ console.log(`Extracted ${iconName}.svg`);
+ }
+
+ console.log('All icons extracted successfully!');
+} catch (error) {
+ console.error('Error extracting icons:', error);
+ process.exit(1);
+}
diff --git a/src/lib/components/EventMenu.svelte b/src/lib/components/EventMenu.svelte
index 256c5a8..ff9d63e 100644
--- a/src/lib/components/EventMenu.svelte
+++ b/src/lib/components/EventMenu.svelte
@@ -19,6 +19,7 @@
import RelatedEventsModal from './modals/RelatedEventsModal.svelte';
import { KIND } from '../types/kind-lookup.js';
import { goto } from '$app/navigation';
+ import Icon from './ui/Icon.svelte';
interface Props {
event: NostrEvent;
@@ -384,30 +385,36 @@
style="top: {menuPosition.top}px; right: {menuPosition.right}px;"
>
{#if isLoggedIn}
{/if}
- 📤
+
@@ -996,7 +997,7 @@
title="Preview"
aria-label="Preview"
>
- 👁️
+
@@ -1014,10 +1015,12 @@
@@ -1049,7 +1052,9 @@
>
{#if previewEvent && previewContent}
@@ -1062,7 +1067,10 @@
{/if}
@@ -1091,7 +1099,9 @@
>
{eventJson}
@@ -1100,8 +1110,14 @@
-
+ }} class="flex items-center gap-2">
+
+
Copy
+
+
@@ -1175,6 +1191,9 @@
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
transition: all 0.2s;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
}
.close-button:hover {
@@ -1449,6 +1468,9 @@
cursor: pointer;
transition: all 0.2s;
border: 1px solid var(--fog-border, #e5e7eb);
+ display: inline-flex;
+ align-items: center;
+ gap: 0.5rem;
}
.cancel-button {
diff --git a/src/lib/components/write/CreateEventForm.svelte b/src/lib/components/write/CreateEventForm.svelte
index 419d52d..3702dfe 100644
--- a/src/lib/components/write/CreateEventForm.svelte
+++ b/src/lib/components/write/CreateEventForm.svelte
@@ -17,6 +17,7 @@
import type { NostrEvent } from '../../types/nostr.js';
import { autoExtractTags, ensureDTagForParameterizedReplaceable } from '../../services/auto-tagging.js';
import { isParameterizedReplaceableKind } from '../../types/kind-lookup.js';
+ import Icon from '../ui/Icon.svelte';
const SUPPORTED_KINDS = getWritableKinds();
@@ -697,7 +698,9 @@
>
{eventJson}
@@ -706,8 +709,14 @@
-
+ }} class="flex items-center gap-2">
+
+
Copy
+
+
@@ -736,7 +745,9 @@
>
{#if previewEvent && previewContent}
@@ -799,7 +810,10 @@
{/if}
@@ -828,7 +842,9 @@
>
{exampleJSON}
@@ -837,8 +853,14 @@
-
+ }} class="flex items-center gap-2">
+
+
Copy
+
+
@@ -1591,6 +1613,9 @@
cursor: pointer;
font-size: 0.875rem;
transition: all 0.2s;
+ display: inline-flex;
+ align-items: center;
+ gap: 0.5rem;
}
.modal-footer button:hover {
diff --git a/src/lib/modules/discussions/DiscussionCard.svelte b/src/lib/modules/discussions/DiscussionCard.svelte
index d9e5832..fbe9aa4 100644
--- a/src/lib/modules/discussions/DiscussionCard.svelte
+++ b/src/lib/modules/discussions/DiscussionCard.svelte
@@ -14,6 +14,7 @@
import type { NostrEvent } from '../../types/nostr.js';
import { getKindInfo, KIND } from '../../types/kind-lookup.js';
import { stripMarkdown } from '../../services/text-utils.js';
+ import Icon from '../../components/ui/Icon.svelte';
interface Props {
thread: NostrEvent;
@@ -339,7 +340,10 @@
Last: {getLatestResponseTime()}
{/if}
{#if zapCount > 0}
- ⚡ {zapTotal.toLocaleString()} sats ({zapCount})
+
+
+ {zapTotal.toLocaleString()} sats ({zapCount})
+
{/if}
{/if}
{:else}
@@ -350,7 +354,10 @@
Last: {getLatestResponseTime()}
{/if}
{#if zapCount > 0}
- ⚡ {zapTotal.toLocaleString()} sats ({zapCount})
+
+
+ {zapTotal.toLocaleString()} sats ({zapCount})
+
{/if}
{/if}
{/if}
diff --git a/src/lib/modules/feed/ZapReceiptReply.svelte b/src/lib/modules/feed/ZapReceiptReply.svelte
index 47cb734..0a8671c 100644
--- a/src/lib/modules/feed/ZapReceiptReply.svelte
+++ b/src/lib/modules/feed/ZapReceiptReply.svelte
@@ -4,6 +4,7 @@
import EventMenu from '../../components/EventMenu.svelte';
import type { NostrEvent } from '../../types/nostr.js';
import { getKindInfo } from '../../types/kind-lookup.js';
+ import Icon from '../../components/ui/Icon.svelte';
interface Props {
zapReceipt: NostrEvent; // Kind 9735 zap receipt
@@ -89,14 +90,17 @@
{#if parentEvent}
- ⚡ Zapping
+
+
+ Zapping
+
{/if}
{/if}
{:else}
@@ -401,10 +405,11 @@
{#if storedNsecKeys.length > 0}
{/if}
@@ -455,9 +460,10 @@
{/if}
@@ -501,18 +507,20 @@
{/if}
{:else}
@@ -521,10 +529,11 @@
{#if storedAnonymousKeys.length > 0}
{/if}
@@ -559,9 +568,10 @@
{/if}
diff --git a/src/routes/settings/+page.svelte b/src/routes/settings/+page.svelte
index dcd32f9..b77f8c9 100644
--- a/src/routes/settings/+page.svelte
+++ b/src/routes/settings/+page.svelte
@@ -4,6 +4,7 @@
import { hasExpiringEventsEnabled } from '../../lib/services/event-expiration.js';
import { shouldIncludeClientTag, setIncludeClientTag } from '../../lib/services/client-tag-preference.js';
import { goto } from '$app/navigation';
+ import Icon from '../../lib/components/ui/Icon.svelte';
type TextSize = 'small' | 'medium' | 'large';
type LineSpacing = 'tight' | 'normal' | 'loose';
@@ -114,10 +115,11 @@
@@ -135,7 +137,7 @@
class:active={isDark}
aria-label={isDark ? 'Switch to light mode' : 'Switch to dark mode'}
>
- {isDark ? '☀️' : '🌙'}
+
{isDark ? 'Light' : 'Dark'}
@@ -252,7 +254,9 @@
class:active={expiringEvents}
aria-label={expiringEvents ? 'Disable expiring events' : 'Enable expiring events'}
>
- {expiringEvents ? '✓' : ''}
+ {#if expiringEvents}
+
+ {/if}
{expiringEvents ? 'Enabled' : 'Disabled'}
@@ -273,7 +277,9 @@
class:active={includeClientTag}
aria-label={includeClientTag ? 'Disable client tag' : 'Enable client tag'}
>
- {includeClientTag ? '✓' : ''}
+ {#if includeClientTag}
+
+ {/if}
{includeClientTag ? 'Enabled' : 'Disabled'}
diff --git a/static/icons/arrow-left.svg b/static/icons/arrow-left.svg
new file mode 100644
index 0000000..a32807f
--- /dev/null
+++ b/static/icons/arrow-left.svg
@@ -0,0 +1 @@
+
diff --git a/static/icons/check.svg b/static/icons/check.svg
new file mode 100644
index 0000000..c2967f3
--- /dev/null
+++ b/static/icons/check.svg
@@ -0,0 +1 @@
+
diff --git a/static/icons/chevron-down.svg b/static/icons/chevron-down.svg
new file mode 100644
index 0000000..a81a4b0
--- /dev/null
+++ b/static/icons/chevron-down.svg
@@ -0,0 +1 @@
+
diff --git a/static/icons/chevron-up.svg b/static/icons/chevron-up.svg
new file mode 100644
index 0000000..34047a5
--- /dev/null
+++ b/static/icons/chevron-up.svg
@@ -0,0 +1 @@
+
diff --git a/static/icons/code.svg b/static/icons/code.svg
new file mode 100644
index 0000000..d3d98f0
--- /dev/null
+++ b/static/icons/code.svg
@@ -0,0 +1 @@
+
diff --git a/static/icons/copy.svg b/static/icons/copy.svg
new file mode 100644
index 0000000..2cdd687
--- /dev/null
+++ b/static/icons/copy.svg
@@ -0,0 +1 @@
+
diff --git a/static/icons/edit.svg b/static/icons/edit.svg
new file mode 100644
index 0000000..3c463c0
--- /dev/null
+++ b/static/icons/edit.svg
@@ -0,0 +1 @@
+
diff --git a/static/icons/eye.svg b/static/icons/eye.svg
new file mode 100644
index 0000000..edb7e12
--- /dev/null
+++ b/static/icons/eye.svg
@@ -0,0 +1 @@
+
diff --git a/static/icons/file-text.svg b/static/icons/file-text.svg
new file mode 100644
index 0000000..3d2b0a4
--- /dev/null
+++ b/static/icons/file-text.svg
@@ -0,0 +1 @@
+
diff --git a/static/icons/heart.svg b/static/icons/heart.svg
new file mode 100644
index 0000000..5d56931
--- /dev/null
+++ b/static/icons/heart.svg
@@ -0,0 +1 @@
+
diff --git a/static/icons/image.svg b/static/icons/image.svg
new file mode 100644
index 0000000..135ea22
--- /dev/null
+++ b/static/icons/image.svg
@@ -0,0 +1 @@
+
diff --git a/static/icons/key.svg b/static/icons/key.svg
new file mode 100644
index 0000000..ca8cf55
--- /dev/null
+++ b/static/icons/key.svg
@@ -0,0 +1 @@
+
diff --git a/static/icons/link.svg b/static/icons/link.svg
new file mode 100644
index 0000000..c2ea855
--- /dev/null
+++ b/static/icons/link.svg
@@ -0,0 +1 @@
+
diff --git a/static/icons/log-in.svg b/static/icons/log-in.svg
new file mode 100644
index 0000000..5d1eb95
--- /dev/null
+++ b/static/icons/log-in.svg
@@ -0,0 +1 @@
+
diff --git a/static/icons/log-out.svg b/static/icons/log-out.svg
new file mode 100644
index 0000000..8cdb5e8
--- /dev/null
+++ b/static/icons/log-out.svg
@@ -0,0 +1 @@
+
diff --git a/static/icons/message-square.svg b/static/icons/message-square.svg
new file mode 100644
index 0000000..0e3b9db
--- /dev/null
+++ b/static/icons/message-square.svg
@@ -0,0 +1 @@
+
diff --git a/static/icons/moon.svg b/static/icons/moon.svg
new file mode 100644
index 0000000..51eafa8
--- /dev/null
+++ b/static/icons/moon.svg
@@ -0,0 +1 @@
+
diff --git a/static/icons/plus.svg b/static/icons/plus.svg
new file mode 100644
index 0000000..e4c6006
--- /dev/null
+++ b/static/icons/plus.svg
@@ -0,0 +1 @@
+
diff --git a/static/icons/radio.svg b/static/icons/radio.svg
new file mode 100644
index 0000000..b9dbdae
--- /dev/null
+++ b/static/icons/radio.svg
@@ -0,0 +1 @@
+
diff --git a/static/icons/search.svg b/static/icons/search.svg
new file mode 100644
index 0000000..e8ce1a9
--- /dev/null
+++ b/static/icons/search.svg
@@ -0,0 +1 @@
+
diff --git a/static/icons/send.svg b/static/icons/send.svg
new file mode 100644
index 0000000..043122a
--- /dev/null
+++ b/static/icons/send.svg
@@ -0,0 +1 @@
+
diff --git a/static/icons/settings.svg b/static/icons/settings.svg
new file mode 100644
index 0000000..f4f5d28
--- /dev/null
+++ b/static/icons/settings.svg
@@ -0,0 +1 @@
+
diff --git a/static/icons/share.svg b/static/icons/share.svg
new file mode 100644
index 0000000..9fb6280
--- /dev/null
+++ b/static/icons/share.svg
@@ -0,0 +1 @@
+
diff --git a/static/icons/smile.svg b/static/icons/smile.svg
new file mode 100644
index 0000000..ae6c961
--- /dev/null
+++ b/static/icons/smile.svg
@@ -0,0 +1 @@
+
diff --git a/static/icons/sun.svg b/static/icons/sun.svg
new file mode 100644
index 0000000..1f4f9ba
--- /dev/null
+++ b/static/icons/sun.svg
@@ -0,0 +1 @@
+
diff --git a/static/icons/trash.svg b/static/icons/trash.svg
new file mode 100644
index 0000000..f0edd59
--- /dev/null
+++ b/static/icons/trash.svg
@@ -0,0 +1 @@
+
diff --git a/static/icons/upload.svg b/static/icons/upload.svg
new file mode 100644
index 0000000..c21b451
--- /dev/null
+++ b/static/icons/upload.svg
@@ -0,0 +1 @@
+
diff --git a/static/icons/user.svg b/static/icons/user.svg
new file mode 100644
index 0000000..ba4ee23
--- /dev/null
+++ b/static/icons/user.svg
@@ -0,0 +1 @@
+
diff --git a/static/icons/video.svg b/static/icons/video.svg
new file mode 100644
index 0000000..f177c20
--- /dev/null
+++ b/static/icons/video.svg
@@ -0,0 +1 @@
+
diff --git a/static/icons/x.svg b/static/icons/x.svg
new file mode 100644
index 0000000..4354dbf
--- /dev/null
+++ b/static/icons/x.svg
@@ -0,0 +1 @@
+
diff --git a/static/icons/zap.svg b/static/icons/zap.svg
new file mode 100644
index 0000000..4555a4c
--- /dev/null
+++ b/static/icons/zap.svg
@@ -0,0 +1 @@
+