@zvndev/darkmown 2.12.0 → 2.12.1

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/AGENTS.md CHANGED
@@ -73,7 +73,7 @@ rss_limit: 20
73
73
  ```
74
74
 
75
75
  - `title`, `description`, `image` drive `<title>`, the meta description, and the Open Graph / Twitter card. `lang` sets `<html lang>` (default `en`).
76
- - `html: true` allows raw HTML in that file's body. `transitions: true` opts into an opacity-only cross-fade view transition plus link prerendering.
76
+ - `html: true` allows raw HTML in that file's body. `transitions: true` opts into a view transition (opacity-only, sequenced out-then-in) plus link prerendering.
77
77
  - `draft: true` keeps the page out of `dist`, `routes.json`, the sitemap, and the feed. `darkmown dev` still serves it; `darkmown build --drafts` includes it.
78
78
  - `noindex: true` builds the page but removes it from discovery: a robots `noindex,nofollow` meta, and exclusion from `sitemap.xml`, `rss.xml`, and the generated `llms.txt`/`llms-full.txt`. On a collection entry with a `_schema.wd`, declare it as `noindex: boolean?`.
79
79
  - `icon: /path` sets the favicon verbatim (a `type` is added for `.svg`/`.png`/`.ico`). With no `icon:` key, the first of `site/pages/icon.svg`, `icon.png`, `favicon.ico` wins; with neither, the built-in mark is the fallback.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  All notable changes to Darkmown are documented here. Versions follow [semver](https://semver.org); pre-1.0 minor versions may contain breaking changes.
4
4
 
5
+ ## 2.12.1 (2026-10-16)
6
+
7
+ A one-item patch revising the navigation animation again, this time from frame-level measurement instead of reading the stylesheet.
8
+
9
+ - **BEHAVIOR CHANGE: the default view transition is now a sequenced out-then-in.** The 2.12.0 simultaneous cross-fade was recorded with CDP screencast on the real demo and it double-exposes: the incoming snapshot stacks OVER the outgoing one, so the outgoing page stays fully visible while the incoming ghosts through it (a fade-in-only variant measured no better, since the old snapshot simply holds at full opacity for the whole window). The old root now fades to opacity 0 in 90ms and the new root fades in over 130ms after an 80ms delay, both opacity-only with the same easing and fill mode, so the overlap is about 10ms near background opacity and nothing ghosts. The reduced-motion block collapses both animations to 1ms with 0ms delay. Keyframe names, the `@view-transition` opt-in, and the prerender hint are unchanged, and a `transitions: true` page still ships zero JS.
10
+
5
11
  ## 2.12.0 (2026-10-16)
6
12
 
7
13
  A perceived-performance release from client-demo feedback (fonts visibly swapped on every reload, and the navigation transition read as a flicker): fonts now preload, the preview server revalidates instead of re-downloading, the default view transition is a plain cross-fade, and Markdown images can carry an author-set `sizes`. No breaking change; the transition animation change is called out below.
package/README.md CHANGED
@@ -288,7 +288,7 @@ A few reserved keys drive **drafts and feeds** (see [SEO & feeds](#seo--feeds-si
288
288
 
289
289
  **Markdown sources: `md_source: true`** on the home page opts the whole site in to publishing every page's byte-exact source beside its HTML: `/docs/` also answers `/docs.md` (the exact `.md`/`.wd` file the author wrote, frontmatter and CRLF endings included) with a `text/markdown` content type, and each published page carries `<link rel="alternate" type="text/markdown" href="/docs.md" title="Markdown source">`, emitted exactly when the file is, so the link never 404s. A page opts back out with `md_source: false`; a page-level `true` without the home flag does nothing, drafts never emit a source, and sitemap/rss/robots never list the `.md` files (they are alternates, not routes). Off by default, and a site that never sets it builds byte-identical output.
290
290
 
291
- `transitions: true` opts a page into **instant, flash-free navigation**: zero JavaScript, all declarative. It emits an opacity-only **cross-fade view transition** for the page swap (old and new animate simultaneously at 160ms with one shared easing, since 2.12; the earlier directional slide read as a flicker mid-navigation), plus a **`<script type="speculationrules">` prerender** hint that renders the next same-origin page on hover/pointerdown so the click activates an already-painted page (no white render-gap flash). It honors `prefers-reduced-motion`. Only same-origin pages that both opt in transition; browsers without support, or with page-preloading disabled, navigate normally. Off by default; opt out with `transitions: false`. Mark a link `{.no-prefetch}` to exclude it from prerendering. (Chrome disables prerendering while DevTools is open, so test the built site with DevTools closed.)
291
+ `transitions: true` opts a page into **instant, flash-free navigation**: zero JavaScript, all declarative. It emits a sequenced, opacity-only **view transition** for the page swap (the old page fades out in 90ms, the new page fades in over 130ms after an 80ms delay, one shared easing; the overlap is about 10ms near background opacity, so nothing ghosts or double-exposes), plus a **`<script type="speculationrules">` prerender** hint that renders the next same-origin page on hover/pointerdown so the click activates an already-painted page (no white render-gap flash). It honors `prefers-reduced-motion` (both animations collapse to 1ms with no delay). Only same-origin pages that both opt in transition; browsers without support, or with page-preloading disabled, navigate normally. Off by default; opt out with `transitions: false`. Mark a link `{.no-prefetch}` to exclude it from prerendering. (Chrome disables prerendering while DevTools is open, so test the built site with DevTools closed.)
292
292
 
293
293
  ## Static assets
294
294
 
@@ -2,6 +2,10 @@
2
2
 
3
3
  This audit compares the current Darkmown implementation with the original MARROW/WhateverDown vision and the follow-up decisions made on 2026-06-11.
4
4
 
5
+ ## 2.12.1 additions: the transition, measured frame by frame (2026-10-16)
6
+
7
+ - **The navigation animation is now a sequenced out-then-in, because the cross-fade double-exposed on camera.** CDP screencast of the 2.12.0 simultaneous fade showed the incoming snapshot stacking over the outgoing one, ghosting the old page through the new; fading out first (90ms) and delaying the fade-in (130ms after 80ms) leaves only a ~10ms overlap near background opacity. Both animations stay opacity-only with shared easing, keyframe names are unchanged, and reduced motion still collapses the effect to 1ms.
8
+
5
9
  ## 2.12.0 additions: perceived performance from demo feedback (2026-10-16)
6
10
 
7
11
  - **First paint stops swapping fonts.** The font post-pass now injects per-page `<link rel="preload" as="font" crossorigin>` tags for exactly the project fonts a page's own stylesheets declare, leading with the subset woff2 when subsetting ran (the original otherwise, still a win), idempotently so dev rebuilds never duplicate a link. `darkmown serve` learned conditional requests: strong ETags and Last-Modified on every static response, RFC 7232-correct 304s, HTML at `no-cache`, fingerprinted-by-build assets at `public, max-age=3600`, dev deliberately unchanged. Together these close the demo's "fonts swap on every reload" complaint from both ends: first load preloads, reloads revalidate.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zvndev/darkmown",
3
- "version": "2.12.0",
3
+ "version": "2.12.1",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -475,7 +475,7 @@ Mark a page `draft: true` and it stays out of production: `darkmown build` leave
475
475
 
476
476
  ## Instant navigation
477
477
 
478
- Add `transitions: true` to a page's frontmatter for **instant, flash-free navigation**: zero JavaScript. It emits an opacity-only cross-fade view transition for the page swap (old and new animate simultaneously at 160ms with one shared easing; the earlier directional slide read as a flicker) and a `<script type="speculationrules">` prerender hint, so the browser renders the next same-origin page on hover/pointerdown and the click activates an already-painted page (no white render-gap). It honors `prefers-reduced-motion`, and only same-origin pages that both opt in transition. Off by default; opt out with `transitions: false`, or exclude a single link from prerendering with `{.no-prefetch}`. (Chrome turns prerendering off while DevTools is open, so test the built site with DevTools closed.)
478
+ Add `transitions: true` to a page's frontmatter for **instant, flash-free navigation**: zero JavaScript. It emits a sequenced, opacity-only view transition for the page swap (the old page fades out in 90ms, the new page fades in over 130ms after an 80ms delay, one shared easing; the ~10ms overlap sits near background opacity, so nothing ghosts) and a `<script type="speculationrules">` prerender hint, so the browser renders the next same-origin page on hover/pointerdown and the click activates an already-painted page (no white render-gap). It honors `prefers-reduced-motion` (both animations collapse to 1ms with no delay), and only same-origin pages that both opt in transition. Off by default; opt out with `transitions: false`, or exclude a single link from prerendering with `{.no-prefetch}`. (Chrome turns prerendering off while DevTools is open, so test the built site with DevTools closed.)
479
479
 
480
480
  ## Images
481
481
 
@@ -57,12 +57,12 @@ ${u}
57
57
  `),N=o.meta.transitions===!0||o.meta.transitions==="true",R=N?`
58
58
  <style>
59
59
  @view-transition { navigation: auto; }
60
- ::view-transition-old(root) { animation: wd-nav-out 160ms cubic-bezier(0.4, 0, 0.2, 1) both; }
61
- ::view-transition-new(root) { animation: wd-nav-in 160ms cubic-bezier(0.4, 0, 0.2, 1) both; }
60
+ ::view-transition-old(root) { animation: wd-nav-out 90ms cubic-bezier(0.4, 0, 0.2, 1) both; }
61
+ ::view-transition-new(root) { animation: wd-nav-in 130ms cubic-bezier(0.4, 0, 0.2, 1) 80ms both; }
62
62
  @keyframes wd-nav-out { to { opacity: 0; } }
63
63
  @keyframes wd-nav-in { from { opacity: 0; } }
64
64
  @media (prefers-reduced-motion: reduce) {
65
- ::view-transition-old(root), ::view-transition-new(root) { animation-duration: 1ms; }
65
+ ::view-transition-old(root), ::view-transition-new(root) { animation-duration: 1ms; animation-delay: 0ms; }
66
66
  }
67
67
  </style>`:"",B=N?`
68
68
  <script type="speculationrules">{"prerender":[{"where":{"and":[{"href_matches":"/*"},{"not":{"selector_matches":".no-prefetch"}},{"not":{"selector_matches":"[rel~=nofollow]"}}]},"eagerness":"moderate"}]}<\/script>`:"",H=fn(e,t,r),J=H&&H.scoped?null:Mr(o.meta,e,t,r).map(C=>It(C,t.cwd,r)).find(C=>C.scoped),ne=H&&H.scoped?H.scopeId:J?.scopeId,re=ne?pn(o.html,ne):o.html,K=rb(re,t,r),{content:q,target:k}=nb(K);return{meta:o.meta,deps:o.deps,collectionsUsed:o.collectionsUsed,symbols:o.symbols,html:`<!doctype html>
@@ -183,24 +183,24 @@ export function compilePage(file, context, options = {}) {
183
183
  // (graceful, transition-free fallback) and only applies to same-origin
184
184
  // navigations where both the outgoing and incoming page opt in.
185
185
  //
186
- // The default is one simultaneous cross-fade: outgoing opacity 1→0 while
187
- // incoming 0→1, same duration and easing both ways. An earlier default paired
188
- // a fade-out with a fade-in of opposite translates and easings; measured on a
189
- // real site that read as a flicker (the outgoing page appeared to come back
190
- // mid-navigation). Opacity-only, no transform: the two snapshots overlap in
191
- // place, so nothing ever moves backwards. Under prefers-reduced-motion the
192
- // same fade runs at 1ms — a cut, keeping the block structure intact.
186
+ // The default is a quick out-then-in: the outgoing snapshot fades to 0 in
187
+ // 90ms, the incoming one fades in after an 80ms delay. A simultaneous
188
+ // cross-fade was measured on a real site and read as flicker: the incoming
189
+ // snapshot stacks over the outgoing one, so image-heavy pages double-expose
190
+ // (outgoing opaque, incoming ghosting through). Sequencing keeps at most a
191
+ // ~10ms overlap near background opacity, so nothing ever ghosts. The delay
192
+ // fills `both`, keeping the incoming snapshot invisible until its turn.
193
193
  const wantTransitions =
194
194
  compiled.meta.transitions === true || compiled.meta.transitions === "true";
195
195
  const transitions = wantTransitions
196
196
  ? `\n <style>
197
197
  @view-transition { navigation: auto; }
198
- ::view-transition-old(root) { animation: wd-nav-out 160ms cubic-bezier(0.4, 0, 0.2, 1) both; }
199
- ::view-transition-new(root) { animation: wd-nav-in 160ms cubic-bezier(0.4, 0, 0.2, 1) both; }
198
+ ::view-transition-old(root) { animation: wd-nav-out 90ms cubic-bezier(0.4, 0, 0.2, 1) both; }
199
+ ::view-transition-new(root) { animation: wd-nav-in 130ms cubic-bezier(0.4, 0, 0.2, 1) 80ms both; }
200
200
  @keyframes wd-nav-out { to { opacity: 0; } }
201
201
  @keyframes wd-nav-in { from { opacity: 0; } }
202
202
  @media (prefers-reduced-motion: reduce) {
203
- ::view-transition-old(root), ::view-transition-new(root) { animation-duration: 1ms; }
203
+ ::view-transition-old(root), ::view-transition-new(root) { animation-duration: 1ms; animation-delay: 0ms; }
204
204
  }
205
205
  </style>`
206
206
  : "";