Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "univerlab-web",
"version": "1.19.5",
"version": "1.19.6",
"description": "<h1 align=\"center\">UniverLab — landing</h1>",
"scripts": {
"dev": "astro dev",
Expand Down
11 changes: 5 additions & 6 deletions src/__tests__/home-lantern.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,15 +287,14 @@ describe('Home — tuning into an experiment', () => {
expect(GLOBAL).toMatch(/@media \(prefers-reduced-motion: reduce\)[\s\S]*animation: none !important/);
});

it('shows static on the screen while it tunes', () => {
expect(TUNE).toContain("classList.add('is-tuning')");
expect(LANTERN).toMatch(/screen\.classList\.contains\('is-tuning'\)/);
expect(LANTERN).toContain('drawSnow(now)');
it('navigates at once, with no static on the screen', () => {
expect(TUNE).not.toContain('is-tuning');
expect(TUNE).not.toContain('setTimeout');
expect(LANTERN).not.toMatch(/snow|is-tuning/i);
});

it('clears the static and the shared name on Back restore', () => {
it('clears the shared name on Back restore', () => {
expect(HOME).toContain("addEventListener('pageshow', untune)");
expect(TUNE).toContain("classList.remove('is-tuning')");
expect(TUNE).toContain("removeProperty('view-transition-name')");
expect(LAYOUT).not.toContain('crt-restore');
});
Expand Down
44 changes: 0 additions & 44 deletions src/scripts/lantern.ts
Original file line number Diff line number Diff line change
Expand Up @@ -406,23 +406,6 @@ export function startLantern(): void {
g.putImageData(data, 0, 0);
return tile;
})();
// The static a tuning screen shows before its experiment opens (tune.ts):
// the same tile size, opaque greys instead of a faint grain.
const snow = (() => {
const tile = document.createElement('canvas');
tile.width = tile.height = 128;
const g = tile.getContext('2d');
if (!g) return tile;
const data = g.createImageData(128, 128);
for (let i = 0; i < data.data.length; i += 4) {
const v = Math.random() * 255;
data.data[i] = data.data[i + 1] = data.data[i + 2] = v;
data.data[i + 3] = 255;
}
g.putImageData(data, 0, 0);
return tile;
})();

// Cost model (measured with a 4x-throttled CPU): every fill on this canvas
// is raster work on the main thread — redrawn at full size each frame, and
// resized each frame, the film alone took a third of it. So it runs at half
Expand All @@ -434,7 +417,6 @@ export function startLantern(): void {
let fw = 0;
let fh = 0;
let pattern: CanvasPattern | null = null;
let snowPattern: CanvasPattern | null = null;
let vignette: CanvasGradient | null = null;
let lastFilm = 0;
let onView = true;
Expand All @@ -446,7 +428,6 @@ export function startLantern(): void {
film.width = fw;
film.height = fh;
pattern = ctx.createPattern(grain, 'repeat');
snowPattern = ctx.createPattern(snow, 'repeat');
vignette = ctx.createRadialGradient(
fw / 2,
fh / 2,
Expand All @@ -464,33 +445,8 @@ export function startLantern(): void {
onView = entry?.isIntersecting ?? true;
}).observe(screen);

/** Tuning: dense static that swells in, with a bright band rolling down. */
let tuneAt = 0;
const drawSnow = (now: number): void => {
if (!ctx) return;
tuneAt ||= now;
const k = Math.min(1, (now - tuneAt) / 90);
ctx.save();
ctx.translate(-Math.random() * 128, -Math.random() * 128);
ctx.globalAlpha = 0.25 + 0.6 * k;
ctx.fillStyle = snowPattern ?? '#888';
ctx.fillRect(0, 0, fw + 128, fh + 128);
ctx.restore();
const band = fh * 0.22;
const y = (((now - tuneAt) / 170) * (fh + band)) % (fh + band) - band;
ctx.globalAlpha = 0.18;
ctx.fillStyle = '#fff';
ctx.fillRect(0, y, fw, band);
ctx.globalAlpha = 1;
};

const drawFilm = (now: number): void => {
if (!ctx || !onView || fw === 0 || fh === 0) return;
if (screen.classList.contains('is-tuning')) {
drawSnow(now);
return;
}
tuneAt = 0;
if (now - lastFilm < FILM_MS) return;
lastFilm = now;
const w = fw;
Expand Down
16 changes: 6 additions & 10 deletions src/scripts/tune.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
/** Home → experiment: the lantern's screen tunes in to the experiment.
*
* A short burst of static (lantern.ts paints it while `#screen.is-tuning`),
* then the browser navigates and the cross-document view transition zooms
* The browser navigates at once and the cross-document view transition zooms
* the source into the destination's hero: both carry `exp-<id>` in the
* `exp-zoom` class (ExperimentLayout's hero, global.css's timing). Engines
* without cross-document view transitions keep the static and load plainly.
* Under reduced motion it navigates at once.
* without cross-document view transitions load plainly. Under reduced motion
* it navigates without naming the source.
*/
const STATIC_MS = 170;

export function tuneNavigate(href: string, exp: string, source: HTMLElement): void {
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
Expand All @@ -16,14 +14,12 @@ export function tuneNavigate(href: string, exp: string, source: HTMLElement): vo
}
source.style.setProperty('view-transition-name', `exp-${exp}`);
source.style.setProperty('view-transition-class', 'exp-zoom');
document.getElementById('screen')?.classList.add('is-tuning');
window.setTimeout(() => window.location.assign(href), STATIC_MS);
window.location.assign(href);
}

/** Back restores the bfcache snapshot mid-tune: drop the static and the
* shared name, or the next click would carry two `exp-*` names. */
/** Back restores the bfcache snapshot mid-tune: drop the shared name, or the
* next click would carry two `exp-*` names. */
export function untune(): void {
document.getElementById('screen')?.classList.remove('is-tuning');
document.querySelectorAll<HTMLElement>('#screen, .plate[data-exp]').forEach((el) => {
el.style.removeProperty('view-transition-name');
el.style.removeProperty('view-transition-class');
Expand Down
2 changes: 1 addition & 1 deletion src/views/Home.astro
Original file line number Diff line number Diff line change
Expand Up @@ -582,7 +582,7 @@ const skyLines = Array.from({ length: 5 }, (_, i) => ({
import { shouldScreenNavigate } from '../lib/lantern-navigate';

// A machine opens its experiment page by tuning the screen in (tune.ts):
// static, then the screen zooms into the page's hero. Pointing at a machine
// the screen zooms into the page's hero. Pointing at a machine
// already projects it, so the screen is the source while it is on view; a
// machine clicked with the screen scrolled away zooms from itself.
const screen = document.getElementById('screen');
Expand Down
Loading