From 5a34b1846c8e5475fe69e4a46c330e8981bbc26b Mon Sep 17 00:00:00 2001 From: anti Date: Sat, 9 May 2026 04:17:50 -0400 Subject: [PATCH] fix(decnet_web/Layout): kill residual theme-swap open flash MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Even with fill: 'both', the new pseudo paints once at its default style (no clip-path = full size) before the JS animation registers — the brief open flash that survived the previous fix. Pre-publish click coords as --reveal-x / --reveal-y on before calling startViewTransition. The static CSS rule on ::view-transition-new(root) now sets clip-path: circle(0px at var(--reveal-x) var(--reveal-y)) as the pseudo's default, so the very first paint is already fully clipped. The animation then grows the circle outward from there. --- decnet_web/src/index.css | 7 +++++++ decnet_web/src/lib/useThemeToggle.ts | 9 +++++++++ 2 files changed, 16 insertions(+) diff --git a/decnet_web/src/index.css b/decnet_web/src/index.css index d5f6ff18..52bc4afe 100644 --- a/decnet_web/src/index.css +++ b/decnet_web/src/index.css @@ -359,6 +359,13 @@ input:focus { } ::view-transition-new(root) { z-index: 1; + /* Default clip-path: a zero-radius circle at the click point + * (--reveal-x / --reveal-y are written to by + * useThemeToggle.ts BEFORE startViewTransition runs). The + * pseudo therefore renders fully clipped on its very first + * frame — no opening flash possible. The web-animation then + * grows the circle outward; fill: 'both' pins the end. */ + clip-path: circle(0px at var(--reveal-x, 50%) var(--reveal-y, 50%)); } /* ── Scrollbar ─────────────────────────────────── */ diff --git a/decnet_web/src/lib/useThemeToggle.ts b/decnet_web/src/lib/useThemeToggle.ts index 9ae992f9..fcf96193 100644 --- a/decnet_web/src/lib/useThemeToggle.ts +++ b/decnet_web/src/lib/useThemeToggle.ts @@ -64,6 +64,15 @@ function animateSwap(next: Theme, x: number, y: number): void { return; } + /* Publish click coords as CSS custom properties BEFORE the + * transition starts — index.css uses these in the + * ::view-transition-new(root) default rule so the new pseudo + * is already clipped to circle(0) at this point on its first + * paint. Without this, the new layer renders at default styles + * (full size) for one frame before the animation registers. */ + document.documentElement.style.setProperty('--reveal-x', `${x}px`); + document.documentElement.style.setProperty('--reveal-y', `${y}px`); + const transition = docVT.startViewTransition(apply)!; transition.ready.then(() => { const endRadius = Math.hypot(