diff --git a/src/utils/homeTopMask.js b/src/utils/homeTopMask.js new file mode 100644 index 0000000..39e0273 --- /dev/null +++ b/src/utils/homeTopMask.js @@ -0,0 +1,11 @@ +export function getTopMaskProgress({ scrollTop, startY, rangeY }) { + const safeScrollTop = Number.isFinite(scrollTop) ? scrollTop : 0 + const start = Math.max(0, Number.isFinite(startY) ? startY : 0) + const range = Math.max(1, Number.isFinite(rangeY) ? rangeY : 1) + + if (safeScrollTop <= start) { + return 0 + } + + return Math.min(1, (safeScrollTop - start) / range) +} diff --git a/tools/homeTopMask.test.mjs b/tools/homeTopMask.test.mjs new file mode 100644 index 0000000..7873cf8 --- /dev/null +++ b/tools/homeTopMask.test.mjs @@ -0,0 +1,30 @@ +import test from 'node:test' +import assert from 'node:assert/strict' +import { getTopMaskProgress } from '../src/utils/homeTopMask.js' + +test('returns 0 before the title reaches the status-bar area', () => { + assert.equal( + getTopMaskProgress({ scrollTop: 30, startY: 80, rangeY: 40 }), + 0 + ) +}) + +test('ramps linearly through the transition range', () => { + assert.equal( + getTopMaskProgress({ scrollTop: 100, startY: 80, rangeY: 40 }), + 0.5 + ) +}) + +test('returns 1 after the full transition range', () => { + assert.equal( + getTopMaskProgress({ scrollTop: 180, startY: 80, rangeY: 40 }), + 1 + ) +}) + +test('clamps invalid values into a stable progress range', () => { + assert.equal(getTopMaskProgress({ scrollTop: -10, startY: 80, rangeY: 40 }), 0) + assert.equal(getTopMaskProgress({ scrollTop: 90, startY: 80, rangeY: 0 }), 1) + assert.equal(getTopMaskProgress({ scrollTop: Number.NaN, startY: 80, rangeY: 40 }), 0) +})