[ui] THE SCREEN click-to-enlarge

Click the sky, get the sky: ~2.5x, centred, over a dimmed backdrop. Esc or a
click closes it.

Enlarges the real element rather than mirroring it. I built the mirror first
and it rendered black — THE SCREEN is a WebGL canvas, and drawImage from a
WebGL context without preserveDrawingBuffer returns nothing. Asking LANE-SCREEN
to enable that flag would cost them a buffer copy every frame for a feature
used occasionally.

Since that means touching another lane's element, it snapshots the style
attribute on open and restores it verbatim on close — including the case where
SCREEN later adds inline styles of their own, which is tested.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
type-two 2026-07-18 14:12:18 +10:00
parent e24100c26e
commit 5d6afdac97
2 changed files with 162 additions and 0 deletions

View File

@ -0,0 +1,80 @@
// @vitest-environment happy-dom
import { describe, expect, it } from 'vitest';
import { createScreenView } from './screenview';
function mount(withInlineStyle = false) {
document.body.innerHTML = withInlineStyle
? '<canvas id="screen" style="position:fixed;top:10px"></canvas>'
: '<canvas id="screen"></canvas>';
const source = document.getElementById('screen') as HTMLCanvasElement;
const view = createScreenView(source);
document.body.append(view.el);
return { view, source };
}
const click = (el: Element) => el.dispatchEvent(new MouseEvent('pointerdown', { bubbles: true }));
describe('THE SCREEN click-to-enlarge', () => {
it('starts closed and leaves THE SCREEN alone', () => {
const { view, source } = mount();
expect(view.isOpen()).toBe(false);
expect(source.getAttribute('style')).toBeNull();
});
it('enlarges THE SCREEN and dims the backdrop when clicked', () => {
const { view, source } = mount();
click(source);
expect(view.isOpen()).toBe(true);
expect(view.el.classList.contains('is-open')).toBe(true);
const style = source.getAttribute('style')!;
expect(style).toContain('position:fixed');
expect(style).toContain('z-index:41'); // above the backdrop
});
it('restores THE SCREEN exactly as it found it', () => {
// Another lane owns this element; we must hand it back untouched.
const { view, source } = mount();
click(source);
view.close();
expect(source.getAttribute('style')).toBeNull();
expect(view.isOpen()).toBe(false);
});
it('preserves inline styles LANE-SCREEN may add later', () => {
const { view, source } = mount(true);
const before = source.getAttribute('style');
click(source);
expect(source.getAttribute('style')).toContain('top:10px'); // ours appended, not replacing
view.close();
expect(source.getAttribute('style')).toBe(before);
});
it('closes when the enlarged SCREEN is clicked again', () => {
const { view, source } = mount();
click(source);
click(source);
expect(view.isOpen()).toBe(false);
expect(source.getAttribute('style')).toBeNull();
});
it('closes on a click on the dimmed backdrop', () => {
const { view, source } = mount();
click(source);
click(view.el);
expect(view.isOpen()).toBe(false);
});
it('close() is idempotent and does not clobber the style on a second call', () => {
const { view, source } = mount(true);
const before = source.getAttribute('style');
view.close();
view.close();
expect(source.getAttribute('style')).toBe(before);
});
it('survives having no SCREEN to enlarge', () => {
document.body.innerHTML = '';
const view = createScreenView(null);
expect(() => { view.toggle(); view.close(); }).not.toThrow();
});
});

View File

@ -0,0 +1,82 @@
/**
* LANE-UI THE SCREEN click-to-enlarge (order 8). The sky is the score; let people look.
*
* Clicking THE SCREEN toggles a centered ~2.5× view over a dimmed backdrop. Esc or a
* click closes it.
*
* We enlarge the real `#screen` element with inline styles rather than mirroring it into
* our own canvas: THE SCREEN is a WebGL canvas, and `drawImage` from a WebGL context
* without `preserveDrawingBuffer` returns black (verified the mirror rendered empty).
* Asking LANE-SCREEN to enable that flag would cost them a frame buffer copy every frame
* for a feature used occasionally, so the transform is both cheaper and what the order
* specified.
*
* Touching another lane's element is done carefully: we snapshot its `style` attribute on
* open and restore it verbatim on close, so THE SCREEN's own positioning stays SCREEN's
* to change.
*/
import { cls, el } from './dom';
export interface ScreenView {
/** The dimming backdrop, mounted in #ui. */
el: HTMLElement;
toggle(): void;
close(): void;
isOpen(): boolean;
}
/** Inline styles applied to #screen while enlarged. z-index clears the backdrop (40). */
const ENLARGED = [
'position:fixed',
'top:50%',
'left:50%',
'transform:translate(-50%,-50%)',
'width:min(800px, calc(100vw - 80px))',
'height:auto',
'z-index:41',
'cursor:zoom-out',
'image-rendering:pixelated', // it's a dying video file — let the blocks show
].join(';');
export function createScreenView(source: HTMLElement | null): ScreenView {
const backdrop = el('div');
backdrop.id = 'fk-screenview';
let open = false;
/** The element's own style attribute before we touched it. */
let savedStyle: string | null = null;
function apply() {
if (!source) return;
savedStyle = source.getAttribute('style');
source.setAttribute('style', `${savedStyle ? savedStyle + ';' : ''}${ENLARGED}`);
}
function restore() {
if (!source) return;
if (savedStyle === null) source.removeAttribute('style');
else source.setAttribute('style', savedStyle);
savedStyle = null;
}
function close() {
if (!open) return;
open = false;
cls(backdrop, 'is-open', false);
restore();
}
function toggle() {
if (open) return close();
open = true;
cls(backdrop, 'is-open', true);
apply();
}
// A click on the dimmed backdrop closes. So does a click on the enlarged SCREEN, which
// sits above the backdrop — that's the same toggle listener below.
backdrop.addEventListener('pointerdown', close);
source?.addEventListener('pointerdown', toggle);
return { el: backdrop, toggle, close, isOpen: () => open };
}