Blocks Campaign Pack

Blocks & Themes
@wabbit/tome-blocks-campaign-packv0.2.1

Crowdfunding-campaign-page blocks — a hero, a live-progress strip, a reward-tier wall, a terms strip, and a closing band — for a single-page all-or-nothing pledge campaign.

Installnpm install @wabbit/tome-blocks-campaign-pack

Overview

@wabbit/tome-blocks-campaign-pack

Crowdfunding-campaign-page blocks for a single-page, all-or-nothing pledge campaign — promoted from wabbit.com's site-local Campaign* block family into a standalone, sellable Tome block pack (family: block-packs, tier: addon).

What this is

Five blocks that together make up a pledge-campaign landing page: an opening hero, a live-progress strip, a reward-tier wall, a plain-language terms strip, and a closing call to action. Every number and every link is authored on the block — nothing here reads from a database or a payment provider. A consuming site that wants live, database-backed campaign numbers wires its own data source (e.g. @wabbit/tome-crowdfund, an optional peer) and re-authors the numeric fields as the campaign progresses, or builds its own server component around the same render layer.

Blocks

| Slug | Name | Variants | Summary | |---|---|---|---| | campaign-hero | Campaign Hero | ledger, counter, night-launch | Opening band — headline, lede, CTA pair, and a marginalia figure or a live-percent monument (from authored pledged/goal/backers/daysLeft). | | campaign-count | Campaign Count | ledger-strip, counter-tally | Funding-progress strip — a four-figure row over a bar, or a giant backer tally. | | campaign-tiers | Campaign Tiers | wall, board | Reward-tier wall or stacked board — title, price label, description, features, and a CTA link per tier. | | campaign-terms | Campaign Terms | — | A one-to-three-row plain-language terms strip (bold lead-in + body). One design. | | campaign-close | Campaign Close | settle, monument | Closing band — a dark two-line headline + body + CTA, or a compact days-left numeral + CTA. |

Install into an existing Payload project

npm install @wabbit/tome-blocks-campaign-pack

@wabbit/tome-blocks-core and @wabbit/tome-blocks-house are required peers and install automatically (npm 7+/pnpm). Add the blocks you want to an existing blocks field and render them — see @wabbit/tome-blocks-core's "Add Tome blocks to an existing Payload project" for the full walkthrough:

// payload.config.ts
import { campaignHeroBlock, campaignCloseBlock } from '@wabbit/tome-blocks-campaign-pack'
// blocks: [...existingBlocks, campaignHeroBlock.block(), campaignCloseBlock.block()]
// blockComponents.ts
import { renderers as campaignRenderers } from '@wabbit/tome-blocks-campaign-pack/render/register'
import { adaptRenderersForPayload } from '@wabbit/tome-blocks-core/render'
// blockComponents: { ...adaptRenderersForPayload(campaignRenderers) }
@import '@wabbit/tome-blocks-core/styles.css';

Registering everything from scratch instead of adding to an existing field:

// payload.config.ts
import { BlockRegistry, BundleRegistry } from '@wabbit/tome-blocks-core/registry'
import { register } from '@wabbit/tome-blocks-campaign-pack'

const blockRegistry = new BlockRegistry()
const bundleRegistry = new BundleRegistry()

register(blockRegistry, bundleRegistry)

Register renderers once at app startup:

import { registerRenderers } from '@wabbit/tome-blocks-campaign-pack/render/register'

registerRenderers()

| Peer | Range | Required | |---|---|---| | payload | >=3.67.0 | yes | | @payloadcms/richtext-lexical | >=3.67.0 | yes | | react | >=19.0.0 | yes | | react-dom | >=19.0.0 | yes | | @wabbit/tome-blocks-core | >=0.18.0 <1.0.0 | yes | | @wabbit/tome-blocks-house | >=0.3.0 <1.0.0 | yes | | @wabbit/tome-crowdfund | >=0.1.0 | no (optional) |

@wabbit/tome-crowdfund is optional (peerDependenciesMeta) — the blocks never import it; it is listed for sites that pair the pack with live campaign data.

Public API

| Export | Subpath | Description | |---|---|---| | Block descriptors (campaignHeroBlock, campaignCountBlock, campaignTiersBlock, campaignTermsBlock, campaignCloseBlock) + register(blockRegistry, bundleRegistry) | . | Payload block configs and bundle registration (bundle slug campaign-pack) | | CampaignHero, CampaignCount, CampaignTiers, CampaignTerms, CampaignClose | ./render | Render components (legacy self-registering barrel) | | renderers map + registerRenderers() | ./render/register | Explicit renderer registration — prefer this from server/config contexts | | getDemoProps(blockSlug, variant, ctx?) plus per-block demo functions | ./demo | Demo-props dispatcher feeding the auto-gallery route | | campaignPackBlockMeta | ./meta | Payload-free block metadata for a storefront gallery |

Server / client posture

No file in this pack carries a 'use client' directive — every renderer is a static server-safe component that renders the props an author entered, so it works from either a server or a client component tree. Register renderers through ./render/register from payload.config.ts or any Node context.

Ported from wabbit-site-core

Source: wabbit-site-core/src/blocks/{CampaignHero,CampaignCount,CampaignTiers,CampaignTerms,CampaignClose} + src/lib/crowdfund/campaignStats.ts. Every block dropped its Payload campaign/tier relationships, its Local API stats lookups, and (campaign-tiers) its live Stripe checkout call, replacing all three with authored fields — numeric stats props (pledged/goal/backers/daysLeft) and plain ctaLabel/ctaHref link pairs — so every block renders statically with no server dependency. src/lib/campaignStats.ts ports only the two pure formatting helpers (formatUsd, fillStatTemplate) the source file had; its getCampaignStats() database reconciler was dropped entirely (the numbers it computed are now authored, not derived). Styling is retokenized onto @wabbit/tome-ui/@wabbit/tome-blocks-house tokens — see each render/*.module.css file's header for its token-mapping table.

Blocks

campaign-hero

The opening section of a single-page pledge campaign. Three directions: Ledger (solid + outlined caps headline, tilted marginalia figure, cream band), Counter (single accented sentence headline, opposite-tilt figure, cream band), and Night Launch (dark band, inline solid+outline headline, an outlined mega-percent monument in place of the figure, driven by the authored pledged/goal/backers/daysLeft numbers).

When to use
  • The first section of a crowdfunding or pre-order campaign page
  • Any single-page pledge/launch pitch that needs a strong opening statement plus an immediate CTA into the reward tiers
  • A dark, high-contrast opening moment where the funding percent itself is the visual (Night Launch)
Page types
  • landing
  • marketing
How to use

Write Headline — solid line (and, for Ledger/Night Launch, Headline — outlined line). Wrap a phrase in *asterisks* in Lede for an accent run. Set CTA label + CTA link to point at the reward-tier section (e.g. "#tiers") or an external URL. Ledger/Counter show a Marginalia figure (an empty slot renders a labeled placeholder frame, never a blank space) — Night Launch instead reads Pledged/Goal/Backers/Days left to render the percent monument. Choose the direction from the variant picker.

Pairs with
  • campaign-count
  • campaign-tiers
  • campaign-terms
  • campaign-close
Precedes
  • campaign-count
  • campaign-tiers
Avoid when
  • The page is not a single pledge/launch campaign — use a standard marketing hero instead
  • Live, database-backed funding numbers are required — this block renders whatever pledged/goal/backers/daysLeft values are authored on it, not a live feed
Register in

marketing-landing

campaign-count

The progress instrument for a pledge campaign page. Ledger Strip shows pledged/goal/backers/days-left as four figures over a fill bar with an optional rule-line caption. Counter Tally shows one giant backer number beside the bar and a caption. Both support {pledged} {goal} {backers} {daysLeft} {percent} tokens in their caption text, filled from the authored numbers at render.

When to use
  • Below the hero on a crowdfunding or pre-order page, to show progress toward the goal
  • Anywhere a campaign needs to restate its numbers without a live database feed
Page types
  • landing
  • marketing
How to use

Set Pledged, Goal, Backers, and Days left — every other number (percent, the bar fill) derives from these. Ledger Strip also accepts an optional Strip label and a Rule line using {tokens}. Counter Tally accepts a Tally label and a Tally caption using the same tokens. Update the numbers by hand as the campaign progresses; this block renders exactly what is authored.

Pairs with
  • campaign-hero
  • campaign-tiers
Follows
  • campaign-hero
Precedes
  • campaign-tiers
Avoid when
  • A live, auto-updating counter is required — this block has no data source and must be edited by hand
Register in

marketing-landing

campaign-tiers

The "ways to back it" section of a pledge campaign. Wall renders 2-4 tiers as a seam-wall of cards with feature bullets and an optional "recommended" highlight + flag chip. Board renders the same tiers as stacked numbered rows with a single description paragraph. Every tier is fully authored — title, price label, description, features, and its own CTA link — no relationship to a commerce collection.

When to use
  • The reward-tier / pricing section of a crowdfunding or pre-order campaign page
  • Any page presenting 2-4 priced options as a wall of cards or a stacked list
Page types
  • landing
  • marketing
  • pricing
How to use

Add one row per tier: title, price label (e.g. "$45"), description, and feature bullets (Wall) or a single description paragraph (Board). Set CTA label + CTA link per tier — point it at a checkout, waitlist, or contact page. Mark one tier Recommended to highlight its cell/row and show an optional Flag chip. Choose Wall for a side-by-side comparison or Board for a narrower, numbered stack.

Pairs with
  • campaign-hero
  • campaign-count
  • campaign-terms
Follows
  • campaign-count
Precedes
  • campaign-terms
  • campaign-close
Avoid when
  • The tiers need to check inventory or live pricing from a commerce backend — this block renders authored text only
Register in

marketing-landing

campaign-terms

Plain-language reassurance copy for a pledge campaign — charge timing, what happens if the goal is missed, when shipping details are collected, and similar all-or-nothing mechanics — as one to three short statements, each a bold lead-in plus a body sentence.

When to use
  • Anywhere a pledge/pre-order campaign needs to state its charge and refund mechanics plainly
  • A trust-building strip between the reward tiers and the closing CTA
Page types
  • landing
  • marketing
How to use

Add one to three rows, each a bold Lead-in phrase and a Body sentence. Keep each to one idea — this renders as a tight three-column row on wide screens.

Pairs with
  • campaign-tiers
  • campaign-close
Follows
  • campaign-tiers
Precedes
  • campaign-close
Avoid when
  • The terms need to be a full legal document — link to one instead of authoring it here
Register in

marketing-landing

campaign-close

The last section of a pledge campaign page. Settle is a dark full band with a solid+outlined two-line headline, a body paragraph, and a right-aligned CTA. Monument is a compact, centered outlined days-left numeral with a caption and one CTA — a lighter-weight closer for a page that already made its case.

When to use
  • The final section of a crowdfunding or pre-order campaign page, restating the ask
  • A compact urgency closer showing days remaining (Monument)
Page types
  • landing
  • marketing
How to use

Settle: write Headline — solid line, Headline — outlined line, and Body, plus a CTA label + link. Monument: set Days left plus a CTA label + link — no headline/body fields render in this direction.

Pairs with
  • campaign-terms
  • campaign-tiers
Follows
  • campaign-terms
Avoid when
  • The page needs more than one final CTA — use a standard content/CTA block for a secondary ask
Register in

marketing-landing

Exports

  • @wabbit/tome-blocks-campaign-pack
  • @wabbit/tome-blocks-campaign-pack/render
  • @wabbit/tome-blocks-campaign-pack/render/register
  • @wabbit/tome-blocks-campaign-pack/demo
  • @wabbit/tome-blocks-campaign-pack/meta

Changelog

v0.2.1patch

c3468b0: `register()` is now built with blocks-core's `createPackRegistrar`, and media fields take their `relationTo` from `mediaRelation(config)` instead of a local `as CollectionSlug` cast. Behaviour and signatures are unchanged. The `@wabbit/tome-blocks-core` peer floor goes up to `>=0.18.0` because that is the first version exporting the helpers.

  • c3468b0: `register()` is now built with blocks-core's `createPackRegistrar`, and media fields take their `relationTo` from `mediaRelation(config)` instead of a local `as CollectionSlug` cast. Behaviour and signatures are unchanged. The `@wabbit/tome-blocks-core` peer floor goes up to `>=0.18.0` because that is the first version exporting the helpers.
v0.2.0minor

404d325: Tome block packs now install into an existing Payload project the way the README says: one `npm install`, one CSS import, no undocumented steps. Proven by the new fresh-install smoke test (`scripts/blocks-fresh-install-smoke.mjs`) against a brand-new `create-payload-app` website-template site. **Consumers: list `@wabbit/tome-blocks-core` and `@wabbit/tome-ui` in your own `package.json`** if you import from them (npm 7+ and pnpm install required peers automatically, so a fresh `npm install` of a pack already brings them in). - **One shared `blocks-core` per site.** Every pack, `blocks-house` and `blocks-extras` now declare `@wabbit/tome-blocks-core` (and, where used, `-house` / `-extras`) as a required peer with an explicit range instead of a regular dependency, so a site gets exactly one hoisted copy and one adapter registry. - **No more ERESOLVE in plain Payload sites.** `blocks-core` no longer declares `@wabbit/tome-core` or `@wabbit/tome-catalog` (their optional peer graph pulled `better-auth` → `@sveltejs/kit` → `vite@8` against a site's `vite@7`). The `block-bundle` product type still auto-registers when both are installed; new structural types `BlockBundleProductTypeDeps`, `BlockBundleProductTypeRegistryLike`, `RegisterProductTypeHooksLike`. - **Tokens in one line:** `@import '@wabbit/tome-blocks-core/styles.css';` (new export; imports `@wabbit/tome-ui/tokens`). `@wabbit/tome-ui` is now a required peer of `blocks-core`. - **Rich text and images render with no adapter setup.** Built-in defaults render Lexical through `@payloadcms/richtext-lexical/react` and resolve populated Payload uploads; an unpopulated upload id warns once in every environment (previously content vanished silently in production). Registered adapters still win. - **Payload's spread-props convention:** new `adaptRenderersForPayload(renderers)` / `adaptRendererForPayload(Component)` wrap any pack's `renderers` map for a site that renders `<Block {...block} />`. - **Slug collisions with Payload's templates** (`cta`, `banner`, `archive`, `content`, `code`): new `applyBlockSlugOverrides(blocks, overrides)` and `remapRendererSlugs(renderers, overrides)` (`@wabbit/tome-blocks-core/slugOverrides`). Defaults are unchanged; no stored data migrates. - **`blocks-house`** owns `gsap` and `hls.js` as dependencies (previously optional peers that still broke the build when missing), and registers GSAP's `ScrollTrigger` itself before first use. - **Full-bleed bands actually span the grid.** Eight `pinnedBand` blocks (cinema-pack AmbientBand, MediaPanel, PullInterlude, SceneCaption, ScenePlate, ScrubStory, StatementBand; blocks-house FullBleedInterstitial) now declare `grid-column: 1 / -1` at their root as the contract requires. **Visible change:** inside a tome-ui `.grid`, these render edge to edge where they were previously squeezed to content width. - **`@wabbit/tome-ui`:** `.grid` declares `reading-start` / `reading-end` below 768px (aliased to the content column), so blocks placed on the reading column no longer collapse to a sliver on phones. - Every pack README gains an "Install into an existing Payload project" section and a peer table that matches `package.json`; `blocks-core`'s README carries the full walkthrough.

  • 404d325: Tome block packs now install into an existing Payload project the way the README says: one `npm install`, one CSS import, no undocumented steps. Proven by the new fresh-install smoke test (`scripts/blocks-fresh-install-smoke.mjs`) against a brand-new `create-payload-app` website-template site. **Consumers: list `@wabbit/tome-blocks-core` and `@wabbit/tome-ui` in your own `package.json`** if you import from them (npm 7+ and pnpm install required peers automatically, so a fresh `npm install` of a pack already brings them in). - **One shared `blocks-core` per site.** Every pack, `blocks-house` and `blocks-extras` now declare `@wabbit/tome-blocks-core` (and, where used, `-house` / `-extras`) as a required peer with an explicit range instead of a regular dependency, so a site gets exactly one hoisted copy and one adapter registry. - **No more ERESOLVE in plain Payload sites.** `blocks-core` no longer declares `@wabbit/tome-core` or `@wabbit/tome-catalog` (their optional peer graph pulled `better-auth` → `@sveltejs/kit` → `vite@8` against a site's `vite@7`). The `block-bundle` product type still auto-registers when both are installed; new structural types `BlockBundleProductTypeDeps`, `BlockBundleProductTypeRegistryLike`, `RegisterProductTypeHooksLike`. - **Tokens in one line:** `@import '@wabbit/tome-blocks-core/styles.css';` (new export; imports `@wabbit/tome-ui/tokens`). `@wabbit/tome-ui` is now a required peer of `blocks-core`. - **Rich text and images render with no adapter setup.** Built-in defaults render Lexical through `@payloadcms/richtext-lexical/react` and resolve populated Payload uploads; an unpopulated upload id warns once in every environment (previously content vanished silently in production). Registered adapters still win. - **Payload's spread-props convention:** new `adaptRenderersForPayload(renderers)` / `adaptRendererForPayload(Component)` wrap any pack's `renderers` map for a site that renders `<Block {...block} />`. - **Slug collisions with Payload's templates** (`cta`, `banner`, `archive`, `content`, `code`): new `applyBlockSlugOverrides(blocks, overrides)` and `remapRendererSlugs(renderers, overrides)` (`@wabbit/tome-blocks-core/slugOverrides`). Defaults are unchanged; no stored data migrates. - **`blocks-house`** owns `gsap` and `hls.js` as dependencies (previously optional peers that still broke the build when missing), and registers GSAP's `ScrollTrigger` itself before first use. - **Full-bleed bands actually span the grid.** Eight `pinnedBand` blocks (cinema-pack AmbientBand, MediaPanel, PullInterlude, SceneCaption, ScenePlate, ScrubStory, StatementBand; blocks-house FullBleedInterstitial) now declare `grid-column: 1 / -1` at their root as the contract requires. **Visible change:** inside a tome-ui `.grid`, these render edge to edge where they were previously squeezed to content width. - **`@wabbit/tome-ui`:** `.grid` declares `reading-start` / `reading-end` below 768px (aliased to the content column), so blocks placed on the reading column no longer collapse to a sliver on phones. - Every pack README gains an "Install into an existing Payload project" section and a peer table that matches `package.json`; `blocks-core`'s README carries the full walkthrough.
v0.1.4patch

Updated dependencies [e044594] - @wabbit/tome-blocks-core@0.16.6

  • Updated dependencies [e044594] - @wabbit/tome-blocks-core@0.16.6
v0.1.3patch

Updated dependencies [a2f2dfa] - @wabbit/tome-blocks-house@0.3.0

  • Updated dependencies [a2f2dfa] - @wabbit/tome-blocks-house@0.3.0
v0.1.2patch

Updated dependencies [7850b7a] - @wabbit/tome-blocks-house@0.2.0

  • Updated dependencies [7850b7a] - @wabbit/tome-blocks-house@0.2.0
v0.1.1patch

f49947a: Demo props now carry `_variant`, so gallery thumbs render the requested variant (the `monument` close rendered blank; hero/count/tiers variants rendered their defaults).

  • f49947a: Demo props now carry `_variant`, so gallery thumbs render the requested variant (the `monument` close rendered blank; hero/count/tiers variants rendered their defaults).