diff --git a/apps/web/public/social/discord.svg b/apps/web/public/social/discord.svg
new file mode 100644
index 0000000..e196d6f
--- /dev/null
+++ b/apps/web/public/social/discord.svg
@@ -0,0 +1,4 @@
+
diff --git a/apps/web/public/social/x.svg b/apps/web/public/social/x.svg
new file mode 100644
index 0000000..a77d253
--- /dev/null
+++ b/apps/web/public/social/x.svg
@@ -0,0 +1,4 @@
+
diff --git a/apps/web/src/components/Header.astro b/apps/web/src/components/Header.astro
index dbd0f4b..293314a 100644
--- a/apps/web/src/components/Header.astro
+++ b/apps/web/src/components/Header.astro
@@ -3,6 +3,7 @@ import ExternalLink from '@lucide/astro/icons/external-link';
import { GITHUB_REPO_URL, withBase } from '../lib/site';
import { HeaderActions } from './HeaderActions.tsx';
import { MobileChrome } from './MobileChrome.tsx';
+import { SocialLinks } from './SocialLinks.tsx';
const homeHref = withBase();
const learnHref = withBase('learn');
@@ -45,6 +46,7 @@ function tabClass(active: boolean): string {
(opens in a new tab)
+
diff --git a/apps/web/src/components/MobileChrome.tsx b/apps/web/src/components/MobileChrome.tsx
index 1fd6840..858f142 100644
--- a/apps/web/src/components/MobileChrome.tsx
+++ b/apps/web/src/components/MobileChrome.tsx
@@ -3,6 +3,7 @@ import { ExternalLink, Menu, X } from 'lucide-react';
import { WalletProviders } from './wallet/WalletProviders.tsx';
import { NetworkSwitch } from './NetworkSwitch.tsx';
import { ConnectButton } from './wallet/ConnectButton.tsx';
+import { SocialLinks } from './SocialLinks.tsx';
export function MobileChrome({
learnHref,
@@ -89,6 +90,12 @@ export function MobileChrome({
(opens in a new tab)
+
diff --git a/apps/web/src/components/SocialLinks.tsx b/apps/web/src/components/SocialLinks.tsx
new file mode 100644
index 0000000..e66636e
--- /dev/null
+++ b/apps/web/src/components/SocialLinks.tsx
@@ -0,0 +1,46 @@
+import { DISCORD_URL, X_URL } from '../lib/site.ts';
+import { SocialMark } from './SocialMark.tsx';
+
+const ITEMS = [
+ { name: 'discord' as const, href: DISCORD_URL, label: 'Discord' },
+ { name: 'x' as const, href: X_URL, label: 'X' },
+] as const;
+
+export function SocialLinks({
+ labeled = false,
+ className,
+ iconClass,
+ linkClass,
+}: {
+ labeled?: boolean;
+ className?: string;
+ iconClass?: string;
+ linkClass?: string;
+}) {
+ const markClass = iconClass ?? (labeled ? 'h-3.5 w-3.5' : 'h-4 w-4');
+ const itemClass =
+ linkClass ??
+ (labeled
+ ? 'inline-flex items-center gap-1.5 text-[0.65rem] font-normal tracking-[0.28em] text-white uppercase md:text-[0.7rem]'
+ : 'inline-flex items-center justify-center p-1 text-paper/80 hover:text-white focus-visible:outline focus-visible:outline-1 focus-visible:outline-offset-2 focus-visible:outline-accent');
+
+ return (
+
+ );
+}
diff --git a/apps/web/src/components/SocialMark.tsx b/apps/web/src/components/SocialMark.tsx
new file mode 100644
index 0000000..b57a773
--- /dev/null
+++ b/apps/web/src/components/SocialMark.tsx
@@ -0,0 +1,16 @@
+import { DISCORD_MARK_PATH, DISCORD_MARK_VIEWBOX, X_MARK_PATH, X_MARK_VIEWBOX } from '../lib/social.ts';
+
+export function SocialMark({ name, className }: { name: 'discord' | 'x'; className?: string }) {
+ const viewBox = name === 'discord' ? DISCORD_MARK_VIEWBOX : X_MARK_VIEWBOX;
+ const path = name === 'discord' ? DISCORD_MARK_PATH : X_MARK_PATH;
+ return (
+
+ );
+}
diff --git a/apps/web/src/components/dashboard/DashboardApp.tsx b/apps/web/src/components/dashboard/DashboardApp.tsx
index 68c3bc3..bf5fd5d 100644
--- a/apps/web/src/components/dashboard/DashboardApp.tsx
+++ b/apps/web/src/components/dashboard/DashboardApp.tsx
@@ -9,6 +9,7 @@ import { MIN_GAS_RESERVE_WEI } from '../../lib/slippage.ts';
import { simulateThenSend, waitOneConfirmation, type WalletWriter } from '../../lib/tx.ts';
import { isUserRejected, revertShortMessage } from '../../lib/errors.ts';
import { useLiveSnapshot } from '../useLiveSnapshot.ts';
+import { SocialLinks } from '../SocialLinks.tsx';
import { WalletProviders } from '../wallet/WalletProviders.tsx';
import { WrongNetworkBanner } from '../wallet/WrongNetworkBanner.tsx';
import { BuryForm } from './BuryForm.tsx';
@@ -261,6 +262,7 @@ function DashboardBody() {
return (
+
);
@@ -378,6 +380,7 @@ function DashboardBody() {
onChain={onChain}
nethBalance={nethBalance}
/>
+
);
diff --git a/apps/web/src/lib/site.ts b/apps/web/src/lib/site.ts
index b96fe0f..55d6890 100644
--- a/apps/web/src/lib/site.ts
+++ b/apps/web/src/lib/site.ts
@@ -47,6 +47,8 @@ export const STATS: readonly Stat[] = [
export const GITHUB_REPO_FALLBACK = 'https://github.com/rastsislaux/nether';
export const GITHUB_REPO_URL = githubRepoUrl();
+export const DISCORD_URL = 'https://discord.gg/N9mTHr5VE';
+export const X_URL = 'https://x.com/netherprotocol';
export const RISK_WARNING =
'Capital at high risk, team does not hold any responsibility for possible losses. Hover for details.';
diff --git a/apps/web/src/lib/social.test.ts b/apps/web/src/lib/social.test.ts
new file mode 100644
index 0000000..51234b7
--- /dev/null
+++ b/apps/web/src/lib/social.test.ts
@@ -0,0 +1,36 @@
+import assert from 'node:assert/strict';
+import { existsSync, readFileSync } from 'node:fs';
+import { dirname, join } from 'node:path';
+import { describe, it } from 'node:test';
+import { fileURLToPath } from 'node:url';
+import { DISCORD_URL, X_URL } from './site.ts';
+import {
+ DISCORD_MARK_FILE,
+ DISCORD_MARK_PATH,
+ DISCORD_MARK_VIEWBOX,
+ X_MARK_FILE,
+ X_MARK_PATH,
+ X_MARK_VIEWBOX,
+} from './social.ts';
+
+describe('social links', () => {
+ const publicDir = join(dirname(fileURLToPath(import.meta.url)), '../../public');
+
+ it('points at Nether Discord and X accounts', () => {
+ assert.equal(DISCORD_URL, 'https://discord.gg/N9mTHr5VE');
+ assert.equal(X_URL, 'https://x.com/netherprotocol');
+ });
+
+ it('publishes unmodified Discord and X marks', () => {
+ for (const [file, path, viewBox] of [
+ [DISCORD_MARK_FILE, DISCORD_MARK_PATH, DISCORD_MARK_VIEWBOX],
+ [X_MARK_FILE, X_MARK_PATH, X_MARK_VIEWBOX],
+ ] as const) {
+ assert.equal(existsSync(join(publicDir, file)), true, file);
+ const svg = readFileSync(join(publicDir, file), 'utf8');
+ assert.ok(svg.includes(path));
+ assert.ok(svg.includes(`viewBox="${viewBox}"`));
+ assert.ok(svg.includes('fill="#000000"'));
+ }
+ });
+});
diff --git a/apps/web/src/lib/social.ts b/apps/web/src/lib/social.ts
new file mode 100644
index 0000000..33673b5
--- /dev/null
+++ b/apps/web/src/lib/social.ts
@@ -0,0 +1,11 @@
+export const DISCORD_MARK_FILE = 'social/discord.svg';
+export const X_MARK_FILE = 'social/x.svg';
+
+export const DISCORD_MARK_VIEWBOX = '0 0 24 24';
+export const X_MARK_VIEWBOX = '0 0 24 24';
+
+export const DISCORD_MARK_PATH =
+ 'M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z';
+
+export const X_MARK_PATH =
+ 'M18.901 1.153h3.68l-8.04 9.19L24 22.846h-7.406l-5.8-7.584-6.638 7.584H.474l8.6-9.83L0 1.154h7.594l5.243 6.932ZM17.61 20.644h2.039L6.486 3.24H4.298Z';
diff --git a/apps/web/src/pages/index.astro b/apps/web/src/pages/index.astro
index 51acbf4..9488da1 100644
--- a/apps/web/src/pages/index.astro
+++ b/apps/web/src/pages/index.astro
@@ -4,6 +4,7 @@ import FeatureIcon from '../components/FeatureIcons.astro';
import GraveCta from '../components/GraveCta.astro';
import { HolderStats } from '../components/HolderStats.tsx';
import RiskWarning from '../components/RiskWarning.astro';
+import { SocialLinks } from '../components/SocialLinks.tsx';
import {
FEATURES,
HOLDER_COPY,
@@ -43,6 +44,7 @@ const heroSrc = withBase('hero.png');
>
{SECONDARY_CTA}
+
diff --git a/apps/web/src/pages/learn.astro b/apps/web/src/pages/learn.astro
index 6b3782e..7279d43 100644
--- a/apps/web/src/pages/learn.astro
+++ b/apps/web/src/pages/learn.astro
@@ -21,7 +21,8 @@ import {
formatLearnNumber,
hasIndependentProductionAudit,
} from '../lib/learn';
-import { GITHUB_REPO_URL, withBase } from '../lib/site';
+import { DISCORD_URL, GITHUB_REPO_URL, X_URL, withBase } from '../lib/site';
+import { SocialMark } from '../components/SocialMark.tsx';
const docsHref = withBase('docs');
const specHref = withBase('docs/protocol-spec');
@@ -138,6 +139,7 @@ const toc = [
{ href: '#strategy-losses', label: 'Strategy losses' },
{ href: '#allocation', label: 'Token allocation' },
{ href: '#faq', label: 'FAQ' },
+ { href: '#community', label: 'Questions' },
{ href: '#risks', label: 'Risks' },
{ href: '#verify', label: 'Verify it' },
] as const;
@@ -684,6 +686,24 @@ const toc = [
)
}
+
+
+ If you have questions, you can ask them in{' '}
+
+ Nether's Discord
+ , opens in a new tab
+
+ .
+
+
+ Protocol updates are also posted on{' '}
+
+ X
+ , opens in a new tab
+
+ .
+
+
-
@@ -711,6 +731,42 @@ const toc = [
+
+