Blocks Industrial Theme
Blocks & themesStableIndustrial treatment for Tome catalog/LMS blocks — safety-yellow accents, charcoal type, motorsport-italic chrome. Opt-in via [data-tome-theme='industrial'] + one stylesheet import; restyles the neutral pack blocks through their public hooks without forking them.
Get a registry token from your credentials page. You need a purchase that includes this package, or a Craft Library membership.
Add the registry and your token to the
.npmrcat the root of your project, with your token in place ofYOUR_TOKEN:@wabbit:registry=https://npm.wabbit.com/ //npm.wabbit.com/:_authToken=YOUR_TOKENThen install:
npm install @wabbit/tome-blocks-industrial-theme
Overview
@wabbit/tome-blocks-industrial-theme
Industrial theme for Tome catalog/LMS blocks — safety-yellow accents, charcoal type, motorsport-italic chrome. An opt-in, sellable brand treatment in the same spirit as @wabbit/tome-blocks-signal-theme: the flavor is the product.
This package is a treatment (a ThemeManifest of kind: 'treatment'): it owns no blocks and no palette. It restyles the neutral pack blocks through their public .tome-* BEM hooks, scoped under [data-tome-theme='industrial'] — so it wins over the packs' neutral CSS modules on specificity regardless of stylesheet load order, and can be applied to a whole page or a single section.
Layer: blocks · Family / tier: block-packs / addon · Versioning: independent (not part of the changeset-linked blocks-* release family — this package changes when the brand treatment changes, not when block schemas do).
Install
pnpm add @wabbit/tome-blocks-industrial-theme| Peer | Range | Required | |---|---|---| | @wabbit/tome-core | >=1.20.0 <2.0.0 | no (optional) |
@wabbit/tome-core is needed only for the ./register subpath (theme registry) and the ThemeManifest type; the stylesheet works without it. The package does not import React or Payload. It only does anything useful next to the packs whose blocks it restyles — install at least one of @wabbit/tome-blocks-lms-pack or @wabbit/tome-blocks-catalog-pack.
Usage
Import the stylesheet once at the app root, then opt a container in with the data attribute:
// app/layout.tsx (or any app-root module)
import '@wabbit/tome-blocks-industrial-theme/styles.css'<!-- whole site -->
<body data-tome-theme="industrial">
<!-- or one section -->
<section data-tome-theme="industrial">…</section>The theme layers on top of the packs' own styling, so the packs must already render correctly first: the @import '@wabbit/tome-blocks-core/styles.css'; token line and RenderBlocks with the tome-ui grid, per @wabbit/tome-blocks-core's README section Add Tome blocks to an existing Payload project. Import order between that token stylesheet, the packs' CSS and this stylesheet does not matter.
Nothing outside a [data-tome-theme='industrial'] scope is affected — every rule carries the attribute selector as its first compound, and the theme conformance check in the test suite asserts it, so a future rule cannot leak.
To have @wabbit/tome-admin's theme switcher (and other registry readers) list it, register it on the server before building the Payload config:
import '@wabbit/tome-blocks-industrial-theme/register'A document carries one data-tome-theme value, so a treatment is not combined with a full theme.
Public API
| Export | Subpath | Purpose | |---|---|---| | INDUSTRIAL_THEME (ThemeManifest) | . | The treatment's manifest: name: 'industrial', selector, kind: 'treatment', label, description, packageName, tier: 'addon', palettes: [] and styledBlocks — the block slugs it restyles. | | ThemeMeta (type, deprecated) | . | Alias of ThemeManifest, kept for one minor. | | styles.css | ./styles.css | The complete stylesheet (imports treatments/catalog-lms.css). The only file that does anything visual. | | side effect | ./register | Registers INDUSTRIAL_THEME in @wabbit/tome-core's theme registry. |
import { INDUSTRIAL_THEME } from '@wabbit/tome-blocks-industrial-theme'
INDUSTRIAL_THEME.styledBlocks
// ['course-card', 'product-grid', 'category-strip', 'featured-product', 'product-card', 'price-table']What it styles
| Block | Pack | Treatment | |---|---|---| | course-card | blocks-lms-pack | White card, hairline border, zero radius, italic charcoal type, uppercase meta, yellow CTA with charcoal hover-invert, yellow featured strip | | product-grid | blocks-catalog-pack | Italic charcoal headings/titles/prices, uppercase badges, yellow CTAs | | category-strip | blocks-catalog-pack | Italic charcoal headings and uppercase rail labels | | featured-product, product-card, price-table | blocks-catalog-pack | Coherence extensions: accent CTAs/badges, zero-radius surfaces |
It also feeds the @wabbit/tome-lms-ui course-catalog CTA hooks (--lms-color-primary-cta / --lms-color-on-primary) inside the theme scope.
Palette
Defined as --industrial-* custom properties on the theme scope. Each prefers a legacy --pc-* token when the consumer defines one (continuity for existing industrial-lineage sites), falling back to the canonical values: accent #ffd204, charcoal #231f20, mid gray #676767, light gray #f1f1f2, border #c9c9c9, divider #8d8d8d, card surface #fff (--industrial-surface, from --pc-white).
Literal colour values appear only in those private --industrial-* declarations (the brand palette is what it sells); every styling declaration reads a token through var(). The conformance check fails on a raw colour literal anywhere else. The blocks themselves stay tokens-only.
Server / client posture
Pure CSS plus a constant. There are no React components and no 'use client' files, so the package is inert under React Server Components and adds nothing to any client bundle beyond the stylesheet the consumer chose to import.
Decisions that shaped this package
- Extract, don't delete. The styling shipped inside
blocks-lms-packandblocks-catalog-packuntil those packs were neutralised onto the--tome-*token system (2026-07-18). Following the signal-theme precedent, the brand treatment was moved into its own addon-tier package rather than dropped. - Attribute scope over load order. Scoping every rule under
[data-tome-theme='industrial']gives the theme higher specificity than the packs' neutral CSS modules, so it wins regardless of import order and can be applied per section. - Legacy token bridge. The
--pc-*fallbacks keep existing industrial-lineage consumer sites rendering identically while they migrate to the canonical palette.
Tests
pnpm --filter @wabbit/tome-blocks-industrial-theme testSmoke tests (node:test): the manifest is a valid treatment and its tier matches package.json, every claimed block has a scoped rule, ./register registers it, and the theme conformance check (scoping, public hooks only, no raw colour literals) passes.
Exports
@wabbit/tome-blocks-industrial-theme@wabbit/tome-blocks-industrial-theme/styles.css@wabbit/tome-blocks-industrial-theme/register
Changelog
775f90a: Published packages now contain compiled JavaScript and type declarations under a one-line licence banner, and no longer include source maps. What you install: one compiled `.js` (ESM) and `.cjs` (CommonJS) file per source module, its `.d.ts` / `.d.cts` declarations, and the stylesheets, fonts and other assets a package already shipped. Every JavaScript module opens with a comment naming the package and its licence: `/*! @wabbit/<package> — © Wabbit, LLC. Wabbit Tome Commercial License (see LICENSE.md). Not for redistribution. */`. The `.map` files and the `sourceMappingURL` comments that pointed at them are gone, which roughly halves the size of each tarball. Debugging: the code is still unbundled and unminified, one readable file per module, so a stack trace points at real code with real names. Line numbers in a stack trace are one higher than before, because of the banner line. A `'use client'` directive stays the first statement of its module (the banner is a comment above it), so React Server Component boundaries are unchanged. No API change, no runtime behaviour change, and nothing to do on upgrade. In `@wabbit/tome-blocks-gallery`, the source snapshots `extractGallerySource` writes from an installed pack leave out the licence banner line, so a component or config snapshot starts at the code and a paid block's preview shows its first 15 lines of real code.
- 775f90a: Published packages now contain compiled JavaScript and type declarations under a one-line licence banner, and no longer include source maps. What you install: one compiled `.js` (ESM) and `.cjs` (CommonJS) file per source module, its `.d.ts` / `.d.cts` declarations, and the stylesheets, fonts and other assets a package already shipped. Every JavaScript module opens with a comment naming the package and its licence: `/*! @wabbit/<package> — © Wabbit, LLC. Wabbit Tome Commercial License (see LICENSE.md). Not for redistribution. */`. The `.map` files and the `sourceMappingURL` comments that pointed at them are gone, which roughly halves the size of each tarball. Debugging: the code is still unbundled and unminified, one readable file per module, so a stack trace points at real code with real names. Line numbers in a stack trace are one higher than before, because of the banner line. A `'use client'` directive stays the first statement of its module (the banner is a comment above it), so React Server Component boundaries are unchanged. No API change, no runtime behaviour change, and nothing to do on upgrade. In `@wabbit/tome-blocks-gallery`, the source snapshots `extractGallerySource` writes from an installed pack leave out the licence banner line, so a component or config snapshot starts at the code and a paid block's preview shows its first 15 lines of real code.
382f36c: **BREAKING:** `INDUSTRIAL_THEME` is now a `ThemeManifest` (`kind: 'treatment'`), and a new `./register` subpath adds it to the Tome theme registry. **Migration:** read `name` instead of `themeAttribute` (both `'industrial'`) and `label` instead of `name`; `slug` and `tags` are gone (the package keywords carry the tags). `styledBlocks` is unchanged. `ThemeMeta` is a deprecated alias of `ThemeManifest` for one minor. To list the treatment in the admin theme switcher, add `import '@wabbit/tome-blocks-industrial-theme/register'` on the server (needs the optional `@wabbit/tome-core` `>=1.20.0` peer). - The stylesheet moved to `treatments/catalog-lms.css`; `./styles.css` imports it, so the import path is unchanged. - The course card's white background now reads a private `--industrial-surface` token (from `--pc-white`, default `#fff`) instead of a hard-coded literal; rendering is unchanged unless a site defines `--pc-white`. - The test suite runs the theme conformance check for treatments: every selector scoped to the theme attribute, public hooks only, and no raw colour literal outside the private `--industrial-*` tokens.
- 382f36c: **BREAKING:** `INDUSTRIAL_THEME` is now a `ThemeManifest` (`kind: 'treatment'`), and a new `./register` subpath adds it to the Tome theme registry. **Migration:** read `name` instead of `themeAttribute` (both `'industrial'`) and `label` instead of `name`; `slug` and `tags` are gone (the package keywords carry the tags). `styledBlocks` is unchanged. `ThemeMeta` is a deprecated alias of `ThemeManifest` for one minor. To list the treatment in the admin theme switcher, add `import '@wabbit/tome-blocks-industrial-theme/register'` on the server (needs the optional `@wabbit/tome-core` `>=1.20.0` peer). - The stylesheet moved to `treatments/catalog-lms.css`; `./styles.css` imports it, so the import path is unchanged. - The course card's white background now reads a private `--industrial-surface` token (from `--pc-white`, default `#fff`) instead of a hard-coded literal; rendering is unchanged unless a site defines `--pc-white`. - The test suite runs the theme conformance check for treatments: every selector scoped to the theme attribute, public hooks only, and no raw colour literal outside the private `--industrial-*` tokens.
917b77a: CSS files are copied to `dist/` by a post-build script instead of a tsup `onSuccess` hook, whose output the type-declaration phase can remove.
- 917b77a: CSS files are copied to `dist/` by a post-build script instead of a tsup `onSuccess` hook, whose output the type-declaration phase can remove.
0836ef5: dist now raw-Node loadable: relative specifiers get explicit extensions post-build. `build` gains `&& node ../../scripts/fix-dist-extensions.mjs --strict` as its last step, joining the 13 packages that already ran it. tsup builds `bundle: false` and emits relative specifiers exactly as the TypeScript source wrote them — extensionless — which bundlers resolve and raw Node does not (ESM `ERR_MODULE_NOT_FOUND`; CJS worse, `require('./x')` finds the ESM `.js` twin and Node 22+ `require(esm)` then dies on that file's own extensionless import). Every consumer outside a bundler hit this: the payload CLI under plain node, `generate:types`, `generate:importmap`, ops scripts, codegen tools. No source changes, no API changes, and bundler consumers are unaffected — extensioned relative specifiers are universally resolvable. Two supporting changes made the wiring possible, both in repo scripts rather than package source. `fix-dist-extensions.mjs` now skips bundler-asset specifiers (`.css`, `.module.css`, `.scss`, fonts, images, shaders) by explicit extension allowlist instead of reporting them as unresolvable — that single gap is why the 13 prior adopters were exactly the 13 packages that ship no CSS, since `--strict` exited 1 on any package with a relative stylesheet import. Dotted MODULE names (`./config.meta`, `./x.variants`, `./y.demo`) are deliberately NOT treated as assets and still get `.js`/`.cjs` appended. `assert-node-loadable.mjs` gained the matching carve-outs so the new repo-wide CI gate reports real defects only: a resolution failure whose path lands under `node_modules` is a peer SKIP (next@15 has no exports map, so `next/image` fails as an absolute path), and a bundler-asset load failure is an environmental SKIP (CJS surfaces it as `SyntaxError: Unexpected token '.'` raised from inside the stylesheet). Verified before/after on four packages built one at a time: print 8 FAIL → 0, readout 22 FAIL → 0, ai 3 FAIL → 0, gamification 2 FAIL → 0 (its failure was the other signature — a `directory import` missing `/index`). cop was already clean on a fresh build, so the audit's "27 of 46 fail" figure includes at least one package whose local dist was merely stale.
- 0836ef5: dist now raw-Node loadable: relative specifiers get explicit extensions post-build. `build` gains `&& node ../../scripts/fix-dist-extensions.mjs --strict` as its last step, joining the 13 packages that already ran it. tsup builds `bundle: false` and emits relative specifiers exactly as the TypeScript source wrote them — extensionless — which bundlers resolve and raw Node does not (ESM `ERR_MODULE_NOT_FOUND`; CJS worse, `require('./x')` finds the ESM `.js` twin and Node 22+ `require(esm)` then dies on that file's own extensionless import). Every consumer outside a bundler hit this: the payload CLI under plain node, `generate:types`, `generate:importmap`, ops scripts, codegen tools. No source changes, no API changes, and bundler consumers are unaffected — extensioned relative specifiers are universally resolvable. Two supporting changes made the wiring possible, both in repo scripts rather than package source. `fix-dist-extensions.mjs` now skips bundler-asset specifiers (`.css`, `.module.css`, `.scss`, fonts, images, shaders) by explicit extension allowlist instead of reporting them as unresolvable — that single gap is why the 13 prior adopters were exactly the 13 packages that ship no CSS, since `--strict` exited 1 on any package with a relative stylesheet import. Dotted MODULE names (`./config.meta`, `./x.variants`, `./y.demo`) are deliberately NOT treated as assets and still get `.js`/`.cjs` appended. `assert-node-loadable.mjs` gained the matching carve-outs so the new repo-wide CI gate reports real defects only: a resolution failure whose path lands under `node_modules` is a peer SKIP (next@15 has no exports map, so `next/image` fails as an absolute path), and a bundler-asset load failure is an environmental SKIP (CJS surfaces it as `SyntaxError: Unexpected token '.'` raised from inside the stylesheet). Verified before/after on four packages built one at a time: print 8 FAIL → 0, readout 22 FAIL → 0, ai 3 FAIL → 0, gamification 2 FAIL → 0 (its failure was the other signature — a `directory import` missing `/index`). cop was already clean on a fresh build, so the audit's "27 of 46 fail" figure includes at least one package whose local dist was merely stale.
- 637db74: README brought to the package contract (install, Public API, posture, decisions) and a node:test smoke suite added (descriptor/manifest tier agreement, every claimed block has a scoped rule, no selector escapes the theme scope). Closes the two CI gate failures the package shipped with.
- 73081e6: Manifest metadata: `homepage`, `bugs`, `engines`. All 46 publishable manifests were missing the three fields a consumer sees before any code (2026-09-01 sale-readiness audit §6). Metadata only — no source, no build, no runtime change. - `homepage` deep-links to that package README on GitHub (`.../tree/main/packages/<dir>#readme`). Without it a registry page links to the monorepo root and the reader has to guess which of 46 folders they want. - `bugs.url` points at the repo issue tracker, so a paying customer has a place to report a defect that is not email. - `engines.node` is `>=22`, matching the root `engines` and `.nvmrc` set the same day. This is a real floor, not decoration: CI on Node 20 could not expand the glob the block packs use for `node --test`, and a package installed on Node 20 fails at a runtime the installer cannot connect back to the version. The forcing function ships with the change: `scripts/assert-manifest-metadata.mjs` (root `pnpm assert:manifest-metadata`, wired into `platform-discipline.yml` beside `assert:license-metadata`) fails when any publishable manifest lacks `description`, `repository.directory` matching its own folder, `homepage`, `bugs`, `engines.node` equal to the repo floor, `license`, `files` or `sideEffects`. It reported 138 violations before this change and 0 after.
Initial release: industrial theme pack — safety-yellow accents, charcoal type, motorsport-italic chrome for catalog/LMS blocks. Extracted from the client-era styling that used to ship inside blocks-lms-pack (CourseCard) and blocks-catalog-pack (ProductGrid, CategoryStrip). Owns no blocks; restyles the neutral packs through their public .tome-_ BEM hooks, scoped under [data-tome-theme='industrial'] so it wins on specificity regardless of stylesheet order and can be applied per-section. Prefers legacy --pc-_ tokens when the consumer defines them.
- Initial release: industrial theme pack — safety-yellow accents, charcoal type, motorsport-italic chrome for catalog/LMS blocks. Extracted from the client-era styling that used to ship inside blocks-lms-pack (CourseCard) and blocks-catalog-pack (ProductGrid, CategoryStrip). Owns no blocks; restyles the neutral packs through their public .tome-_ BEM hooks, scoped under [data-tome-theme='industrial'] so it wins on specificity regardless of stylesheet order and can be applied per-section. Prefers legacy --pc-_ tokens when the consumer defines them.