teaser: the reject shot redone (7-11 s, 4 s): the common rejections storebound catches as real scan finding cards landing one per beat (2.1(a) demo account, 5.1.1(v) account deletion, 5.1.2(i) ATT from tests/fixtures/scan/teaser; ITMS-91064 from the new teaser-manifest fixture), guideline + one plain line each, then our own REJECTED stamp hits the stack on the beat at 9.5 s, no shake (one 4 px 1-frame jolt), a 3% push, the picture darkens into the cut; caption 'Rejected. Again.'; later shots +1.5 s (42.5 s, every cut on a beat, music follows the scenes, a thud per card and a hit on the stamp); render.ts --animatic --range a,b and --sheet a,b,fps, and the reject cards' boxes per frame in animatic-checks.json; tests: real finding lines, beat timing, and every card inside 6% safe margins at every frame
This commit is contained in:
parent
511716bfd5
commit
20a764b3ee
11 changed files with 205 additions and 86 deletions
|
|
@ -4,7 +4,7 @@ import { ASK_AT, BUILD_LINES, CAMERA, CAP_FADE, CAPTIONS, captionTimes, CONSOLE_
|
|||
DATA_SAFETY_CSV, DATA_SAFETY_ROWS, DECL_ASK, DECL_AT, DECL_LINES, DECL_STEP, DECL_TOTAL, DESK, DESK_SCENES, DURATION, END, END_CMD, END_URL,
|
||||
FINDINGS, FINDINGS_AT, FINDINGS_STEP, FIX_AT, FORM_AT, FORM_STEP, FORMAT, FORMS_COUNT_AT, FPS, GAME_SCREENS, GAME_SPEED, MONTAGE,
|
||||
PILE, PILE_TITLES, PRICE_ROWS, PRIVACY_ANSWERS, PRIVACY_STEPS, PRODUCT_LINES, PROMPT, PROMPT_AT, PROMPT_CPS, PROMPT_SENT,
|
||||
REJECT, REJECT_GROW, REJECT_SLAM, RESCAN_AT, RESCAN_LINE, RISKS_LINE, SCAN_CMD, SCENES, SHOT_LANGS, SHOT_SIZES, SHOTS_AT, SHOTS_ROW, SHOTS_STEP, scene, veil } from './script.js';
|
||||
REJECT_DARK, REJECT_LAND, REJECT_PUSH, REJECT_STEP, REJECTS, STAMP, RESCAN_AT, RESCAN_LINE, RISKS_LINE, SCAN_CMD, SCENES, SHOT_LANGS, SHOT_SIZES, SHOTS_AT, SHOTS_ROW, SHOTS_STEP, scene, veil } from './script.js';
|
||||
|
||||
const q = new URLSearchParams(location.search);
|
||||
const W = FORMAT.w, H = FORMAT.h;
|
||||
|
|
@ -170,22 +170,29 @@ function pile(t) {
|
|||
});
|
||||
}
|
||||
|
||||
// ---------- 3 reject: one finding grows to fill the frame ----------
|
||||
const findEl = $('#finding');
|
||||
{
|
||||
const m = REJECT.head.match(/^(BLOCKER) (\S+) · App Store · (.+)$/);
|
||||
findEl.querySelector('.l1').innerHTML = `<span class="sev">${m[1]}</span><span class="gl">${esc(m[2])}</span><span>· App Store ·</span><span>${esc(m[3])}</span>`;
|
||||
findEl.querySelector('.l2').innerHTML = esc(REJECT.at).replace(/^(at \S+)/, '<span class="loc">$1</span>');
|
||||
}
|
||||
// ---------- 3 reject: real scan findings land one per beat, then the REJECTED stamp ----------
|
||||
const rejEl = $('#rejects'), stampEl = $('#stamp');
|
||||
rejEl.innerHTML = REJECTS.map(f => {
|
||||
const m = f.head.match(/^(\S+)\s+.*? · App Store · (.+)$/);
|
||||
return `<div class="rj"><div class="rule"><span class="sev">${m[1]}</span><span>${esc(f.rule)}</span></div><div class="title">${esc(f.title)}</div><div class="real">${esc(m[2])}</div></div>`;
|
||||
}).join('');
|
||||
const rjEls = [...rejEl.children];
|
||||
function reject(t) {
|
||||
const s = scene('reject'); if (t < s.start - 1 || t > s.end + 1) return;
|
||||
const r = t - s.start;
|
||||
// the slam: in at once, overshooting scale down to 1 with a short shake; then a push onto the rule
|
||||
const sp = prog(r, -LEAD, REJECT_SLAM.d + LEAD);
|
||||
set(findEl, 'opacity', String(clamp(sp * 4)));
|
||||
const z = lerp(REJECT_SLAM.from, 1, eoc(sp)) * lerp(REJECT_GROW[0], REJECT_GROW[1], ecam(prog(r, REJECT_SLAM.d, s.end - s.start - REJECT_SLAM.d)));
|
||||
const shake = r < 0.45 && r > REJECT_SLAM.d ? Math.sin(r * 90) * 14 * (1 - (r - REJECT_SLAM.d) / (0.45 - REJECT_SLAM.d)) : 0;
|
||||
set(findEl, 'transform', `translate(calc(-50% + ${shake.toFixed(1)}px), -50%) scale(${z.toFixed(4)})`);
|
||||
const r = t - s.start, len = s.end - s.start;
|
||||
const jolt = r >= STAMP.at && r < STAMP.at + 1 / 30 ? STAMP.jolt : 0;
|
||||
const z = lerp(REJECT_PUSH[0], REJECT_PUSH[1], esin(prog(r, 0, len)));
|
||||
set(rejEl, 'transform', `translate(calc(-50% + ${jolt}px), calc(-50% + ${jolt / 2}px)) scale(${z.toFixed(4)})`);
|
||||
set(rejEl, 'filter', `brightness(${(1 - 0.45 * eio(prog(r, REJECT_DARK, len - REJECT_DARK))).toFixed(3)})`);
|
||||
rjEls.forEach((el, i) => {
|
||||
const lp = prog(r, i * REJECT_STEP - (i ? 0 : LEAD), REJECT_LAND.d + (i ? 0 : LEAD));
|
||||
set(el, 'opacity', String(clamp(lp * 4)));
|
||||
set(el, 'transform', `scale(${lerp(REJECT_LAND.from, 1, eoc(lp)).toFixed(4)})`);
|
||||
});
|
||||
const sp = prog(r, STAMP.at - STAMP.d, STAMP.d);
|
||||
set(stampEl, 'opacity', String(r < STAMP.at - STAMP.d ? 0 : clamp(sp * 3)));
|
||||
set(stampEl, 'filter', rejEl.style.filter);
|
||||
set(stampEl, 'transform', `translate(calc(-50% + ${jolt}px), -50%) rotate(${STAMP.rot}deg) scale(${lerp(STAMP.from, 1, eoc(sp)).toFixed(4)})`);
|
||||
}
|
||||
|
||||
// ---------- 4 ask: the request to the agent ----------
|
||||
|
|
@ -419,6 +426,13 @@ function end(t) {
|
|||
// the caret: solid while typing, then a steady blink (two per second, on the beat) through the hold
|
||||
set(endCaret, 'opacity', r < END.full || Math.floor((r - END.full) * 2) % 2 === 0 ? '1' : '0');
|
||||
}
|
||||
/** The reject cards' boxes (and the stamp's) as drawn at time t, for the safe-margin check. */
|
||||
function rejectLayout(t) {
|
||||
seek(t);
|
||||
const k = W / stage.getBoundingClientRect().width;
|
||||
const box = el => { const b = el.getBoundingClientRect(); return { x: b.x * k, y: b.y * k, w: b.width * k, h: b.height * k }; };
|
||||
return { t, cards: rjEls.filter(el => Number(el.style.opacity) > 0).map(box), stamp: Number(stampEl.style.opacity) > 0 ? box(stampEl) : null };
|
||||
}
|
||||
/** Layout data for the animatic checks: the end card's install line, as drawn at time t. */
|
||||
function layout(t) {
|
||||
seek(t);
|
||||
|
|
@ -462,7 +476,7 @@ const ready = (async () => {
|
|||
for (let i = 0; i < all.length; i += 8) await Promise.all(all.slice(i, i + 8).map(decode));
|
||||
seek(0);
|
||||
})();
|
||||
window.teaser = { ready, seek, layout, DURATION, FPS, W, H, captionTimes };
|
||||
window.teaser = { ready, seek, layout, rejectLayout, DURATION, FPS, W, H, captionTimes };
|
||||
|
||||
if (q.has('t')) ready.then(() => seek(Number(q.get('t'))));
|
||||
if (q.has('play')) ready.then(() => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue