ultimate-jekyll-manager 1.7.2 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/scheduled_tasks.lock +1 -0
- package/CHANGELOG.md +61 -1
- package/CLAUDE.md +36 -15
- package/README.md +4 -2
- package/TODO-AUTH-TESTING.md +1 -1
- package/dist/assets/themes/newsflash/README.md +58 -0
- package/dist/assets/themes/newsflash/_config.scss +138 -0
- package/dist/assets/themes/newsflash/_theme.js +27 -0
- package/dist/assets/themes/newsflash/_theme.scss +37 -0
- package/dist/assets/themes/newsflash/css/base/_mixins.scss +50 -0
- package/dist/assets/themes/newsflash/css/base/_root.scss +134 -0
- package/dist/assets/themes/newsflash/css/base/_typography.scss +49 -0
- package/dist/assets/themes/newsflash/css/base/_utilities.scss +58 -0
- package/dist/assets/themes/newsflash/css/components/_badges.scss +65 -0
- package/dist/assets/themes/newsflash/css/components/_buttons.scss +139 -0
- package/dist/assets/themes/newsflash/css/components/_cards.scss +52 -0
- package/dist/assets/themes/newsflash/css/components/_editorial.scss +182 -0
- package/dist/assets/themes/newsflash/css/components/_forms.scss +75 -0
- package/dist/assets/themes/newsflash/css/components/_infinite-scroll.scss +102 -0
- package/dist/assets/themes/newsflash/css/components/_panels.scss +91 -0
- package/dist/assets/themes/newsflash/css/components/_ticker.scss +70 -0
- package/dist/assets/themes/newsflash/css/layout/_general.scss +264 -0
- package/dist/assets/themes/newsflash/css/layout/_navigation.scss +164 -0
- package/dist/assets/themes/newsflash/js/initialize-tooltips.js +20 -0
- package/dist/assets/themes/newsflash/js/masthead-scroll.js +29 -0
- package/dist/assets/themes/newsflash/pages/404/index.scss +27 -0
- package/dist/assets/themes/newsflash/pages/about/index.scss +70 -0
- package/dist/assets/themes/newsflash/pages/blog/index.scss +17 -0
- package/dist/assets/themes/newsflash/pages/blog/post.js +29 -0
- package/dist/assets/themes/newsflash/pages/blog/post.scss +164 -0
- package/dist/assets/themes/newsflash/pages/index.scss +159 -0
- package/dist/assets/themes/newsflash/pages/pricing/index.scss +194 -0
- package/dist/assets/themes/newsflash/pages/test/libraries/layers/index.js +9 -0
- package/dist/assets/themes/newsflash/pages/test/libraries/layers/index.scss +7 -0
- package/dist/commands/blogify.js +6 -3
- package/dist/commands/test.js +34 -5
- package/dist/defaults/CLAUDE.md +17 -4
- package/dist/defaults/dist/_includes/core/pricing/resolve-plan.html +59 -0
- package/dist/defaults/dist/_includes/themes/classy/frontend/sections/footer.html +20 -3
- package/dist/defaults/dist/_layouts/themes/classy/admin/core/minimal-viewport-locked.html +1 -1
- package/dist/defaults/dist/_layouts/themes/classy/admin/core/minimal.html +1 -1
- package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/pricing.html +5 -40
- package/dist/defaults/dist/_layouts/themes/neobrutalism/frontend/pages/pricing.html +33 -34
- package/dist/defaults/dist/_layouts/themes/newsflash/frontend/core/base.html +61 -0
- package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/404.html +86 -0
- package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/about.html +353 -0
- package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/blog/categories/category.html +105 -0
- package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/blog/categories/index.html +93 -0
- package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/blog/index.html +373 -0
- package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/blog/post.html +289 -0
- package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/blog/tags/index.html +90 -0
- package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/blog/tags/tag.html +107 -0
- package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/contact.html +340 -0
- package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/index.html +522 -0
- package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/pricing.html +485 -0
- package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/team/index.html +207 -0
- package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/team/member.html +134 -0
- package/dist/defaults/test/README.md +4 -0
- package/dist/gulp/tasks/jekyll.js +4 -2
- package/dist/test/runner.js +50 -3
- package/dist/test/suites/build/attach-log-file.test.js +102 -0
- package/dist/test/suites/build/theme-contract.test.js +173 -0
- package/dist/test/utils/extended-mode-warning.js +13 -0
- package/dist/utils/attach-log-file.js +70 -43
- package/docs/appearance.md +1 -0
- package/docs/assets.md +9 -0
- package/docs/audit.md +27 -7
- package/docs/build-system.md +57 -0
- package/docs/common-mistakes.md +15 -0
- package/docs/{project-structure.md → directory-structure.md} +1 -1
- package/docs/environment-detection.md +1 -1
- package/docs/javascript-libraries.md +38 -1
- package/docs/layouts-and-pages.md +146 -0
- package/docs/local-development.md +1 -8
- package/docs/logging.md +30 -0
- package/docs/migration.md +131 -0
- package/docs/no-inline-scripts.md +304 -0
- package/docs/purgecss.md +164 -0
- package/docs/seo.md +131 -4
- package/docs/templating.md +23 -0
- package/docs/test-boot-layer.md +1 -1
- package/docs/test-framework.md +56 -8
- package/docs/themes.md +254 -13
- package/logs/test.log +111 -0
- package/package.json +1 -1
package/docs/themes.md
CHANGED
|
@@ -16,6 +16,14 @@ Shipped themes live in [src/assets/themes/](../src/assets/themes/):
|
|
|
16
16
|
fallback source** (see below).
|
|
17
17
|
- **`neobrutalism/`** — bold high-contrast theme (hard borders, offset shadows,
|
|
18
18
|
zero radius). A worked example of a second theme.
|
|
19
|
+
- **`newsflash/`** — editorial news-site theme (paper + ink + vermilion, serif
|
|
20
|
+
headlines, live ticker, reading progress). The worked example of a
|
|
21
|
+
**genre-specific** theme: news-native frontmatter defaults, news-purposed
|
|
22
|
+
homepage sections (`latest`, `rundown`, `desks`, `more_stories`), membership-tier pricing, desk/topic
|
|
23
|
+
archives (blog categories + tags), and a newsroom masthead (team + reporter
|
|
24
|
+
profile pages). Functional pages (download, feedback, updates, auth, account)
|
|
25
|
+
intentionally ride the classy fallback — their structure is the feature, and
|
|
26
|
+
the theme CSS restyles them.
|
|
19
27
|
- **`_template/`** — a copy-paste starter for new themes (the `_` prefix excludes
|
|
20
28
|
it from selection).
|
|
21
29
|
|
|
@@ -89,13 +97,147 @@ adopts your look. Override a layout only when the **structure itself** must diff
|
|
|
89
97
|
The `neobrutalism` theme demonstrates both. It restyles classy's markup everywhere
|
|
90
98
|
EXCEPT the homepage and pricing page, where it ships genuinely different structure
|
|
91
99
|
(asymmetric split hero, offset showcase rows, oversized color-block stats) at
|
|
92
|
-
`_layouts/themes/neobrutalism/frontend/pages/{index,pricing}.html`.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
100
|
+
`_layouts/themes/neobrutalism/frontend/pages/{index,pricing}.html`. When you
|
|
101
|
+
override a page layout, **preserve any data-resolution Liquid** (the pricing
|
|
102
|
+
product-matching, paginator loops, `uj_post`/`uj_member` resolution) and keep the
|
|
103
|
+
**universal section keys** (`hero`, `cta`, `stats`, `faqs`, `testimonials`,
|
|
104
|
+
`newsletter`, `trusted_by`, `pricing.plans`) so a consumer's existing page
|
|
105
|
+
frontmatter keeps working across theme swaps — but write your own defaults and
|
|
106
|
+
structure for everything else (next section).
|
|
107
|
+
|
|
108
|
+
#### Frontmatter defaults are part of the theme's identity
|
|
109
|
+
|
|
110
|
+
Every theme page layout ships **default frontmatter** that renders when a
|
|
111
|
+
consumer page doesn't override it. Those defaults are not filler — they are the
|
|
112
|
+
theme's out-of-the-box voice, and **they MUST be written for the theme's genre,
|
|
113
|
+
NOT copied from classy.** Themes serve different purposes: classy/neobrutalism
|
|
114
|
+
are SaaS-product themes, `newsflash` is a news-site theme. A new theme's default
|
|
115
|
+
copy, section names, and demo data should read like the kind of site the theme
|
|
116
|
+
is for.
|
|
117
|
+
|
|
118
|
+
Concretely, when authoring a theme's page layouts:
|
|
119
|
+
|
|
120
|
+
1. **Write genre-native default values for every key.** A news theme's homepage
|
|
121
|
+
hero pitches the publication ("News with a pulse", "Read the latest" →
|
|
122
|
+
`/blog`), its pricing page sells reader memberships (`Reader` / `Supporter` /
|
|
123
|
+
`Insider` tiers funding the journalism), its contact page has a tips line and
|
|
124
|
+
a corrections subject — not "Technical support" and "API access". See
|
|
125
|
+
`_layouts/themes/newsflash/frontend/pages/*.html` for the reference example.
|
|
126
|
+
2. **Keep universal keys universal.** Concepts that exist on any site keep the
|
|
127
|
+
shared names — `hero`, `cta`, `stats`, `faqs`, `testimonials`, `newsletter`,
|
|
128
|
+
`trusted_by`, and the `pricing` engine block — so consumer overrides survive
|
|
129
|
+
a theme swap.
|
|
130
|
+
3. **Genre-specific sections get genre-specific keys.** When a section only
|
|
131
|
+
makes sense for the theme's genre, name the key for what it means there
|
|
132
|
+
instead of force-fitting classy's vocabulary: newsflash's homepage replaces
|
|
133
|
+
classy's `showcase`/`features` with `latest` (the front-page post feed),
|
|
134
|
+
`rundown` (the newsroom's numbered playbook), `desks` (coverage areas), and
|
|
135
|
+
`more_stories` (extra story tiles). Consumers customizing those sections
|
|
136
|
+
write frontmatter against the active theme's contract — document the keys
|
|
137
|
+
in the layout's frontmatter comments.
|
|
138
|
+
4. **Never invent parallel resolution logic.** Whatever the keys are called,
|
|
139
|
+
the Liquid that resolves posts, members, and pagination is copied from
|
|
140
|
+
classy verbatim — and the plan-pricing math is not even copied: every
|
|
141
|
+
theme's pricing layout calls the shared
|
|
142
|
+
`{% include core/pricing/resolve-plan.html plan=plan %}` (product lookup,
|
|
143
|
+
monthly/annual precedence, per-unit math) and renders the variables it
|
|
144
|
+
assigns (`_plan_monthly`, `_plan_annually`, `monthly_price_per_unit`,
|
|
145
|
+
`annual_price_per_unit`, `_config_product`). Only the data defaults and
|
|
146
|
+
presentation change per theme.
|
|
147
|
+
|
|
148
|
+
#### The pricing page has a JS contract too
|
|
149
|
+
|
|
150
|
+
The pricing page is the one page whose content is **dynamically driven at
|
|
151
|
+
runtime**: the framework page module `src/assets/js/pages/pricing/index.js`
|
|
152
|
+
runs on every theme's `/pricing` and queries the DOM for the billing toggle,
|
|
153
|
+
price swapping, checkout routing, the current-plan indicator, and the
|
|
154
|
+
flash-sale promo banner. A theme that overrides the pricing layout MUST keep
|
|
155
|
+
these hooks (restyle them freely — the ids, classes, and data attributes are
|
|
156
|
+
the contract):
|
|
157
|
+
|
|
158
|
+
| Hook | What the framework JS does with it |
|
|
159
|
+
|---|---|
|
|
160
|
+
| `input[name="billing"]` radios with `data-billing="monthly"` / `"annually"` (one `checked`) | source of truth for the billing toggle |
|
|
161
|
+
| `.amount`, `.billing-info`, `.price-per-unit` — each carrying `data-monthly` + `data-annually` | text content swapped when the toggle changes |
|
|
162
|
+
| `button[data-plan-id="<plan id>"]` inside a `.card` | click → `/payment/checkout?product=<id>` (`enterprise` → `/contact`); the current-plan indicator disables + relabels the signed-in user's active plan button |
|
|
163
|
+
| plan name element matching `.card-title`, `.h2`, or `.h3` inside the card | plan name for add-to-cart analytics (falls back to the plan id) |
|
|
164
|
+
| `#pricing-promo-banner` (shipped with the `hidden` attribute) containing `#pricing-promo-badge` / `#pricing-promo-text` / `#pricing-promo-countdown` / `#pricing-promo-code` | flash-sale banner: the JS reveals it, fills in the rotating sale name + countdown, and pushes `.navbar-wrapper` + `main > section:first-of-type` down to make room |
|
|
165
|
+
|
|
166
|
+
Two behaviors worth knowing:
|
|
167
|
+
|
|
168
|
+
- The JS swaps active/inactive **button classes** on the toggle only when the
|
|
169
|
+
radios live inside a `.btn-group` (classy's structure). A custom toggle
|
|
170
|
+
(newsflash's `.billing-toggle`) skips that gracefully — style the active
|
|
171
|
+
state in CSS via `.btn-check:checked + label` instead.
|
|
172
|
+
- Omitting a hook fails **silently**, not loudly — a missing promo banner just
|
|
173
|
+
never appears, a missing `.card-title` quietly degrades analytics. Diff your
|
|
174
|
+
pricing layout against classy's hooks before calling the theme done.
|
|
175
|
+
|
|
176
|
+
The hooks (and the rest of the theme conventions: entry files, `$avatar-sizes`,
|
|
177
|
+
`[ site.theme.id ]` bracket parents, no theme-prefixed classes, no inline
|
|
178
|
+
scripts, page-asset shapes) are enforced by the build-layer **theme-contract
|
|
179
|
+
test** — `npx mgr test mgr:build/theme-contract` — which globs every theme, so
|
|
180
|
+
a new theme is covered the moment it lands. It caught neobrutalism's missing
|
|
181
|
+
promo banner + `.card-title` the day it was written.
|
|
182
|
+
|
|
183
|
+
#### Theme chrome: inherit classy's nav + footer, restyle via CSS
|
|
184
|
+
|
|
185
|
+
The site chrome (masthead/nav + footer) resolves automatically: the
|
|
186
|
+
`jsonToHtml` task generates wrapper includes that dispatch to
|
|
187
|
+
`themes/<active-id>/frontend/sections/*.html` with the consumer's
|
|
188
|
+
`nav.json`/`footer.json` data, and `copyFallbackThemeFiles()` supplies classy's
|
|
189
|
+
version (with `themes/classy/` paths rewritten to your namespace) when the
|
|
190
|
+
theme doesn't ship one.
|
|
191
|
+
|
|
192
|
+
**Inherit by default — do NOT fork chrome includes.** The chrome's *identity*
|
|
193
|
+
comes from theme CSS, not from forked markup: newsflash's sticky blurred-paper
|
|
194
|
+
masthead and editorial ink-slab footer are achieved entirely in
|
|
195
|
+
`css/layout/_navigation.scss` + `_general.scss` against classy's inherited
|
|
196
|
+
markup (serif wordmark sizing, `.avatar { display: none }`, panel-color
|
|
197
|
+
repaints of the `.link-muted`/`.text-body` utilities, volt column heads). A
|
|
198
|
+
forked include that only re-skins is a copy that silently drifts every time
|
|
199
|
+
classy's chrome gets a fix — newsflash's nav fork was deleted for exactly this
|
|
200
|
+
reason after diverging from classy by nothing but a comment.
|
|
201
|
+
|
|
202
|
+
Fork a chrome include ONLY when the *structure* genuinely diverges (different
|
|
203
|
+
element order, removed/added blocks that CSS cannot express). If you do:
|
|
204
|
+
|
|
205
|
+
1. **Keep the data contract** — render the same `data.logo` / `data.links` /
|
|
206
|
+
`data.actions` / `data.socials` / `data.legal` / `data.copyright` shapes from
|
|
207
|
+
`nav.json`/`footer.json` so consumer config works across theme swaps.
|
|
208
|
+
2. **Reference your own theme namespace** for nested includes (e.g.
|
|
209
|
+
`{% include themes/<id>/global/sections/account.html %}`) — the fallback
|
|
210
|
+
copies classy's file into your namespace when you don't ship one.
|
|
211
|
+
3. **The footer MUST include the appearance picker** (see below) and the
|
|
212
|
+
language dropdown.
|
|
213
|
+
|
|
214
|
+
#### The appearance picker is required in every footer
|
|
215
|
+
|
|
216
|
+
Every theme footer includes the appearance dropdown — a pure drop-in block; all
|
|
217
|
+
logic is handled framework-side via `data-appearance-*` attributes (see
|
|
218
|
+
[docs/appearance.md](appearance.md)). The toggle button is **icon-only**: the
|
|
219
|
+
mode icons swap via `data-appearance-icon`, and there is deliberately NO
|
|
220
|
+
`data-appearance-current` text label in the button (the words live in the menu
|
|
221
|
+
items):
|
|
222
|
+
|
|
223
|
+
```html
|
|
224
|
+
<div class="dropup uj-appearance-dropdown">
|
|
225
|
+
<button class="btn btn-sm btn-outline-adaptive dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-label="Appearance">
|
|
226
|
+
<span data-appearance-icon="light" hidden>{% uj_icon "sun", "fa-sm" %}</span>
|
|
227
|
+
<span data-appearance-icon="dark" hidden>{% uj_icon "moon-stars", "fa-sm" %}</span>
|
|
228
|
+
<span data-appearance-icon="system" hidden>{% uj_icon "circle-half-stroke", "fa-sm" %}</span>
|
|
229
|
+
</button>
|
|
230
|
+
<ul class="dropdown-menu">
|
|
231
|
+
<li><button class="dropdown-item" type="button" data-appearance-set="light">Light</button></li>
|
|
232
|
+
<li><button class="dropdown-item" type="button" data-appearance-set="dark">Dark</button></li>
|
|
233
|
+
<li><button class="dropdown-item" type="button" data-appearance-set="system">System</button></li>
|
|
234
|
+
</ul>
|
|
235
|
+
</div>
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Classy's footer ships it next to the language dropdown, so themes inheriting
|
|
239
|
+
the fallback footer get it for free; themes with custom footers must include
|
|
240
|
+
it themselves.
|
|
99
241
|
|
|
100
242
|
#### No theme-prefixed classes — use universal class names
|
|
101
243
|
|
|
@@ -263,7 +405,8 @@ Get this distinction right or you'll either duplicate plumbing or fight override
|
|
|
263
405
|
|---|---|---|
|
|
264
406
|
| Core behavior CSS | [src/assets/css/core/](../src/assets/css/core/) | animations, alerts, lazy-loading shimmer, cookie consent, bindings skeletons, social sharing. Injected for **every** theme by `ultimate-jekyll-manager.scss`. |
|
|
265
407
|
| Bootstrap extensions | [src/assets/themes/bootstrap/overrides/](../src/assets/themes/bootstrap/overrides/) | avatars, color-shades, soft-colors, adaptive buttons, spacing, link/typography utilities. Each theme pulls these in via `@import '../bootstrap/overrides'` at the end of its `_theme.scss`. |
|
|
266
|
-
| Page layouts/includes | classy theme + fallback copy | the ~40 frontend/backend/admin layouts and nav
|
|
408
|
+
| Page layouts/includes (fallback) | classy theme + fallback copy | the ~40 frontend/backend/admin layouts and ALL includes a theme doesn't define — including the nav + footer chrome, which themes inherit and restyle via CSS (see [Theme chrome](#theme-chrome-inherit-classys-nav--footer-restyle-via-css)). |
|
|
409
|
+
| Pricing math | [_includes/core/pricing/resolve-plan.html](../src/defaults/dist/_includes/core/pricing/resolve-plan.html) | the plan price-resolution Liquid (product lookup, monthly/annual precedence, per-unit math). Every theme's pricing layout calls `{% include core/pricing/resolve-plan.html plan=plan %}` and renders the assigned variables — never re-implement the math. |
|
|
267
410
|
| Bootstrap class contract | `bootstrap/scss` | the markup + class names (`.btn`, `.card`, `.navbar`, `.form-control`). Themes **restyle** these classes; they don't invent new markup. |
|
|
268
411
|
|
|
269
412
|
### Per-theme — this IS the theme's job (and SHOULD differ between themes)
|
|
@@ -272,8 +415,12 @@ Get this distinction right or you'll either duplicate plumbing or fight override
|
|
|
272
415
|
- `_root.scss` — SCSS → CSS-variable bridge for light/dark.
|
|
273
416
|
- Component SCSS — how `.btn`/`.card`/`.form-control`/`.navbar` actually look.
|
|
274
417
|
- `_theme.js` — expose Bootstrap + run theme behaviors on DOM ready.
|
|
418
|
+
- Chrome LOOK via CSS — masthead/footer restyling in `css/layout/` against the
|
|
419
|
+
inherited classy chrome markup (fork the include itself only on real
|
|
420
|
+
structural divergence — see [Theme chrome](#theme-chrome-inherit-classys-nav--footer-restyle-via-css)).
|
|
275
421
|
- *(optional)* Page-layout overrides under `_layouts/themes/<id>/...` — for pages
|
|
276
|
-
whose structure must differ (
|
|
422
|
+
whose structure must differ (keep the resolution Liquid + universal keys, write
|
|
423
|
+
genre-native defaults).
|
|
277
424
|
- *(optional)* Theme page CSS at `pages/<path>/index.scss` — additive per-page
|
|
278
425
|
styles for those overridden layouts.
|
|
279
426
|
- *(optional)* Theme page JS at `pages/<path>/index.js` — additive per-page behavior
|
|
@@ -343,6 +490,80 @@ One more: a generic `a:hover { color: … }` also paints **button** labels (butt
|
|
|
343
490
|
`<a>`). Guard it with `a.btn:hover { color: var(--bs-btn-color); }` so buttons keep
|
|
344
491
|
their own (frozen) text color. Nav/dropdown/footer links already win on specificity.
|
|
345
492
|
|
|
493
|
+
### Page bundles re-emit Bootstrap — double your selectors (gotcha)
|
|
494
|
+
|
|
495
|
+
Every theme **page** bundle (`pages/<path>/index.<id>.bundle.css`) compiles
|
|
496
|
+
standalone via `@use 'config'`, so it contains a full copy of Bootstrap — and it
|
|
497
|
+
loads AFTER `main.bundle.css`. Any main-bundle rule that ties Bootstrap's
|
|
498
|
+
specificity loses on pages that ship page CSS: Bootstrap's re-emitted
|
|
499
|
+
`:root`/`[data-bs-theme=dark]` variable blocks clobber a theme's single-selector
|
|
500
|
+
`_root.scss` bridge (dark mode reverts to Bootstrap gray), and re-emitted
|
|
501
|
+
`.btn`/`.btn-outline-*` rules clobber single-class button overrides.
|
|
502
|
+
|
|
503
|
+
**The fix is doubled selectors** in the theme's structural rules so they win on
|
|
504
|
+
specificity regardless of load order:
|
|
505
|
+
|
|
506
|
+
```scss
|
|
507
|
+
:root:root, [data-bs-theme="light"][data-bs-theme="light"] { /* light vars */ }
|
|
508
|
+
[data-bs-theme="dark"][data-bs-theme="dark"] { /* dark vars */ }
|
|
509
|
+
.btn.btn { /* press/lift system */ }
|
|
510
|
+
[class*="btn-outline-"][class*="btn-outline-"] { /* ghost buttons */ }
|
|
511
|
+
.dropdown-menu.dropdown-menu { /* panel inset (Bootstrap re-emits padding-x: 0) */ }
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
See newsflash's `_root.scss` + `_buttons.scss` (and neobrutalism's `.btn.btn`)
|
|
515
|
+
for reference treatments.
|
|
516
|
+
|
|
517
|
+
The same trap applies to **type metrics**: Bootstrap's re-emitted `.display-*`
|
|
518
|
+
(`font-weight: 300`), `.lead` (`font-weight: 300`), and `body` rules clobber
|
|
519
|
+
main-bundle element-rule overrides on any page that ships page CSS — headings
|
|
520
|
+
silently go thin on exactly those pages. For values Bootstrap owns a variable
|
|
521
|
+
for, **set the variable in the config `@forward ... with (...)` block instead
|
|
522
|
+
of writing an element rule** (`$display-font-weight`, `$display-line-height`,
|
|
523
|
+
`$lead-font-weight`, `$headings-line-height`, `$line-height-base`, …) — then
|
|
524
|
+
every Bootstrap copy compiles the right value natively and there is no
|
|
525
|
+
specificity war at all. Element rules in `_typography.scss` are only for
|
|
526
|
+
props Bootstrap has no variable for (optical sizing, letter-spacing,
|
|
527
|
+
`text-wrap`, font smoothing).
|
|
528
|
+
|
|
529
|
+
### Remap the `--bs-*-rgb` companions too (gotcha)
|
|
530
|
+
|
|
531
|
+
Bootstrap's `.bg-body`, `.bg-body-secondary`, `.bg-body-tertiary`,
|
|
532
|
+
`.text-body`, `.text-body-secondary` utilities paint from
|
|
533
|
+
`rgba(var(--bs-*-rgb), opacity)` — NOT the hex variables. A theme that remaps
|
|
534
|
+
`--bs-secondary-bg` but not `--bs-secondary-bg-rgb` gets Bootstrap's default
|
|
535
|
+
gray triplets bleeding through every `.bg-body-*` surface (most visibly in dark
|
|
536
|
+
mode). When the `_root.scss` bridge remaps a surface/color var, **always remap
|
|
537
|
+
its `-rgb` companion** in the same block:
|
|
538
|
+
|
|
539
|
+
```scss
|
|
540
|
+
--bs-secondary-bg: #{$nf-paper-2};
|
|
541
|
+
--bs-secondary-bg-rgb: #{red($nf-paper-2)}, #{green($nf-paper-2)}, #{blue($nf-paper-2)};
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
Also note: shared includes may put `!important` utilities (e.g.
|
|
545
|
+
`.bg-body-secondary` on the footer) on elements a theme wants to restyle — the
|
|
546
|
+
override needs `!important` AND equal-or-higher specificity
|
|
547
|
+
(`footer.bg-body-secondary`, not bare `footer`).
|
|
548
|
+
|
|
549
|
+
### Derive dark-mode brand remaps from `$primary` (gotcha)
|
|
550
|
+
|
|
551
|
+
When a dark block remaps `--bs-primary` / `--bs-link-color` (e.g. to brighten
|
|
552
|
+
the brand color for contrast on dark surfaces), **derive the value from the
|
|
553
|
+
compile-time `$primary`** — never hardcode the theme's stock color. Consumers
|
|
554
|
+
override `$primary` via `main.scss`'s `with (...)` block; a hardcoded dark
|
|
555
|
+
remap would snap their brand back to the theme's color the moment dark mode
|
|
556
|
+
engages (light honors the override, dark ignores it). newsflash's pattern:
|
|
557
|
+
|
|
558
|
+
```scss
|
|
559
|
+
// Stock vermilion keeps its hand-tuned brightening; any other brand color
|
|
560
|
+
// gets a generic white-mix lift.
|
|
561
|
+
$nf-primary-dark-mode: if($primary == $nf-vermilion, $nf-vermilion-dark-mode, mix(white, $primary, 15%));
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
The theme's own identity accents (newsflash's `--nf-vermilion` used in cover-art
|
|
565
|
+
gradients) are exempt — those ARE the theme, not the consumer's brand.
|
|
566
|
+
|
|
346
567
|
---
|
|
347
568
|
|
|
348
569
|
## 🚨 BOOTSTRAP-FIRST — NEVER reinvent the wheel
|
|
@@ -408,7 +629,14 @@ The GOOD version uses zero custom CSS for layout/buttons — the theme's `_butto
|
|
|
408
629
|
6. **Fonts** load via the base layout's `theme.head.content`, NOT a CSS `@import`
|
|
409
630
|
(avoids render-blocking duplicate loads). To use custom fonts, override
|
|
410
631
|
`frontend/core/base.html` (see below).
|
|
411
|
-
7. **
|
|
632
|
+
7. **Develop in ONE appearance mode; ship with BOTH.** Pick a primary mode
|
|
633
|
+
while building (the consumer's `theme.appearance` default is the natural
|
|
634
|
+
choice) and get the design right there first — splitting attention across
|
|
635
|
+
both modes mid-build doubles every iteration. But a theme is only **done**
|
|
636
|
+
when light AND dark are both validated: the `_root.scss` bridge makes dark
|
|
637
|
+
mode a single remap block, so build the token bridge correctly, then do a
|
|
638
|
+
dedicated both-modes screenshot pass at the end.
|
|
639
|
+
8. **Validate live, then document.** UJM can't run a dev server — build in a
|
|
412
640
|
consumer and screenshot (see [Validating](#validating-a-theme)).
|
|
413
641
|
|
|
414
642
|
---
|
|
@@ -436,8 +664,15 @@ Use this for first-party themes like `neobrutalism`.
|
|
|
436
664
|
classy fallback supplies the rest. The most common override is
|
|
437
665
|
`frontend/core/base.html` to load your fonts (neobrutalism overrides just this
|
|
438
666
|
one file).
|
|
439
|
-
5. **
|
|
440
|
-
|
|
667
|
+
5. **Write genre-native frontmatter defaults** in every layout you override —
|
|
668
|
+
the default copy/sections must match the theme's purpose, not classy's SaaS
|
|
669
|
+
demo data. See [Frontmatter defaults are part of the theme's
|
|
670
|
+
identity](#frontmatter-defaults-are-part-of-the-themes-identity).
|
|
671
|
+
6. **Restyle the inherited nav + footer chrome via CSS** (`css/layout/`) — do
|
|
672
|
+
NOT fork the chrome includes unless the structure genuinely diverges. See
|
|
673
|
+
[Theme chrome](#theme-chrome-inherit-classys-nav--footer-restyle-via-css).
|
|
674
|
+
7. **Add a `README.md`** in the theme folder (customization quickstart).
|
|
675
|
+
8. `npm run prepare` (copies `src/`→`dist/`) so consumers see it. Then test in a
|
|
441
676
|
consumer (Path: [Validating](#validating-a-theme)).
|
|
442
677
|
|
|
443
678
|
## Path B — author a theme IN A CONSUMER PROJECT (that project only)
|
|
@@ -481,7 +716,10 @@ UJM cannot run a dev server itself (it runs inside a consumer). To verify a them
|
|
|
481
716
|
3. Screenshot the key pages (home, pricing, signin, signup) in **both** light and
|
|
482
717
|
dark (`document.documentElement.setAttribute('data-bs-theme','dark')`) — e.g.
|
|
483
718
|
via the chrome-devtools MCP. Check the console for errors and that your theme's
|
|
484
|
-
"loaded" log appears.
|
|
719
|
+
"loaded" log appears. Developing in one mode is fine (and encouraged — see
|
|
720
|
+
[Authoring conventions](#authoring-conventions-both-paths)); **shipping
|
|
721
|
+
requires both modes validated**, plus a click-through of the footer
|
|
722
|
+
appearance picker to confirm live switching looks right.
|
|
485
723
|
4. Iterate on SCSS — the consumer's gulp watcher recompiles when UJM's `dist/`
|
|
486
724
|
changes (if UJM is running `npm start`), or re-run `npm run prepare`.
|
|
487
725
|
|
|
@@ -495,6 +733,9 @@ all four pages in light + dark.
|
|
|
495
733
|
|
|
496
734
|
- Theme tokens example: [src/assets/themes/neobrutalism/_config.scss](../src/assets/themes/neobrutalism/_config.scss)
|
|
497
735
|
- CSS-variable bridge example: [src/assets/themes/neobrutalism/css/base/_root.scss](../src/assets/themes/neobrutalism/css/base/_root.scss)
|
|
736
|
+
- Genre-native frontmatter defaults example: [src/defaults/dist/_layouts/themes/newsflash/frontend/pages/index.html](../src/defaults/dist/_layouts/themes/newsflash/frontend/pages/index.html)
|
|
737
|
+
- Posts-driven theme sections (ticker in base.html, cover-story hero, most-read rail — all guarded for empty `site.posts`): [src/defaults/dist/_layouts/themes/newsflash/](../src/defaults/dist/_layouts/themes/newsflash/)
|
|
738
|
+
- Theme page JS example (blog post reading-progress, flat `asset_path` shape): [src/assets/themes/newsflash/pages/blog/post.js](../src/assets/themes/newsflash/pages/blog/post.js)
|
|
498
739
|
- Starter: [src/assets/themes/_template/](../src/assets/themes/_template/)
|
|
499
740
|
- Fallback mechanism: [src/gulp/tasks/distribute.js](../src/gulp/tasks/distribute.js) (`copyFallbackThemeFiles`)
|
|
500
741
|
- Resolution: [src/gulp/tasks/sass.js](../src/gulp/tasks/sass.js), [src/gulp/tasks/webpack.js](../src/gulp/tasks/webpack.js)
|
package/logs/test.log
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# ujm log — 2026-06-11T08:01:19.096Z — pid=72007
|
|
2
|
+
[01:01:19] 'test': Running tests (layer=all)
|
|
3
|
+
[01:01:19] 'test': Test mode: normal (external APIs skipped)
|
|
4
|
+
|
|
5
|
+
Ultimate Jekyll Manager Tests
|
|
6
|
+
|
|
7
|
+
Framework Tests
|
|
8
|
+
⤷ attach-log-file — tee stdout/stderr to a file
|
|
9
|
+
✓ exports the expected surface (0ms)
|
|
10
|
+
✓ stripAnsi removes color escape codes (0ms)
|
|
11
|
+
hello world
|
|
12
|
+
colored line
|
|
13
|
+
✓ attach + stdout.write + detach: file contains the writes (1ms)
|
|
14
|
+
✓ idempotent: attaching twice with same name returns same fd (0ms)
|
|
15
|
+
✓ attach with falsy name returns null and does nothing (0ms)
|
|
16
|
+
⤷ CLI alias resolution
|
|
17
|
+
✓ cli.js exports a Main class (0ms)
|
|
18
|
+
✓ all expected commands exist on disk (0ms)
|
|
19
|
+
✓ each command module exports an async function (6ms)
|
|
20
|
+
⤷ collectTextNodes (utils/collectTextNodes.js)
|
|
21
|
+
✓ extracts page title (145ms)
|
|
22
|
+
✓ skips <script> and <style> (1ms)
|
|
23
|
+
✓ spellcheck dictionary (utils/dictionary.js) (1ms)
|
|
24
|
+
⤷ expect() matcher set
|
|
25
|
+
✓ toBe + toEqual basics (0ms)
|
|
26
|
+
✓ .not negates (0ms)
|
|
27
|
+
✓ toContain works on arrays and strings (0ms)
|
|
28
|
+
✓ toThrow catches sync + async throws (0ms)
|
|
29
|
+
✓ toBeGreaterThan / toBeLessThan (0ms)
|
|
30
|
+
✓ failing assertions throw AssertionError (0ms)
|
|
31
|
+
✓ package.json exports resolve to real files in dist/ (0ms)
|
|
32
|
+
⤷ Logger (src/lib/logger.js)
|
|
33
|
+
✓ Logger constructor stores name (0ms)
|
|
34
|
+
✓ Logger exposes log/error/warn/info methods (0ms)
|
|
35
|
+
✓ Logger.format is chalk (0ms)
|
|
36
|
+
✓ Logger output goes through console with prefix (0ms)
|
|
37
|
+
⤷ Manager (build.js) public surface
|
|
38
|
+
✓ Manager constructor is a function (0ms)
|
|
39
|
+
✓ static methods match prototype methods (0ms)
|
|
40
|
+
✓ isBuildMode reflects UJ_BUILD_MODE env (0ms)
|
|
41
|
+
✓ isQuickMode reflects UJ_QUICK env (0ms)
|
|
42
|
+
✓ isServer reflects UJ_IS_SERVER env (0ms)
|
|
43
|
+
✓ getEnvironment maps to development/testing/production (0ms)
|
|
44
|
+
✓ actLikeProduction is true when isBuildMode OR UJ_AUDIT_FORCE (0ms)
|
|
45
|
+
✓ getRootPath("package") points at UJM root (1ms)
|
|
46
|
+
✓ getMemoryUsage returns shape with MB-sized numbers (0ms)
|
|
47
|
+
✓ getArguments returns object with _ array + boolean defaults (0ms)
|
|
48
|
+
✓ logger returns object with log/error/warn/info methods (0ms)
|
|
49
|
+
✓ processBatches processes items in chunks and returns flat results (0ms)
|
|
50
|
+
⤷ mergeJekyllConfigs (utils/merge-jekyll-configs.js)
|
|
51
|
+
✓ merges collections from both configs (project additions win) (6ms)
|
|
52
|
+
✓ dedups defaults by scope key (project wins) (2ms)
|
|
53
|
+
✓ returns null when there is nothing to merge (1ms)
|
|
54
|
+
⤷ mode-helpers (isTesting / isDevelopment / isProduction / getVersion)
|
|
55
|
+
✓ helpers attach to Manager statically AND on prototype (0ms)
|
|
56
|
+
✓ isTesting reflects UJ_TEST_MODE env (0ms)
|
|
57
|
+
✓ isDevelopment false / isProduction true when UJ_BUILD_MODE=true (and not testing) (0ms)
|
|
58
|
+
✓ environments are mutually exclusive — testing wins under UJ_TEST_MODE (0ms)
|
|
59
|
+
✓ invariant: is*() exactly matches getEnvironment() + mutually exclusive (every scenario) (1ms)
|
|
60
|
+
✓ getVersion returns a non-empty string when run from a package (0ms)
|
|
61
|
+
⤷ createTemplateTransform (utils/template-transform.js)
|
|
62
|
+
✓ replaces [site.theme.id] with config value in .html files (2ms)
|
|
63
|
+
✓ leaves non-matching extensions untouched (e.g. .css) (0ms)
|
|
64
|
+
✓ passes directories through untouched (0ms)
|
|
65
|
+
⤷ node-powertools templating brackets ({} and [])
|
|
66
|
+
✓ default { } brackets resolve nested keys (0ms)
|
|
67
|
+
✓ [ ] brackets resolve nested keys when explicitly configured (1ms)
|
|
68
|
+
✓ [ ] brackets leave Jekyll {{ }} placeholders alone (0ms)
|
|
69
|
+
⤷ theme contract (structure, swappability, cross-theme JS contracts)
|
|
70
|
+
✓ _template: entry files + config contract (0ms)
|
|
71
|
+
✓ classy: entry files + config contract (0ms)
|
|
72
|
+
✓ neobrutalism: entry files + config contract (0ms)
|
|
73
|
+
✓ newsflash: entry files + config contract (1ms)
|
|
74
|
+
✓ _template: layouts swappable, markup clean (1ms)
|
|
75
|
+
✓ classy: layouts swappable, markup clean (12ms)
|
|
76
|
+
✓ neobrutalism: layouts swappable, markup clean (2ms)
|
|
77
|
+
✓ newsflash: layouts swappable, markup clean (3ms)
|
|
78
|
+
✓ _template: cross-theme JS contracts (0ms)
|
|
79
|
+
✓ classy: cross-theme JS contracts (1ms)
|
|
80
|
+
✓ neobrutalism: cross-theme JS contracts (1ms)
|
|
81
|
+
✓ newsflash: cross-theme JS contracts (0ms)
|
|
82
|
+
✓ page asset files match a declared asset_path shape (7ms)
|
|
83
|
+
⤷ validateYAMLFrontMatter (utils/_validate-yaml.js)
|
|
84
|
+
✓ returns { valid: true } for a file with valid frontmatter (3ms)
|
|
85
|
+
✓ returns { valid: true } when no frontmatter present (0ms)
|
|
86
|
+
✓ flags malformed YAML frontmatter as invalid with error message (1ms)
|
|
87
|
+
⤷ page-layer baseline (DOM + fetch + storage)
|
|
88
|
+
✓ document is interactive or complete (0ms)
|
|
89
|
+
✓ fetch() works against the local harness server (7ms)
|
|
90
|
+
✓ localStorage is available (1ms)
|
|
91
|
+
⤷ harness globals (window.Configuration + dataset)
|
|
92
|
+
✓ window.Configuration has brand + theme + web_manager (0ms)
|
|
93
|
+
✓ document.documentElement.dataset.pagePath is set (0ms)
|
|
94
|
+
✓ UJ_TEST_MODE is signalled on globalThis (0ms)
|
|
95
|
+
⤷ prerendered icons template lookup
|
|
96
|
+
✓ template#prerendered-icons exists and has the test icon (1ms)
|
|
97
|
+
✓ looking up a missing icon returns null (0ms)
|
|
98
|
+
⤷ boot tests (consumer _site/)
|
|
99
|
+
✓ /service-worker.js served with javascript content type (105ms)
|
|
100
|
+
✓ index.html registers SW and reaches activated state (202ms)
|
|
101
|
+
✓ SW responds to get-cache-name message with brand-id pattern (116ms)
|
|
102
|
+
✓ home page renders with title + body content (210ms)
|
|
103
|
+
✓ /about resolves via Jekyll-style .html fallback (93ms)
|
|
104
|
+
✓ build.json is served with brand metadata (109ms)
|
|
105
|
+
✓ CSS bundle served with text/css content type (94ms)
|
|
106
|
+
|
|
107
|
+
Results
|
|
108
|
+
80 passing
|
|
109
|
+
|
|
110
|
+
Total: 80 tests in 24960ms
|
|
111
|
+
|