diff --git a/snapshot.js b/snapshot.js
index 97a2499..26520bc 100644
--- a/snapshot.js
+++ b/snapshot.js
@@ -112,16 +112,37 @@ async function tick() {
async function preview() { const sc = X.siteConfig(); return compose(await gather(), sc.telegramCtaUrl); }
-// ---- membership milestones (Marty, 2026-09-19): one celebratory post per mark, the moment it is crossed
-const MILESTONES = [500, 1000, 2500, 5000, 10000];
+// ---- membership milestones: one celebratory post per mark, the moment it is crossed.
+// Every 100 members since 2026-09-22 (Marty), with the fuller write-up saved for the round
+// thousands and the half-thousands so the small ones stay a quick cheer, not a repeated speech.
+const STEP = 100;
+const isBig = m => m % 500 === 0;
+const marksUpTo = count => { const out = []; for (let m = STEP; m <= count; m += STEP) out.push(m); return out; };
+const MILESTONES = marksUpTo(10000); // kept for callers that want the ladder
+
function composeMilestone(mark, s, cta) {
- const L = [];
- L.push('\u{1F389} ' + n(mark) + ' members on InstantAdPay.');
- L.push(n(mark) + ' people have joined, and here is what that looks like right now: ' + n(s.wallets) + ' wallets linked, ' + n(s.payoutsOn) + ' with payouts switched on, ' + n(s.lifeBuys) + ' purchases, and ' + pol(s.lifePaidWei) + ' POL paid to members in the same transactions the purchases came from. Every one of those payments is on the public ledger.');
- L.push('Thank you to every member who brought someone in. That is the whole engine, and it is working.');
- L.push('The next ' + n(mark) + ' start today.');
- L.push('Live ledger' + (cta ? ' \u00b7 Join free' : ''));
- return L.join('\n\n');
+ const ledger = 'Live ledger' + (cta ? ' \u00b7 Join free' : '');
+ const B = [];
+ B.push('\u{1F389} ' + n(mark) + ' members on InstantAdPay');
+ if (isBig(mark)) {
+ B.push(n(mark) + ' people have joined. Here is what that looks like right now:');
+ B.push('' + n(s.wallets) + ' wallets linked\n'
+ + '' + n(s.payoutsOn) + ' with payouts switched on\n'
+ + '' + n(s.lifeBuys) + ' purchases\n'
+ + '' + pol(s.lifePaidWei) + ' POL paid to members\nin the same transactions');
+ B.push('Every one of those payments is on the public ledger.');
+ B.push('Thank you to every member who brought someone in. That is the whole engine, and it is working.');
+ B.push('The next ' + n(mark) + ' start today.');
+ } else {
+ B.push('Another hundred through the door.');
+ B.push('' + n(s.wallets) + ' wallets linked\n'
+ + '' + n(s.payoutsOn) + ' with payouts on\n'
+ + '' + pol(s.lifePaidWei) + ' POL paid to members');
+ B.push('Every payment on the public ledger.');
+ B.push('Next stop: ' + n(mark + STEP) + '.');
+ }
+ B.push(ledger);
+ return B.join('\n\n');
}
async function memberCount() {
if (!(X.db && X.db.enabled())) return 0;
@@ -132,7 +153,7 @@ async function milestoneTick() {
const sc = X.siteConfig(); if (!sc.telegramBotToken) return null;
const st = state(); const done = st.milestones || {};
const count = await memberCount();
- const due = MILESTONES.filter(m => count >= m && !done[m]);
+ const due = marksUpTo(count).filter(m => !done[m]);
if (!due.length) return null;
const mark = due[due.length - 1];
const text = composeMilestone(mark, await gather(), sc.telegramCtaUrl);
@@ -147,4 +168,4 @@ async function milestoneTick() {
}
async function previewMilestone(mark) { const sc = X.siteConfig(); return composeMilestone(Number(mark) || 500, await gather(), sc.telegramCtaUrl); }
-module.exports = { init, gather, compose, post, tick, preview, milestoneTick, previewMilestone, memberCount, MILESTONES };
+module.exports = { init, gather, compose, post, tick, preview, milestoneTick, previewMilestone, composeMilestone, memberCount, MILESTONES, STEP };