+ )}
+
+
+ );
+}
diff --git a/src/components/HelpFAQSection.tsx b/src/components/HelpFAQSection.tsx
index c67b9677..ac6c6416 100644
--- a/src/components/HelpFAQSection.tsx
+++ b/src/components/HelpFAQSection.tsx
@@ -8,54 +8,7 @@ import {
} from '@/components/ui/accordion';
import { useAppContext } from '@/hooks/useAppContext';
import { getFAQCategories, type FAQCategory, type FAQItem } from '@/lib/helpContent';
-
-// ── Inline markup renderer ────────────────────────────────────────────────────
-
-/**
- * Very lightweight inline markup: **bold** and [text](url).
- * Returns an array of React nodes.
- */
-function renderInlineMarkup(text: string): React.ReactNode[] {
- const nodes: React.ReactNode[] = [];
- // Match **bold** or [text](url)
- const regex = /\*\*(.+?)\*\*|\[([^\]]+)\]\(([^)]+)\)/g;
- let lastIndex = 0;
- let match: RegExpExecArray | null;
-
- while ((match = regex.exec(text)) !== null) {
- // Push text before this match
- if (match.index > lastIndex) {
- nodes.push(text.slice(lastIndex, match.index));
- }
-
- if (match[1] !== undefined) {
- // **bold**
- nodes.push({match[1]});
- } else if (match[2] !== undefined && match[3] !== undefined) {
- // [text](url)
- nodes.push(
-
- {match[2]}
- ,
- );
- }
-
- lastIndex = match.index + match[0].length;
- }
-
- // Trailing text
- if (lastIndex < text.length) {
- nodes.push(text.slice(lastIndex));
- }
-
- return nodes;
-}
+import { renderInlineMarkup } from '@/lib/helpMarkup';
// ── Component ─────────────────────────────────────────────────────────────────
diff --git a/src/components/HelpTip.tsx b/src/components/HelpTip.tsx
index b9c3ceb4..8e42ae64 100644
--- a/src/components/HelpTip.tsx
+++ b/src/components/HelpTip.tsx
@@ -4,42 +4,7 @@ import { Link } from 'react-router-dom';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { useAppContext } from '@/hooks/useAppContext';
import { getFAQItem } from '@/lib/helpContent';
-
-/**
- * Renders **bold** and [text](url) markup in FAQ answer strings.
- */
-function renderInlineMarkup(text: string): React.ReactNode[] {
- const nodes: React.ReactNode[] = [];
- const regex = /\*\*(.+?)\*\*|\[([^\]]+)\]\(([^)]+)\)/g;
- let lastIndex = 0;
- let match: RegExpExecArray | null;
-
- while ((match = regex.exec(text)) !== null) {
- if (match.index > lastIndex) {
- nodes.push(text.slice(lastIndex, match.index));
- }
- if (match[1] !== undefined) {
- nodes.push({match[1]});
- } else if (match[2] !== undefined && match[3] !== undefined) {
- nodes.push(
-
- {match[2]}
- ,
- );
- }
- lastIndex = match.index + match[0].length;
- }
- if (lastIndex < text.length) {
- nodes.push(text.slice(lastIndex));
- }
- return nodes;
-}
+import { renderInlineMarkup } from '@/lib/helpMarkup';
// ── Component ─────────────────────────────────────────────────────────────────
diff --git a/src/lib/helpContent.ts b/src/lib/helpContent.ts
index a132ad35..71b7fd4d 100644
--- a/src/lib/helpContent.ts
+++ b/src/lib/helpContent.ts
@@ -345,6 +345,61 @@ const FAQ_TEMPLATE: FAQCategory[] = [
},
],
},
+
+ // ── About Agora (design rationale) ──────────────────────────────────────
+ {
+ id: 'agora-design',
+ label: 'About Agora',
+ items: [
+ {
+ id: 'what-is-agora',
+ question: 'What is {appName} for?',
+ answer: [
+ '{appName} is a Nostr platform for sending on-chain Bitcoin donations directly to activists. No middleman, no payment processor, no account to freeze.',
+ ],
+ },
+ {
+ id: 'donations-are-public-general',
+ question: 'Are donations on {appName} public?',
+ answer: [
+ 'Yes. Every donation \u2014 given or received \u2014 is recorded on the public Bitcoin blockchain and on Nostr. Anyone can see the amounts, the timing, and the addresses involved.',
+ 'Read the **Donor Guide** and **Activist Guide** for what this means in practice and how to protect your privacy if you need to.',
+ ],
+ },
+ {
+ id: 'why-not-lightning',
+ question: 'Why doesn\'t {appName} use Lightning?',
+ answer: [
+ 'Lightning requires a Lightning wallet. The easiest ones (like Wallet of Satoshi) are **custodial** \u2014 a company holds the funds and can be shut down, pressured, or pulled offline by a bad actor. Non-custodial Lightning is technically demanding and unreliable for newcomers.',
+ 'We want {appName} to work for someone whose only Bitcoin experience is Cash App. On-chain Bitcoin works with every wallet on the planet.',
+ ],
+ },
+ {
+ id: 'why-not-silent-payments',
+ question: 'Why doesn\'t {appName} use silent payments?',
+ answer: [
+ 'Silent payments only work when the **sender\'s** wallet supports them. Most popular wallets \u2014 Cash App, Strike, and nearly every custodial wallet \u2014 do not.',
+ 'Asking donors to install new software is a barrier we won\'t put in front of activists who need support.',
+ ],
+ },
+ {
+ id: 'why-not-rotating-addresses',
+ question: 'Why doesn\'t {appName} generate a new address for every donation?',
+ answer: [
+ 'Generating a fresh address per donation would require {appName} to run a server that signs and serves addresses. That server becomes a single point of failure \u2014 someone could shut it down to silence activists.',
+ '{appName} derives each user\'s donation address from their Nostr public key. No server is required, and the platform itself can\'t be turned off to censor anyone.',
+ ],
+ },
+ {
+ id: 'why-onchain',
+ question: 'Why on-chain Bitcoin?',
+ answer: [
+ 'On-chain Bitcoin is the most widely supported and censorship-resistant payment rail in the world. Every Bitcoin wallet can send it.',
+ 'The tradeoff is that on-chain transactions are public and pay a miner fee. The Donor and Activist guides explain how to handle both.',
+ ],
+ },
+ ],
+ },
];
// ── Helpers ───────────────────────────────────────────────────────────────────
@@ -404,3 +459,193 @@ export function getFAQItem(appName: string, itemId: string): FAQItem | undefined
* canonical constant directly.
*/
export { TEAM_SOAPBOX as TEAM_SOAPBOX_PACK } from '@/lib/agoraDefaults';
+
+// ── Donor / Activist guide content ────────────────────────────────────────────
+
+/**
+ * A single section inside a long-form guide page (Donor Guide / Activist
+ * Guide). Each section renders as a Card on the guide page.
+ *
+ * `paragraphs` accept the same inline markup as FAQ answers (**bold** and
+ * [link](url)), rendered by `renderInlineMarkup` from `@/lib/helpMarkup`.
+ *
+ * `pros` / `cons` are optional and render as a bullet pair underneath the
+ * paragraphs. They are used for tradeoff-heavy topics like cash-out methods.
+ */
+export interface GuideSection {
+ /** Stable key, used for React keys and potential deep-linking. */
+ id: string;
+ /** Section heading. */
+ heading: string;
+ /** Body paragraphs, in order. */
+ paragraphs: string[];
+ /** Optional positives, rendered as a green-flavored bullet list. */
+ pros?: string[];
+ /** Optional negatives / caveats, rendered as an amber-flavored bullet list. */
+ cons?: string[];
+}
+
+const DONOR_GUIDE_TEMPLATE: GuideSection[] = [
+ {
+ id: 'how-donating-works',
+ heading: 'How donating works',
+ paragraphs: [
+ 'You send real Bitcoin on-chain directly to the activist. {appName} doesn\'t hold or route the money \u2014 the address you\'re paying is derived from the activist\'s Nostr key, so there\'s no middleman in between.',
+ 'You pay a small network fee to Bitcoin miners. Once the transaction is broadcast, it\'s public and irreversible.',
+ ],
+ },
+ {
+ id: 'why-public',
+ heading: 'Why your donation is public',
+ paragraphs: [
+ 'Bitcoin is a public ledger. Anyone can look up an activist\'s address and see every donation \u2014 the amount, the time, and the address it came from.',
+ 'Your sending address can usually be traced back to wherever you bought the Bitcoin (Cash App, Coinbase, Strike, etc.). That link is what ties a donation to your real identity.',
+ ],
+ },
+ {
+ id: 'privacy-non-kyc',
+ heading: 'For privacy: use non-KYC Bitcoin',
+ paragraphs: [
+ 'Buy Bitcoin peer-to-peer so it isn\'t linked to your government ID. Marketplaces like [Bisq](https://bisq.network), [RoboSats](https://learn.robosats.com), and [HodlHodl](https://hodlhodl.com) let you trade directly with another person.',
+ ],
+ pros: ['No exchange knows who you are.', 'Strongest privacy starting point.'],
+ cons: ['Slower and harder than Cash App.', 'Requires finding a counterparty.'],
+ },
+ {
+ id: 'privacy-coinjoin',
+ heading: 'For privacy: coinjoin before donating',
+ paragraphs: [
+ 'A coinjoin mixes your Bitcoin with other people\'s coins so the output can\'t be linked back to the input. Wallets like [Wasabi](https://wasabiwallet.io), [Sparrow](https://sparrowwallet.com), and [JoinMarket](https://github.com/JoinMarket-Org/joinmarket-clientserver) support this.',
+ ],
+ pros: ['Breaks the on-chain trail from your KYC purchase.', 'Non-custodial \u2014 you keep your keys.'],
+ cons: ['Costs fees and takes time.', 'Fewer maintained tools after the Samourai shutdown.'],
+ },
+ {
+ id: 'fresh-wallet',
+ heading: 'Use a fresh wallet',
+ paragraphs: [
+ 'Donate from a wallet that has never touched a KYC exchange or your main identity. Even one shared transaction input can link the wallet back to you.',
+ 'Free options include [Sparrow](https://sparrowwallet.com) on desktop and [BlueWallet](https://bluewallet.io) on mobile.',
+ ],
+ },
+ {
+ id: 'vary-amounts',
+ heading: 'Vary amounts and timing',
+ paragraphs: [
+ 'Round numbers ($50, $100) and recurring donations create a pattern that\'s easy to fingerprint. Send unusual amounts at irregular times if you want to be harder to track.',
+ ],
+ },
+ {
+ id: 'what-cash-app-cant-do',
+ heading: 'What Cash App and similar apps can\'t do',
+ paragraphs: [
+ 'Cash App, Strike, and most custodial wallets are convenient but tied to your real identity. They can\'t make a donation truly anonymous, no matter how you send it.',
+ 'If anonymity matters to you, use a non-custodial wallet you control.',
+ ],
+ },
+];
+
+const ACTIVIST_GUIDE_TEMPLATE: GuideSection[] = [
+ {
+ id: 'how-receiving-works',
+ heading: 'How receiving works',
+ paragraphs: [
+ 'Your {appName} donation address is derived from your Nostr public key. Donors send on-chain Bitcoin directly to it. No one stands between you and the funds, and no server can be shut down to stop the donations.',
+ ],
+ },
+ {
+ id: 'why-public',
+ heading: 'Why incoming donations are public',
+ paragraphs: [
+ 'Bitcoin is a public ledger. Anyone can look up your address and see every donation \u2014 the amount, the time, and the sending address. Your supporters\' addresses are visible too.',
+ ],
+ },
+ {
+ id: 'dont-keep-funds',
+ heading: 'Don\'t keep funds at your {appName} address',
+ paragraphs: [
+ 'Move funds to a wallet you control as soon as practical. Treat your {appName} address like a mailbox, not a savings account.',
+ 'Good self-custody wallets to move funds into: [Sparrow](https://sparrowwallet.com), [BlueWallet](https://bluewallet.io), or [Phoenix](https://phoenix.acinq.co) (Lightning).',
+ ],
+ },
+ {
+ id: 'cashout-overview',
+ heading: 'Cashing out privately \u2014 overview',
+ paragraphs: [
+ 'To spend donations without revealing who you are, you have to break the on-chain trail before converting to cash. The next sections cover the main paths. Each has tradeoffs in custody, privacy, difficulty, and fees.',
+ ],
+ },
+ {
+ id: 'cashout-lightning-swap',
+ heading: 'Lightning swap (Boltz, Bolt.exchange)',
+ paragraphs: [
+ 'Services like [Boltz](https://boltz.exchange) atomic-swap your on-chain Bitcoin into Lightning. Lightning payments are private by default \u2014 they don\'t appear on the public blockchain.',
+ ],
+ pros: ['Instant and non-custodial.', 'Lightning payments aren\'t publicly traceable.'],
+ cons: ['Per-swap limits and swap fees.', 'Depends on the swap service being online.'],
+ },
+ {
+ id: 'cashout-coinjoin',
+ heading: 'Coinjoin',
+ paragraphs: [
+ 'A coinjoin mixes your Bitcoin with other users\' coins so the output can\'t be linked to the input. [Wasabi](https://wasabiwallet.io) and [JoinMarket](https://github.com/JoinMarket-Org/joinmarket-clientserver) are the main maintained options after the Samourai shutdown.',
+ ],
+ pros: ['Strong on-chain unlinkability.', 'Non-custodial.'],
+ cons: ['Fees and wait time.', 'Steeper learning curve than a swap.'],
+ },
+ {
+ id: 'cashout-p2p',
+ heading: 'Peer-to-peer exchange',
+ paragraphs: [
+ 'Trade Bitcoin for fiat directly with another person on [Bisq](https://bisq.network), [RoboSats](https://learn.robosats.com), or [HodlHodl](https://hodlhodl.com). No exchange records your identity.',
+ ],
+ pros: ['Cash in hand without KYC.', 'No central exchange knows you.'],
+ cons: ['Slower than an exchange.', 'Requires a willing counterparty.', 'Some learning curve.'],
+ },
+ {
+ id: 'cashout-tumblers',
+ heading: 'Tumblers and centralized mixers',
+ paragraphs: [
+ '**Generally not recommended.** Centralized tumblers are custodial \u2014 you have to trust the operator not to steal your coins or log who sent what. Many are scams or law-enforcement honeypots.',
+ 'Coinjoin is the non-custodial alternative and is almost always the better choice.',
+ ],
+ },
+ {
+ id: 'cashout-comparison',
+ heading: 'Quick comparison',
+ paragraphs: [
+ '**Lightning swap (Boltz):** non-custodial \u00b7 medium privacy \u00b7 easy \u00b7 low fees.',
+ '**Coinjoin (Wasabi, JoinMarket):** non-custodial \u00b7 high privacy \u00b7 medium difficulty \u00b7 medium fees.',
+ '**Peer-to-peer (Bisq, RoboSats):** non-custodial \u00b7 high privacy \u00b7 harder \u00b7 variable fees.',
+ '**Tumblers:** custodial \u00b7 unpredictable privacy \u00b7 easy \u00b7 high risk. **Avoid.**',
+ ],
+ },
+ {
+ id: 'donors-can-be-seen',
+ heading: 'Your donation history is visible to future supporters',
+ paragraphs: [
+ 'Anyone considering supporting you can look up your address and see the full donation history. Keep in mind how that history reads to a new donor.',
+ ],
+ },
+];
+
+/** Substitute placeholders in a single guide section. */
+function substituteGuideSection(section: GuideSection, appName: string): GuideSection {
+ return {
+ ...section,
+ heading: substitute(section.heading, appName),
+ paragraphs: section.paragraphs.map((p) => substitute(p, appName)),
+ pros: section.pros?.map((p) => substitute(p, appName)),
+ cons: section.cons?.map((c) => substitute(c, appName)),
+ };
+}
+
+/** Donor guide sections with `{appName}` resolved. */
+export function getDonorGuideSections(appName: string): GuideSection[] {
+ return DONOR_GUIDE_TEMPLATE.map((s) => substituteGuideSection(s, appName));
+}
+
+/** Activist guide sections with `{appName}` resolved. */
+export function getActivistGuideSections(appName: string): GuideSection[] {
+ return ACTIVIST_GUIDE_TEMPLATE.map((s) => substituteGuideSection(s, appName));
+}
diff --git a/src/lib/helpMarkup.tsx b/src/lib/helpMarkup.tsx
new file mode 100644
index 00000000..00279836
--- /dev/null
+++ b/src/lib/helpMarkup.tsx
@@ -0,0 +1,60 @@
+/**
+ * Shared inline-markup renderer used by Help/FAQ surfaces and the
+ * Donor / Activist guide pages.
+ *
+ * Supports a deliberately tiny syntax so authors can write content in plain
+ * strings without pulling in a full markdown parser:
+ *
+ * **bold** → bold
+ * [link text](url) → link text
+ *
+ * The renderer returns an array of React nodes suitable for splatting into a
+ * paragraph or span. It is intentionally non-recursive: bold inside a link or
+ * vice versa is not supported (and not needed by current content).
+ */
+
+export function renderInlineMarkup(text: string): React.ReactNode[] {
+ const nodes: React.ReactNode[] = [];
+ // Match **bold** or [text](url)
+ const regex = /\*\*(.+?)\*\*|\[([^\]]+)\]\(([^)]+)\)/g;
+ let lastIndex = 0;
+ let match: RegExpExecArray | null;
+
+ while ((match = regex.exec(text)) !== null) {
+ // Push text before this match
+ if (match.index > lastIndex) {
+ nodes.push(text.slice(lastIndex, match.index));
+ }
+
+ if (match[1] !== undefined) {
+ // **bold**
+ nodes.push(
+
+ {match[1]}
+ ,
+ );
+ } else if (match[2] !== undefined && match[3] !== undefined) {
+ // [text](url)
+ nodes.push(
+
+ {match[2]}
+ ,
+ );
+ }
+
+ lastIndex = match.index + match[0].length;
+ }
+
+ // Trailing text
+ if (lastIndex < text.length) {
+ nodes.push(text.slice(lastIndex));
+ }
+
+ return nodes;
+}
diff --git a/src/pages/ActivistGuidePage.tsx b/src/pages/ActivistGuidePage.tsx
new file mode 100644
index 00000000..7f67c38b
--- /dev/null
+++ b/src/pages/ActivistGuidePage.tsx
@@ -0,0 +1,71 @@
+import { useSeoMeta } from '@unhead/react';
+import { AlertTriangle, Megaphone } from 'lucide-react';
+
+import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
+import { GuideSectionCard } from '@/components/GuideSectionCard';
+import { PageHeader } from '@/components/PageHeader';
+import { useAppContext } from '@/hooks/useAppContext';
+import { useLayoutOptions } from '@/contexts/LayoutContext';
+import { getActivistGuideSections } from '@/lib/helpContent';
+
+/**
+ * Activist Guide — long-form companion to the Help page.
+ *
+ * Explains how receiving donations works on Agora, why incoming donations
+ * are public, and the main paths for cashing out privately. Linked from
+ * `/help` as one of the two large guide buttons.
+ */
+export function ActivistGuidePage() {
+ const { config } = useAppContext();
+ useLayoutOptions({});
+
+ useSeoMeta({
+ title: `Activist Guide | ${config.appName}`,
+ description: `How to receive donations on ${config.appName} and cash out privately.`,
+ });
+
+ const sections = getActivistGuideSections(config.appName);
+
+ return (
+
+ }
+ backTo="/help"
+ />
+
+
+ {config.appName} is recommended only for above-ground activism. Every donation you
+ receive is recorded publicly on the Bitcoin blockchain and on Nostr. If you or your
+ donors require extreme privacy — including protection from state actors
+ — additional steps are needed to protect yourself and the people supporting you.
+ Read the sections below before accepting donations.
+
+
+
+
+ {/* Short intro */}
+
+ Receiving support on {config.appName} means donors send Bitcoin directly to an address
+ derived from your Nostr key. Here's how it works, and how to move funds privately if
+ you need to.
+
+
+ );
+}
+
+export default ActivistGuidePage;
diff --git a/src/pages/DonorGuidePage.tsx b/src/pages/DonorGuidePage.tsx
new file mode 100644
index 00000000..1edb9e20
--- /dev/null
+++ b/src/pages/DonorGuidePage.tsx
@@ -0,0 +1,69 @@
+import { useSeoMeta } from '@unhead/react';
+import { AlertTriangle, HandHeart } from 'lucide-react';
+
+import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
+import { GuideSectionCard } from '@/components/GuideSectionCard';
+import { PageHeader } from '@/components/PageHeader';
+import { useAppContext } from '@/hooks/useAppContext';
+import { useLayoutOptions } from '@/contexts/LayoutContext';
+import { getDonorGuideSections } from '@/lib/helpContent';
+
+/**
+ * Donor Guide — long-form companion to the Help page.
+ *
+ * Explains how on-chain donations on Agora work, why they are publicly
+ * visible, and what a donor can do if they need privacy. Linked from
+ * `/help` as one of the two large guide buttons.
+ */
+export function DonorGuidePage() {
+ const { config } = useAppContext();
+ useLayoutOptions({});
+
+ useSeoMeta({
+ title: `Donor Guide | ${config.appName}`,
+ description: `How donating works on ${config.appName} and how to protect your privacy.`,
+ });
+
+ const sections = getDonorGuideSections(config.appName);
+
+ return (
+
+ }
+ backTo="/help"
+ />
+
+
+ {config.appName} is recommended only for supporting above-ground activism. Your
+ donation is public on the Bitcoin blockchain and on Nostr. If you need extreme
+ privacy — including protection from state actors — additional steps are
+ required before donating. Read the sections below first.
+
+
+
+
+ {/* Short intro */}
+
+ Supporting an activist on {config.appName} means sending real Bitcoin on-chain. Here's
+ how it works, and how to do it privately if you need to.
+
+
+ );
+}
+
+export default DonorGuidePage;
diff --git a/src/pages/HelpPage.tsx b/src/pages/HelpPage.tsx
index fc5241aa..28dd2dfe 100644
--- a/src/pages/HelpPage.tsx
+++ b/src/pages/HelpPage.tsx
@@ -1,7 +1,8 @@
import { useSeoMeta } from '@unhead/react';
-import { HelpCircle, Shield } from 'lucide-react';
+import { AlertTriangle, ChevronRight, HandHeart, HelpCircle, Megaphone, Shield } from 'lucide-react';
import { Link } from 'react-router-dom';
+import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { useAppContext } from '@/hooks/useAppContext';
import { useLayoutOptions } from '@/contexts/LayoutContext';
import { PageHeader } from '@/components/PageHeader';
@@ -14,21 +15,54 @@ export function HelpPage() {
useSeoMeta({
title: `Help | ${config.appName}`,
- description: `Get help with ${config.appName} — Nostr 101, FAQs, and support`,
+ description: `Get help with ${config.appName} — donor and activist guides, FAQs, and support`,
});
return (
} />
+ {/* Top-of-page disclaimer: first thing visitors see */}
+
+
+
+ Read this first
+
+
+ {config.appName} is recommended only for above-ground activism. Every donation
+ — given or received — is public on the Bitcoin blockchain and on Nostr. If
+ you or your donors require extreme privacy, including from state actors, additional
+ steps are required to protect yourself. Read the Donor Guide and{' '}
+ Activist Guide below before participating.
+
+
+
+
+
+ {/* Two large guide buttons */}
+
+ }
+ title="Donor Guide"
+ description="How to support activists privately and safely."
+ />
+ }
+ title="Activist Guide"
+ description="Receiving donations and cashing out privately."
+ />
+
- Everything you need to know about Nostr, {config.appName}, and how it all works.
+ Everything else you need to know about Nostr, {config.appName}, and how it all works.