Commit Graph

51 Commits

Author SHA1 Message Date
martbost a85ee4cacf Login slots: 1,000 credits, at most 3 member slots live; viewer gets an open-in-tab link
Marty (2026-09-19): cap the login-ad slots. About 124 members sign in a day and every live
slot shares those views, so a fourth slot only thins the other three. Member slots only;
house login ads now step aside whenever a member slot is running (JSON: filtered out of the
pool; MySQL: ORDER BY house ASC). The 14 slots already running keep their term; new buys are
refused until one ends. Format label and chatbot updated.

mindfulmojo (email, 2026-09-19): the viewer frames the advertiser's page and both controls
meant "back"; some sites refuse to load inside a frame and signing up is easier in a full tab.
The viewer bar now carries "Open site in a new tab", set from the ad's target URL.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-19 07:46:12 -05:00
martbost b061053ccf Daily rules run on Central time, and the sign-in bonus gets a hard floor
today() was a UTC day, which rolls over at 7 PM Central. Sign in at 6:55 PM and again at
7:05 PM and those were two different days, so the once-per-day sign-in bonus paid twice,
ten minutes apart. On the live ledger 83 of the first 364 sign-in bonuses landed less than
24 hours after the previous one, one pair 24 minutes apart, worth 618 credits.

The same boundary governs the daily-credit claim, the claim streak, each campaign's daily
spend cap and the login-ad daily meter, all of which could turn over twice in one Central
evening.

ctDay() puts every member-facing daily rule on America/Chicago, which tracks CST/CDT on its
own. Reporting slices elsewhere are left alone rather than re-bucketing chart history.

The bonus also records login_ts now and refuses to pay again inside 20 hours, whatever the
day string says. 20 rather than 24 so signing in at the same time each morning never skips.

Safe to deploy between midnight and 7 PM Central, when the two day strings agree.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 05:19:44 -05:00
martbost 0a50d07658 Login ads become a flat 30-day slot
The daily meter charged 100 credits for every day a campaign was shown,
with no end date. It produced 74-day queues, and worse, each new campaign
added 100/day of charges while adding no inventory: on 18 September, 15
campaigns were charged 1,500 credits against a platform that had produced
367 login impressions in its entire life. That had stopped being a pricing
inefficiency and become members paying for delivery that did not exist.

Now: 3,000 credits buys 30 days of unlimited impressions, charged once at
purchase, with no delivery promise — the model Marty wanted, matching how
ClickBaitPays sells theirs. The price is deliberately identical to 30 days
at the old daily rate, so nobody pays more than before; they just get a
known end instead of an open-ended drain.

It reuses the `featured` shape, which was already a flat up-front buy that
runs to `expires`. Both daily meters — MySQL and JSON — now skip any
campaign with an expiry, so a slot can never be metered. The existing
sweep closes it on day 30 and returns anything unspent.

qa/login-slot.mjs, 9 checks, including that the slot price still equals
30 days at the old rate and that neither meter can touch a slot campaign.
Member walk clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 16:18:27 -05:00
martbost d4e2f59f9b Member campaigns now go out as click offers on DripOffers
Third syndication rail. The first two send impressions; this one sends
clicks — a real person picks the offer off the offerwall, goes to the
member's link, and has to stay the dwell time before anyone is paid.

Two things it does that the other rails can't:

- It carries geo-targeted campaigns. DripOffers filters on an explicit
  country list, so a campaign aimed at Tier 1 finally reaches an outside
  audience instead of staying on our own site. Tier-3-only campaigns are
  skipped rather than quietly sent worldwide, because an inclusion list
  can't express "everywhere except the other tiers" and widening it would
  deliver exactly the traffic the owner chose to exclude.
- Delivery is counted, not derived. It reads rows from the platform's
  permanent click ledger. It never computes delivery as ordered minus
  remaining, and pausing never zeroes remaining — that pair is what
  charged members for impressions that never ran on Network Ad Space.

Also fixes two live bugs found while wiring it: the AdRevLinks pause was
nested inside the Network Ad Space check in both the end sweep and
setStatus, so with NAS switched off an ended or paused campaign kept
running on AdRevLinks. Each rail is now checked on its own.

Caps: 1,000 credits minimum, 10,000 clicks maximum per campaign. Not for
cost — Marty owns the platform and these placements are free — but because
the whole site delivers around 2,700 clicks a day, and one campaign
booking 50,000 would sit in the list for weeks.

Inert unless DRIPOFFERS_BRIDGE_URL and _KEY are set. 26 checks green
against the live endpoint; member walk clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 06:27:33 -05:00
martbost dc0f0d056d Syndicate member campaigns to AdRevLinks as Tier 1 popup ads
Second delivery surface for member campaigns, and deliberately the opposite shape to the
Network Ad Space rail: that one is Tier 3 heavy, this one only has prices configured for
US, CA, GB, AU, NZ and UM, so its traffic is Tier 1 by construction.

The AdRevLinks database is not reachable from this server, so rather than opening a
database port to the internet there is a narrow authenticated endpoint on that box which
does the insert locally. Five actions, no general query surface: a leaked key can only
create or remove popup campaigns. The secret lives outside that server's webroot and is
compared in constant time; Apache there strips Authorization, so it travels as
X-Bridge-Key.

adrevlnks.js mirrors nas.js, with two rules carried over from this morning's billing bug:
- DELIVERY IS READ, NEVER DERIVED. status returns the rotator's own per-country counters.
  Nothing is computed from a figure a stop could overwrite, which is exactly what charged
  16 members for undelivered impressions on the NAS side.
- EVERY WRITE IS IDEMPOTENT. Each campaign carries ref "iap:<id>", so a retry after a
  timeout returns the existing campaign instead of booking a second one.

Capped on purpose. That server serves roughly 5,500 popup impressions a DAY in total,
shared by every active campaign, and its rotator favours whichever has delivered least. So
syndicating everything unchecked would starve what is already running, Marty's own ads
included. Hence a credit floor and a per-campaign view cap. Cost is not the reason: he owns
the platform and treats the placements as free. Finite shared inventory is the reason.

Hooked into create, pause/resume and the scheduled end sweep so both networks stay in step.
Inert unless ADREVLNKS_BRIDGE_URL and _KEY are set, and a bridge hiccup can never block a
campaign going live.

qa/adrevlnks-bridge.mjs (15 assertions) drives the REAL endpoint: under-floor campaigns
skipped, full credit value booked, Tier 1 targeting, retry returns the same campaign,
delivery read back, pause/resume mirrored, then deleted and confirmed gone. Creates only
paused campaigns so no live traffic is spent, and leaves nothing behind.

nas-served 8, fraud-allow 12, sponsor-note 5, chatbot-parse 35, qa/run.sh member 0 bugs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 05:53:38 -05:00
martbost cb6da01e1c Stop billing members for syndicated impressions that were never delivered
Network Ad Space has no stop flag. Its serving query only picks rows with remaining>0,
so zeroing that counter is the only way to halt an ad. But delivery is derived as
assigned - remaining, so the moment an ad was stopped it read back as 100% DELIVERED.

That was not only a reporting error. reconcileNas() runs every five minutes, had no
status filter, and CHARGES member credits off that figure. So pausing a campaign, or
ending one, made the next reconcile pass bill the member for the entire unspent budget
as though it had all been served.

Measured on production before the fix: 23 member campaigns across 16 members, every
single one charged to exactly 100% of budget, 11,437 credits in total, against on-site
delivery evidence of roughly 4,700 impressions. A naturally exhausted ad also ends at
remaining=0, so the two cases cannot be told apart after the fact, which is why the
true figure has to be captured before the stop.

Three changes:
- nas.deactivate() now reads the real served count BEFORE zeroing and returns it.
- a new ads.stopNas() helper is the only path to a stop, and it persists that figure as
  the campaign's final delivery. No caller touches nas.deactivate() directly any more.
- reconcileNas() only processes campaigns with status='active'. A stopped ad delivers
  nothing further, so there is never anything legitimate left to charge for.

qa/nas-served.mjs (8 assertions) stubs the NAS layer and drives the real code: pausing
records the true 3,000 rather than the 10,000 allocation, a paused campaign is never
charged afterwards and its figure never jumps to the allocation, and an active campaign
still reconciles and is charged normally so the guard did not break delivery.

fraud-allow 12, sponsor-note 5, chatbot-parse 35, qa/run.sh member 0 bugs.

Historical delivery is not recoverable: the stop overwrote the only record of it.
Refunding the 11,437 credits to the 16 affected members is Marty's call, pending.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 19:52:12 -05:00
martbost 69d672665c Credit activity ledger: every earn and spend logged with a reason, listed under Campaigns
Hugh earned a 10-credit sign-in bonus, then bought a 280-credit extension; earned credits spend
first, so Earned read 0 and the bonus looked lost. There was no record a member could check.
Now every real credit movement writes a credit_log row (JSON store in file mode): welcome, sign-in
bonus with streak, daily claim, inbox reads, video watches, verified visits, milestone/leaderboard
bonuses, partner codes, admin/team grants, AI Copy Engine charges + refunds, flat buys (featured
run, extension, visit pack, more visits), login-ad days, and metered delivery rolled up per
campaign per day; on-chain purchased-credit spends are marked. GET /api/my/credits/activity;
dashboard shows the last 40 under Campaigns (also when there are no campaigns) with a link from
Earn credits. Chatbot canned answer + AI prompt updated; my.js tag bumped.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 08:41:43 -05:00
martbost ffacf741da Extend run / visits top-up: budget = charged + this buy (release parked credits); featured row shows days left
Hugh extended his featured link and his balance fell 1,133 -> 0: the row still carried the 1,400
parked by the old add-credits button, and reactivating it reserved that budget again. Both flat
paths now set budget to spent + accrued + cost. Featured Run column shows "N of M days left"
(Hugh: "says 14 but it's actually 7") and "M days done" after the run.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 08:19:33 -05:00
martbost 28bce75913 Featured links: Extend run (book more days) replaces Buy more views; visit packs top up by visits; campaign rows say paid up front and runs through
Hugh and Michael both added credits to a featured run expecting more days. Featured is a flat
daily buy, so the credits sat unspent until the run ended and their balance read 0 meanwhile.
Now: /api/my/campaigns/:id/extend books extra days after the current run (or from today if it
ended) and charges them at once; top-up refuses featured; a visits top-up buys more visits at the
pack rate, charged now; rows show paid up front + day count + runs through <date> / run ended;
explainer under the campaign table; chatbot canned answer + AI prompt updated; my.js v tag bumped.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 07:38:37 -05:00
martbost 66b3c83d3d Burner: when a campaign's pinned position is dry on-chain, settle the spend from another funded position on the same account (credits are pooled per account)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-15 12:49:16 -05:00
martbost e03dbbffcf Audit: ignore history already refunded and campaigns older than the hourly log; login daily fee capped at remaining budget
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-15 05:45:57 -05:00
martbost 86a7f83874 Login ads: charge the daily fee only on days the ad was actually shown (last_shown_day); featured views counted
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-15 05:26:16 -05:00
martbost 7423347c95 Featured strip: count impressions (one per viewer per campaign per hour) so featured advertisers see views and the by-hour chart
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-15 05:21:58 -05:00
martbost c1f58b2acf Promo toolkit by badge + AI Copy Engine at Surge: posts, DMs, follow-ups, objection replies, emails, stories in the member's name; monthly free allowance by badge (20/60/150), then ad credits per generation; admin usage view
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-14 06:42:55 -05:00
martbost 964a02a342 Achievement badges: personalised badge image auto-posted to the Telegram payments topic and main group on unlock (once per badge), Post to Telegram button, feed toast
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-13 16:55:43 -05:00
martbost 4efba20d4e Earn: video stops when leaving the tab/pane, 'all watched today' message, open earn tokens survive a redeploy; admin member search matches as you type, clearer old-site line; QA earn harness follows the done screen
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-13 06:46:08 -05:00
martbost a6dcd02999 Earn: claim streak (5/7/10, 25 every 7th day), after-set prompts for verified visits and launching a campaign
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 17:30:02 -05:00
martbost cd26161db5 Country-tier targeting for campaigns: DB-IP lite lookup, tier lists in Settings, Show-to tiers on every format, geo-restricted ads kept off the network, per-country serve stats
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-11 12:02:40 -05:00
martbost bf5371ab20 Campaign scheduling (start/end, solo send-from), scheduled NAS push + end sweep, views-by-hour chart
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-11 06:53:53 -05:00
martbost cac78133ca Never charge a campaign past its budget (on-site batches vs network reconcile overlap)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 11:42:46 -05:00
martbost 4487a90d16 Balances: one reservation rule (earned first) + purchased-grade credited pool
- balances(): live campaign budgets are set aside up front, earned pool first then
  purchased, so dashboard numbers never move per impression; new 'in campaigns' line
- earned pool gains a purchase_grade portion for refunds/comps: shows under Purchased
  as 'credited to you' and funds login ads (daily login charge draws on it first)
- viewing credits always spend before credited purchased money
- dashboard/donut/spend banner/earn pane/positions show purchased, credited, earned, in campaigns
- chatbot canned answer + AI prompt: the balance rule

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 11:16:40 -05:00
martbost 38366e0beb Credits: Welcome label shows only the welcome amount; login ads explain they spend purchased credits only
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 10:42:16 -05:00
martbost 6e187c4f41 Campaigns: optional daily cap on banner/text paces spend on-site and hands Network Ad Space one day's allowance at a time
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 10:03:57 -05:00
martbost 7374dc8f1b Credits: reserve live campaign budgets so purchased credits can't be over-committed; burner dry-runs and skips reverting burns; coaching flags directs not bound to you on-chain
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 07:50:59 -05:00
martbost 5652987675 Verified visits: budget derives from the pack count (form has no budget box)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 07:39:27 -05:00
martbost 046464b83e Linked positions (Qualified Start): extra wallets on one account, credits pooled, buy-from picker
- accounts/db: positions table + JSON store (add/list/owner/remove; main-wallet and cross-account guards)
- auth verify: asPosition links a second wallet without touching the session; position wallets can't mint a session
- /api/my/positions (chain-refreshed member ids, buyer counts, credits) + remove
- credits pooled across main + positions on /api/me, dashboard, campaigns; campaign charged to the best-funded position
- My line: own positions listed on level 1 as 'You · position N'
- Buy pane: Qualified Start card + Add a position flow + Buy-from picker with connected-wallet guard
- Wallet pane: Your positions card; chatbot answer updated

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-09 17:18:47 -05:00
martbost 4846af535a Earn credits: ad view dwell defaults to 10 seconds (was 5)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-09 07:58:24 -05:00
martbost 438b1921e9 Admin portal (/admin) + free house ads; POL amounts show two decimals
- /admin: email magic-code sign-in allowlisted to ADMIN_EMAIL, 12h admin
  session (cookie iap.adm, persisted in the volume). Bearer ADMIN_PASSWORD
  API access still works. Member area shows an Admin link for that email.
- House ads: admin places banner/text/login/solo/video/featured/visits
  campaigns owned by house@instantadpay.com that cost nothing; budget is
  only a delivery cap, spend is never charged or burned.
- Admin APIs: overview, all campaigns (+pause/resume any), members
  (+re-point sponsor), reports (+resolve), pending burns, rates/site
  config get+patch, creative upload.
- fmtPol rounds to two decimals everywhere (dashboard, toasts, prices).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-09 05:15:19 -05:00
martbost ae580b966c 125x125 sidebar banner (sized ad serving) + shorts report link
- New 125x125 "square button" banner size; ad serve now filters by width/height,
  and the sidebar slot serves a 125x125 banner (empty until such inventory exists).
- Shorts reel gets a "report this short" link (posts to /api/report-ad).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-07 08:47:17 -05:00
martbost 4a9e4c789e Daily login bonus (streak) + solo-ad budget default
- Daily login bonus: once/day, base 5 credits + gentle streak (+1/day, cap +5)
  for consecutive days; granted after sign-in, toast + cha-ching. New
  login_day/login_streak on earned_credits; /api/my/login-bonus.
- Solo ad: selecting Solo now defaults Budget to 50 credits (5cr x 10 min
  deliveries) so entering 10 isn't rejected after submit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-07 08:19:40 -05:00
martbost 19b7770a4d Video once-per-day dedup + featured gold border & spacing
- Video watch-to-earn: a member can no longer re-earn from the same video the
  same day. New video_seen table (dual-mode); serveVideo excludes today's
  already-watched videos; videowatch double-checks before crediting.
- Featured card: equal top/bottom margin (no longer touches the stat cards) and
  a gold border on the card + the rotating link.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-07 08:13:09 -05:00
martbost a634f83a8e Shorts = portrait-only reel; Watch videos = landscape; badge name in gold
- Videos now carry client-detected pixel dimensions (videoW/videoH) captured at
  campaign create (upload or direct link). serveVideo takes an orientation filter:
  Shorts reel (/shorts) serves portrait (height>width); the Watch videos tab
  serves landscape, including legacy/unknown-dimension videos so nothing is orphaned.
- shorts.js requests the portrait feed and has a client guard that skips any
  landscape video that slips through. Create form shows the detected orientation.
- Achievement badge share image: member name now renders in gold with a dark
  outline (was low-contrast dark ink) and sits centered on the ribbon.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-07 05:41:17 -05:00
martbost cb2542fca5 Verified visits (7th ad format): guaranteed unique human-visit packs
- Buy N verified visits flat up-front; each is a distinct member, dwell + captcha verified
- Verified visits earn sub-tab: open site (new tab), dwell, human-check, earn
- Per-viewer dedup (visit_seen unique), daily cap, completes at N; single-use tokens

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-06 09:21:59 -05:00
martbost f30a462ca8 Featured rotation -> day-slot booking: 10 slots/day, pick your start day, live occupancy
- Per-day occupancy shown in composer (Today 1/10, Tomorrow 2/10, …), full days unpickable
- Book a specific start day + duration; every covered day must have an open slot
- Slot cap turns dilution disclosure into a hard ceiling; featured skips frame-check (new-tab)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-06 09:16:24 -05:00
martbost 37a2b97bfd Featured rotation format: duration-priced link rotation with disclosed dilution
- New 'featured' type: headline + link + 1/2/7 day run, flat up-front price (40cr/day)
- Composer discloses dilution (N links share the rotation, yours makes N+1) before buying
- Overview featured strip shows the live rotation; expires automatically

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-06 08:58:22 -05:00
martbost b25b4c67fe Buy more views: top up any campaign's budget, reactivates spent/paused ones (+NAS extend)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-06 08:53:35 -05:00
martbost 9e05711f4e Achievement badges + milestone credit bonuses
- Idempotent one-time credit bonus per milestone (payouts/firstBuyer/level2/level3): 10/25/50/100 cr
- Overview badges strip (unlockable medals) with canvas share-to-image (username overlaid)
- Newly-unlocked milestones toast the bonus on next dashboard load

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-06 08:43:50 -05:00
martbost 7701a29169 Sponsor to downline messaging: lineage view, daily broadcast (email+inbox), login modal
- Downline resolver (3 levels); email visible for directs only, username+ID deeper
- Broadcast composer (rich editor) to directs or full downline, 1/day cap, sends email + on-site inbox
- Upline messages inbox in My line; unmissable sign-in modal for unread, ack marks read
- messages.js dual-mode store; sponsor_messages table; sanitizeRich reused

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-06 08:39:06 -05:00
martbost 57bba0111a NAS syndication adapter (feature-flagged off): push banner/text to Network Ad Space, reconcile delivery into unified credit pool
- nas.js: direct-MySQL writer mirroring CTB's proven payments+sponsorads path (approved:1, pid 1/2, catid 5, remaining counts DOWN)
- Create-time push for banner/text; pause/resume mirrors to NAS deactivate/topUp
- reconcileNas() 5-min loop: reads served, charges delta credits to the shared budget, stops NAS ad when budget dry
- Campaign row shows IAP + '+N net' network views (differentiated stats)
- Inert unless NAS_DB_* env is set; zero prod impact until enabled
- Also: rename Inbox sub-tab to 'Inbox Ads'

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-06 08:11:32 -05:00
martbost 860aedabf6 Video ads (watch-to-earn): duration-tiered pricing, escape-proof player, per-view charge
- New 'video' campaign type; MP4/WebM upload or direct https link + required-watch tier (10/30/60s → 3/7/12 cr per view; viewer earns 1/2/4)
- Composer: video fields, tier dropdown, live price hint
- Earn credits > Watch videos sub-tab: no-seek player, server-clock watch floor, daily cap, self-exclusion, single-use tokens
- media-src CSP for direct-link/hosted video; video skips frame-check

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-06 08:03:01 -05:00
martbost 49508c2a13 Banner ads: required IAB size picker (width/height stored, shown in row, maps to NAS)
Also lands dormant server-side scaffolding for video ads (type, tiers, validation) — not yet exposed in UI.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-06 07:51:28 -05:00
martbost 1ee0b5626e Viral welcome tour + line banner + wall, milestone stepper, rich solo editor, CTA-gated reads
- Welcome tour (3 levels x 10s) unlocks welcome credits; line banner in Profile; public /wall/<username>
- 'Your next move' redesigned as a milestone stepper
- Solo composer: BV-style rich editor (H2/H3, inline image+video, undo/redo, raw text)
- Solo read reward now requires clicking through to the advertiser, not just dwelling
- Sanitizer: inline media whitelist + script/style stripped whole

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-06 07:30:49 -05:00
martbost 1c31679b6c Login ads as post-signin interstitials: Open Ad in new tab, timer here, Go to Dashboard
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-05 16:28:57 -05:00
martbost 2cc2855488 Rich solo composer: WYSIWYG editor, sanitized HTML bodies, media uploads, CTA labels
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-05 15:38:46 -05:00
martbost f6a3befe09 Onsite solo ads: inbox delivery with read rewards, composer, homepage format live
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-05 15:25:00 -05:00
martbost 15b036b940 Earn viewer v2: full-screen URL visits with countdown, human check, explicit return
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-05 07:23:30 -05:00
martbost e22021c266 Member identity, promo tools, back-office color, seeded inventory
Real people, not numbers: usernames (unique, profile pane to set them),
shown across the ledger, activity, rosters, and the sidebar chip; vanity
invite links (/join/<username>) with late chain binding intact. Promo tools
pane ships Branded Voice share posts and an email swipe personalized with
each member's link. The earn viewer excludes a member's own campaigns, so
nobody earns from their own spend. Back office gains the cyan/violet/amber
accent family over the green base. Three house campaigns seeded so every
surface shows live inventory. Assets v=20260905i.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-05 06:28:17 -05:00
martbost cdeb59ed7f Earn-and-spend credit economy + private rehearsal chain cutover
Members earn credits by attention (spec 8b): daily 5-ad set with dwell
timing and a too-fast guard, then a claimable daily batch. Earned credits
now FUND campaigns: banner/text budgets draw earned-first (free members can
advertise on welcome credits alone), purchased credits and the on-chain
burn queue only cover the remainder; earned-only campaigns pause when the
pool runs dry. New Earn credits section in the member menu with the viewer.
Explorer links degrade gracefully for the private chain (contract page
points at the audited Amoy verification). Site flipped to the anvil
rehearsal chain at rpc.instantadpay.com: unlimited test POL, no more
faucets. E2E: welcome->views->claim->earned-funded campaign->charged
serving, all green. Assets v=20260905d.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-05 05:59:42 -05:00
martbost d5bb74db7a Welcome credits (spec 8b earned pool v1) + official rehearsal rate card
Every account gets welcomeCredits (25, rate-card configurable) via an
idempotent lazy grant on dashboard load, so existing members receive theirs
retroactively. earned_credits table with JSON fallback; Overview stat shows
the purchased/welcome split. Chatbot facts and canned answers synced;
homepage free-membership list mentions the welcome batch. Rate card
promoted from placeholder to decided and persisted in the volume.
Spending integration for the earned pool is the next engine build.
Assets v=20260905b.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-05 05:48:42 -05:00
martbost 6680e154d0 MySQL data layer: accounts, sessions, campaigns, burns (Marty: real concurrency)
Coolify MySQL (instantadpay-db) via DATABASE_URL; db.js bootstraps schema
and one-time imports the volume JSON. accounts/auth/ads are dual-mode: the
MySQL path uses guarded UPDATEs for the concurrent ad-serving hot path;
without DATABASE_URL the JSON stores remain (local dev). All data functions
async; server boots through db.init. Chain index stays a file: it is a
rebuildable cache of the blockchain, which remains the money truth.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-04 14:13:37 -05:00