diff --git a/fktry/src/ui/screenview.test.ts b/fktry/src/ui/screenview.test.ts
new file mode 100644
index 0000000..250f1c5
--- /dev/null
+++ b/fktry/src/ui/screenview.test.ts
@@ -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
+ ? ''
+ : '';
+ 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();
+ });
+});
diff --git a/fktry/src/ui/screenview.ts b/fktry/src/ui/screenview.ts
new file mode 100644
index 0000000..b9ab498
--- /dev/null
+++ b/fktry/src/ui/screenview.ts
@@ -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 };
+}