deckhearth/lib/scanner-video-layout.js
varutasu 28bdd6aa5b
Fix scanner multi-card flow, frame overlay, and rate limits. (#166)
Allow consecutive scans without refresh by resetting trackers and counting vision rate limits once per card. Add a fixed card guide, widen detection bounds, and correct object-cover overlay math.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-15 18:48:05 -05:00

98 lines
2.8 KiB
JavaScript

/** Trading card aspect ratio (width / height). */
export const CARD_ASPECT_RATIO = 5 / 7;
/**
* Layout math for a video element using CSS object-cover inside a container.
*/
export function computeObjectCoverLayout(videoWidth, videoHeight, containerWidth, containerHeight) {
if (!videoWidth || !videoHeight || !containerWidth || !containerHeight) {
return null;
}
const videoAspect = videoWidth / videoHeight;
const containerAspect = containerWidth / containerHeight;
let renderedWidth;
let renderedHeight;
let offsetX;
let offsetY;
if (videoAspect > containerAspect) {
renderedHeight = containerHeight;
renderedWidth = containerHeight * videoAspect;
offsetX = (containerWidth - renderedWidth) / 2;
offsetY = 0;
} else {
renderedWidth = containerWidth;
renderedHeight = containerWidth / videoAspect;
offsetX = 0;
offsetY = (containerHeight - renderedHeight) / 2;
}
return {
videoWidth,
videoHeight,
containerWidth,
containerHeight,
renderedWidth,
renderedHeight,
offsetX,
offsetY,
};
}
/** Map video-pixel bounds to percentage styles within an object-cover container. */
export function videoBoundsToContainerStyle(bounds, layout) {
const {
videoWidth,
videoHeight,
containerWidth,
containerHeight,
renderedWidth,
renderedHeight,
offsetX,
offsetY,
} = layout;
const left = offsetX + (bounds.x / videoWidth) * renderedWidth;
const top = offsetY + (bounds.y / videoHeight) * renderedHeight;
const width = (bounds.width / videoWidth) * renderedWidth;
const height = (bounds.height / videoHeight) * renderedHeight;
return {
left: `${(left / containerWidth) * 100}%`,
top: `${(top / containerHeight) * 100}%`,
width: `${(width / containerWidth) * 100}%`,
height: `${(height / containerHeight) * 100}%`,
};
}
/** Centered card guide frame in container pixel coordinates. */
export function computeCardGuideBounds(containerWidth, containerHeight) {
let width = containerWidth * 0.78;
let height = width / CARD_ASPECT_RATIO;
const maxHeight = containerHeight * 0.72;
if (height > maxHeight) {
height = maxHeight;
width = height * CARD_ASPECT_RATIO;
}
return {
left: (containerWidth - width) / 2,
top: (containerHeight - height) / 2,
width,
height,
};
}
/** Card guide as percentage styles for an object-cover container. */
export function cardGuideToContainerStyle(layout) {
const guide = computeCardGuideBounds(layout.containerWidth, layout.containerHeight);
return {
left: `${(guide.left / layout.containerWidth) * 100}%`,
top: `${(guide.top / layout.containerHeight) * 100}%`,
width: `${(guide.width / layout.containerWidth) * 100}%`,
height: `${(guide.height / layout.containerHeight) * 100}%`,
};
}