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:
+51
-15
@@ -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
|
* 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
|
* 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
|
* 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
|
* cover the video — and since the crop only ever covers the video's rect, a
|
||||||
* still never contains another still either.
|
* 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) {
|
export function startCorners(context) {
|
||||||
const { element } = context;
|
const { element } = context;
|
||||||
@@ -209,13 +237,19 @@ export function startCorners(context) {
|
|||||||
|
|
||||||
// The three non-home corners, in clockwise order starting after the video.
|
// The three non-home corners, in clockwise order starting after the video.
|
||||||
const slots = [1, 2, 3].map((step) => (home + step) % CORNERS);
|
const slots = [1, 2, 3].map((step) => (home + step) % CORNERS);
|
||||||
const images = slots.map(() => {
|
|
||||||
|
// 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');
|
const img = document.createElement('img');
|
||||||
layer.appendChild(img);
|
layer.appendChild(img);
|
||||||
return img;
|
cells.push({ img, corner, depth });
|
||||||
});
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let slot = 0;
|
let next = 0;
|
||||||
let size;
|
let size;
|
||||||
|
|
||||||
function layout() {
|
function layout() {
|
||||||
@@ -226,12 +260,12 @@ export function startCorners(context) {
|
|||||||
const spot = cornerPosition(home, size, viewport, margin);
|
const spot = cornerPosition(home, size, viewport, margin);
|
||||||
place(element, spot.x, spot.y);
|
place(element, spot.x, spot.y);
|
||||||
|
|
||||||
images.forEach((img, i) => {
|
for (const cell of cells) {
|
||||||
const at = cornerPosition(slots[i], size, viewport, margin);
|
const at = cornerStackPosition(cell.corner, cell.depth, size, viewport, margin);
|
||||||
img.style.width = `${size.width}px`;
|
cell.img.style.width = `${size.width}px`;
|
||||||
img.style.height = `${size.height}px`;
|
cell.img.style.height = `${size.height}px`;
|
||||||
place(img, at.x, at.y);
|
place(cell.img, at.x, at.y);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function shoot() {
|
function shoot() {
|
||||||
@@ -243,10 +277,12 @@ export function startCorners(context) {
|
|||||||
const shot = context.capture.grab(rect);
|
const shot = context.capture.grab(rect);
|
||||||
if (!shot) return;
|
if (!shot) return;
|
||||||
|
|
||||||
const img = images[slot];
|
const cell = cells[next];
|
||||||
img.src = shot;
|
cell.img.src = shot;
|
||||||
img.classList.add('visible');
|
cell.img.classList.add('visible');
|
||||||
slot = (slot + 1) % images.length;
|
// Re-append so the freshest still paints over the rest of its pile.
|
||||||
|
layer.appendChild(cell.img);
|
||||||
|
next = (next + 1) % cells.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
layout();
|
layout();
|
||||||
@@ -256,7 +292,7 @@ export function startCorners(context) {
|
|||||||
return function stop() {
|
return function stop() {
|
||||||
clearInterval(timer);
|
clearInterval(timer);
|
||||||
window.removeEventListener('resize', layout);
|
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