PTENES
MODULE 2.3

🎯 Coordinates, selectors & bounding boxes

The detail that gives it a professional feel: the real bbox becomes the cursor's target on the canvas. Here you learn coordinate mapping, robust selectors, and the zoom/highlight region.

7
Topics
~30
Minutes
Inter.
Level
Accuracy
Type
bbox → center → cursor VIEWPORT 1280×800 {x,y,w,h} WIN_L + sx SHOT_T + sy CANVAS 1920×1080 tip at center the real bbox = the target for the cursor, highlight, and zoom
1

📍 Viewport-relative coordinates

The bboxes are relative to the viewport. In the video, they become canvas coordinates by adding the window offset.

Main Concept

The capture runs in a fixed viewport (e.g., 1280×800). Every bbox comes from this space. On the 1920×1080 canvas, the screenshot is drawn inside the window and each point is mapped.

The mapping (point in the shot → canvas)
// WIN_L = (1920 - VW) / 2 · SHOT_T = WIN_T + TITLE_H
canvasX = WIN_L + sx;
canvasY = SHOT_T + sy;
// the center of the mapped bbox = where the cursor goes
📐
WIN_L
center X
📏
SHOT_T
top + title
🎯
center
cursor target
2

📐 Capture the bbox for each target

For each step with target, capture.mjs reads the rect and rounds to {x,y,w,h}.

How capture reads the bbox (real excerpt)
const box = evalJSON(`(()=>{
const el = finder(target);
if(!el) return null;
const r = el.getBoundingClientRect();
return{x:Math.round(r.x), y:Math.round(r.y),
w:Math.round(r.width), h:Math.round(r.height)};
})()`);
⚠️
Target not found = bbox null

If the selector doesn’t match, capture records target: null and prints ! alvo não encontrado no passo X. It's a clear sign the selector is wrong — fix it before rendering.

3

🧷 Robust selectors

data-testid, role, and visible text survive state changes. The refs @eN no.

✓ Stable selectors
  • ✓ [data-testid="gerar"]
  • ✓ role + accessible name
  • ✓ Visible text: {tag:"button",text:"Gerar"}
  • ✓ Attributes: input[name=height]
✗ Fragile selectors
  • ✗ Refs @eN on screens that change
  • ✗ Generated classes (CSS-in-JS) that change during the build
  • ✗ positional nth-child in a dynamic layout
  • ✗ Text with accent/space that doesn't match exactly
💡
Refs shift after "Generate"

In the POC, when the "download PNG" link appeared, all the refs shifted. So, in a mutable state, use CSS selectors or {tag,text} — and take a new snapshot when the DOM changes significantly.

4

🎯 The cursor aims at the center of the box

The cursor is a global SVG with the hotspot at its tip. The tween compensates for the offset so the tip lands in the center.

Cursor mechanics
1
Cursor is global, not per scene

A single #cursor animated on the main timeline — moves continuously as the screenshots change.

2
Hotspot at the end

The pointer sits at ~(6,3) inside the 42px SVG. The tween uses x = alvoX - 6, y = alvoY - 3.

3
Click = pulse + ripple

scale:.82 yoyo + one #ripple amber that expands over the target at the moment of the click.

💡
Curved easing avoids a robotic feel

The movement uses duration:.7, ease:"power3.inOut", starting ~0.35s after the scene appears. The cursor reaches the target before the main explanation.

5

🔍 Zoom / highlight region

The same bbox positions the highlight ring and the zoom push-in. One correct coordinate serves all three effects.

📊 Three uses for the same bbox
Cursor
The pointer lands at the center of the box.
Highlight (.hlbox)
Amber ring with a glow, ~6px clearance, positioned over the box.
Zoom
transformOrigin in the center of the target.
Zoom on the result (zoom:true)
// cinematic push-in on the "ta-da"
gsap.to(img, { scale: 1.12,
transformOrigin: `${cx}px ${cy}px`, duration: narrDur });
💡
Highlight focuses without covering

O .hlbox is just box-shadow (ring + glow): comes in with back.out and pulses, drawing the eye to the active control without covering anything.

6

📜 Handle a target that’s off-screen

If the target is below the fold, scroll to it before capturing and measuring—and do both after the same scroll.

⚠️
Scrolling is still manual

capture.mjs doesn't scroll the page today. On long screens, control agent-browser manually (scrollIntoView) before the screenshot. Add scroll/scrollTo is backlog item #1.

💡
The bboxes are viewport-relative

Because the bbox is measured in the current viewport, it matches the screenshot from that scroll position — as long as the screenshot and measurement are taken after of the same scrollIntoView.

7

✅ Check that you selected the right element

Compare the bbox with the screenshot before rendering. Capture prints each bbox in the log for a quick check.

capture.mjs log (verification)
> viewport 1280x800; opening http://localhost:8000/
[00] home: shot=00-home.png target=—
[01] prompt: shot=01-prompt.png target=129,252 482x96
[02] size: shot=02-size.png target=179,501 44x26
! target not found in height step
✓ Check the capture
  • ✓ Bounding box falls over the visible control in the shot
  • ✓ No "target not found" warning
  • ✓ plausible w/h (not 0×0 or the entire screen)
✗ Warning signs
  • ✗ target=— where there should have been a bbox
  • ✗ Bounding box over the wrong element
  • ✗ Coordinates outside the shot bounds

🎯 Module summary

  • ✓ viewport-relative bbox → canvasX = WIN_L + sx, canvasY = SHOT_T + sy
  • ✓ capture reads getBoundingClientRect; no target match = bbox null + warning
  • ✓ prefer data-testid / role / visible text over refs @eN
  • ✓ global cursor with hotspot at the tip; click = pulse + ripple
  • ✓ the same bbox is used for the cursor, highlight, and zoom; check the log before rendering
Next module
2.4
⏳ Long pages, React inputs & multi-state
The tricky cases in real demos—and what already works vs. what’s on the roadmap.
Go to module 2.4 →