Skip to content

Commit 81a34f3

Browse files
committed
feat(site): camera-snap theme toggle (replaces the laggy scan)
Swap the View-Transition scan reveal — which lagged because it animated a blurred filter over a full-page snapshot — for a quick white "screenshot" flash that masks an instant theme swap, like snapping a photo of the repo. It's just one fading overlay (GPU-cheap, no snapshot), so there's no lag. Reduced-motion still flips instantly. Removes lib/themeScan.ts + the ::view-transition CSS.
1 parent 799bfb1 commit 81a34f3

4 files changed

Lines changed: 51 additions & 52 deletions

File tree

website/app/global.css

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -153,31 +153,31 @@
153153
}
154154

155155
/* ════════════════════════════════════════════════════════════════════════
156-
Theme scan — the View Transitions API "develops" the new theme down the page
157-
top→bottom, with a glowing cyan scan edge leading the front. Triggered only
158-
by the theme toggle (ThemeToggle.tsx); falls back to an instant flip where
159-
the API or motion preference says no.
156+
Theme snaptoggling the theme fires a quick white "screenshot" flash that
157+
masks an instant swap, like snapping a photo of the repo (see snapToTheme in
158+
lib/themeSnap.ts). One fading overlay, GPU-cheap — no full-page snapshot, so
159+
no lag. Reduced-motion skips the flash (snapToTheme does a plain instant flip).
160160
──────────────────────────────────────────────────────────────────────── */
161-
@media (prefers-reduced-motion: no-preference) {
162-
::view-transition-old(root),
163-
::view-transition-new(root) {
164-
/* drop the default cross-fade — we own the reveal */
165-
animation: none;
166-
mix-blend-mode: normal;
167-
}
168-
/* old theme stays put underneath; the new theme is clipped in from the top */
169-
::view-transition-new(root) {
170-
/* Steady, near-even travel so you watch the line sweep down. No drop-shadow
171-
glow here on purpose — a blurred shadow of a full-page snapshot animating
172-
every frame is the main source of jank. The clip wipe alone is GPU-cheap. */
173-
animation: theme-scan 720ms cubic-bezier(0.45, 0.05, 0.55, 0.95);
174-
}
161+
.theme-flash {
162+
position: fixed;
163+
inset: 0;
164+
z-index: 9999;
165+
pointer-events: none;
166+
background: #ffffff;
167+
opacity: 0;
168+
will-change: opacity;
175169
}
176-
@keyframes theme-scan {
177-
from {
178-
clip-path: inset(0 0 100% 0);
170+
.theme-flash.is-on {
171+
animation: theme-snap 440ms ease-out forwards;
172+
}
173+
@keyframes theme-snap {
174+
0% {
175+
opacity: 0;
176+
}
177+
16% {
178+
opacity: 0.92;
179179
}
180-
to {
181-
clip-path: inset(0 0 0 0);
180+
100% {
181+
opacity: 0;
182182
}
183183
}

website/components/site/ThemeToggle.tsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import { useEffect, useState } from 'react';
44
import { useTheme } from 'next-themes';
5-
import { scanToTheme } from '@/lib/themeScan';
5+
import { snapToTheme } from '@/lib/themeSnap';
66

77
/**
88
* Sun⇄moon toggle that *morphs*: in dark mode a cutout circle carves the orb
@@ -21,11 +21,9 @@ export function ThemeToggle() {
2121
const isLight = mounted && resolvedTheme !== 'dark';
2222
const label = mounted ? `Switch to ${isLight ? 'dark' : 'light'} mode` : 'Toggle theme';
2323

24-
// A scanline "develops" the page into the new theme top→bottom via the View
25-
// Transitions API (see ::view-transition-new(root) in global.css). flushSync
26-
// makes next-themes apply the class synchronously so the snapshot captures the
27-
// new theme. Instant flip where the API is missing or motion is reduced.
28-
const runToggle = () => scanToTheme(setTheme, isLight ? 'dark' : 'light');
24+
// A camera-style flash "snaps" the page into the new theme (see .theme-flash
25+
// in global.css). Instant flip under reduced-motion.
26+
const runToggle = () => snapToTheme(setTheme, isLight ? 'dark' : 'light');
2927

3028
return (
3129
<button

website/lib/themeScan.ts

Lines changed: 0 additions & 23 deletions
This file was deleted.

website/lib/themeSnap.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/**
2+
* Flip the theme with a camera-style "snap": a quick white flash masks an
3+
* instant theme swap — like snapping a screenshot of the repo. GPU-cheap (one
4+
* fading overlay, no full-page snapshot), so there's no lag. Reduced-motion gets
5+
* a plain instant flip.
6+
*
7+
* Used by the theme toggle (ThemeToggle).
8+
*/
9+
export function snapToTheme(setTheme: (theme: string) => void, next: string): void {
10+
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
11+
setTheme(next);
12+
return;
13+
}
14+
15+
const flash = document.createElement('div');
16+
flash.className = 'theme-flash';
17+
document.body.appendChild(flash);
18+
19+
// Next frame: run the flash. Swap the theme at its bright peak so the change
20+
// is hidden behind the flash, then clean up after it fades.
21+
requestAnimationFrame(() => flash.classList.add('is-on'));
22+
window.setTimeout(() => setTheme(next), 110);
23+
window.setTimeout(() => flash.remove(), 460);
24+
}

0 commit comments

Comments
 (0)