Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions components/sections/CapabilitiesScene.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,17 @@ const ACCENTS = [
// at ~5 wheel notches each, plus the sticky 100vh.
const SCENE_LENGTH_VH = 440;
const TILT = 10;
/** Scroll progress at which the magnifier lens becomes interactive β€” the point
* where the label, headline and sub copy have all landed (the cards are still
* rising in below, and the lens only shows over rows anyway). */
const LENS_ARM_AT = 0.48;
/** How far a single card must have dealt in (0β†’1) before the lens works on it. */
const LENS_ARM_AT = 0.98;
/** Vertical breathing room kept between the scene and the viewport edges. */
const SCENE_PAD = 72;
const MONO = "'JetBrains Mono', ui-monospace, monospace";

const clamp = (v: number, a: number, b: number) => Math.min(b, Math.max(a, v));
/** Entrance progress of card `c` at scene progress `p` β€” mirrors the transform
* timeline below so the lens gate and the animation never disagree. */
const cardProgress = (c: number, p: number) =>
1 - Math.pow(1 - clamp((p - 0.4 - c * 0.07) / 0.22, 0, 1), 3);
const ease = (t: number) => 1 - Math.pow(1 - t, 3);
const fr = (n: number) => n - Math.floor(n);

Expand Down Expand Up @@ -227,15 +229,19 @@ export default function CapabilitiesScene() {
let over = -1;
// The lens only arms once the scene has finished dealing itself out β€”
// popping a magnifier over cards that are still flying in reads as a bug.
const armed = p >= LENS_ARM_AT;
// Hit-testing walks up to 30 elements with getBoundingClientRect, which
// forces layout each time. The answer can only change when the cursor
// moves, so reuse the previous result on idle frames.
const needsHitTest = armed && mouseMoved;
const needsHitTest = mouseMoved;
mouseMoved = false;
for (let c = 0; c < 4 && over < 0 && needsHitTest; c++) {
const d = capData[c];
if (!d) continue;
// Per-card gate: each card deals in on its own schedule, so a single
// scene-wide threshold would arm the lens over cards that are still
// flying in (and the lens content is a static snapshot, so it would
// sit offset from the real card). Only cards that have landed qualify.
if (cardProgress(c, p) < LENS_ARM_AT) continue;
const targets = d.rows.slice();
if (d.head) targets.unshift(d.head);
if (d.chipsRow) targets.push(d.chipsRow);
Expand Down
Loading