Stack corner stills at staggered offsets
A full corner used to swap its single still in place, so a fresh screenshot was indistinguishable from the one it replaced. Each corner now holds three stills nudged inward by 9% of the frame per depth, and the newest one moves to the top of the layer, leaving the edges of the ones below visible. Fill order is slot-major, so a new shot never lands where the previous shot at that corner was. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tg15tLAuT6NrCE1ZxKVgDB
This commit is contained in:
+53
-17
@@ -195,12 +195,40 @@ export function cornerPosition(index, size, viewport, margin) {
|
||||
};
|
||||
}
|
||||
|
||||
// How many stills each corner holds, and how far apart they sit as a fraction
|
||||
// of the frame size. Deep enough that a corner reads as a pile, shallow enough
|
||||
// that the stack stays in its own quarter of the screen.
|
||||
const CORNER_STACK = 3;
|
||||
const CORNER_STACK_STEP = 0.09;
|
||||
|
||||
/**
|
||||
* Top-left offset for the `depth`-th still of a corner: the same spot as the
|
||||
* corner itself, nudged inward (toward the middle of the screen) once per
|
||||
* depth. Successive stills therefore never land on top of each other — each
|
||||
* one leaves the edges of the ones below it showing.
|
||||
*/
|
||||
export function cornerStackPosition(index, depth, size, viewport, margin) {
|
||||
const at = cornerPosition(index, size, viewport, margin);
|
||||
const right = index === 1 || index === 2;
|
||||
const bottom = index === 2 || index === 3;
|
||||
const step = depth * CORNER_STACK_STEP;
|
||||
|
||||
return {
|
||||
x: at.x + (right ? -1 : 1) * step * size.width,
|
||||
y: at.y + (bottom ? -1 : 1) * step * size.height,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The video parks in one random corner and stays there; every few seconds a
|
||||
* screenshot of it lands in the next of the three *other* corners, cycling
|
||||
* clockwise. Because the video's own corner is never a slot, a still can never
|
||||
* cover the video — and since the crop only ever covers the video's rect, a
|
||||
* still never contains another still either.
|
||||
*
|
||||
* Each corner stacks CORNER_STACK stills at staggered offsets, and the newest
|
||||
* shot always moves to the top of the layer, so a full corner still reads as
|
||||
* something new arriving rather than as an unchanged picture.
|
||||
*/
|
||||
export function startCorners(context) {
|
||||
const { element } = context;
|
||||
@@ -209,13 +237,19 @@ export function startCorners(context) {
|
||||
|
||||
// The three non-home corners, in clockwise order starting after the video.
|
||||
const slots = [1, 2, 3].map((step) => (home + step) % CORNERS);
|
||||
const images = slots.map(() => {
|
||||
const img = document.createElement('img');
|
||||
layer.appendChild(img);
|
||||
return img;
|
||||
});
|
||||
|
||||
let slot = 0;
|
||||
// Slot-major: consecutive shots move to the next corner, and the depth only
|
||||
// advances once every corner at the current depth has been filled.
|
||||
const cells = [];
|
||||
for (let depth = 0; depth < CORNER_STACK; depth += 1) {
|
||||
for (const corner of slots) {
|
||||
const img = document.createElement('img');
|
||||
layer.appendChild(img);
|
||||
cells.push({ img, corner, depth });
|
||||
}
|
||||
}
|
||||
|
||||
let next = 0;
|
||||
let size;
|
||||
|
||||
function layout() {
|
||||
@@ -226,12 +260,12 @@ export function startCorners(context) {
|
||||
const spot = cornerPosition(home, size, viewport, margin);
|
||||
place(element, spot.x, spot.y);
|
||||
|
||||
images.forEach((img, i) => {
|
||||
const at = cornerPosition(slots[i], size, viewport, margin);
|
||||
img.style.width = `${size.width}px`;
|
||||
img.style.height = `${size.height}px`;
|
||||
place(img, at.x, at.y);
|
||||
});
|
||||
for (const cell of cells) {
|
||||
const at = cornerStackPosition(cell.corner, cell.depth, size, viewport, margin);
|
||||
cell.img.style.width = `${size.width}px`;
|
||||
cell.img.style.height = `${size.height}px`;
|
||||
place(cell.img, at.x, at.y);
|
||||
}
|
||||
}
|
||||
|
||||
function shoot() {
|
||||
@@ -243,10 +277,12 @@ export function startCorners(context) {
|
||||
const shot = context.capture.grab(rect);
|
||||
if (!shot) return;
|
||||
|
||||
const img = images[slot];
|
||||
img.src = shot;
|
||||
img.classList.add('visible');
|
||||
slot = (slot + 1) % images.length;
|
||||
const cell = cells[next];
|
||||
cell.img.src = shot;
|
||||
cell.img.classList.add('visible');
|
||||
// Re-append so the freshest still paints over the rest of its pile.
|
||||
layer.appendChild(cell.img);
|
||||
next = (next + 1) % cells.length;
|
||||
}
|
||||
|
||||
layout();
|
||||
@@ -256,7 +292,7 @@ export function startCorners(context) {
|
||||
return function stop() {
|
||||
clearInterval(timer);
|
||||
window.removeEventListener('resize', layout);
|
||||
for (const img of images) img.remove();
|
||||
for (const cell of cells) cell.img.remove();
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user