feat: light a chapter when its heading crosses 60% of the viewport

This commit is contained in:
2026-10-11 01:11:09 +03:00
parent 7fa1c08ce8
commit d6045ad2c1
3 changed files with 13 additions and 6 deletions

View File

@@ -32,10 +32,17 @@ export interface ActivationLineInput {
visualViewportHeight: number;
}
/**
* A chapter becomes current when its heading's top crosses this fraction of
* the visual viewport, measured from the top. At 60% the chapter lights up as
* its heading settles into the reading zone, not after it has been read.
*/
export const ACTIVATION_LINE_VH = 0.6;
export const activationLineDocumentY = (input: ActivationLineInput): number =>
input.scrollY +
input.visualViewportOffsetTop +
input.visualViewportHeight * 0.35;
input.visualViewportHeight * ACTIVATION_LINE_VH;
export interface DocumentEndInput {
scrollY: number;

View File

@@ -73,9 +73,9 @@ test("passive scrolling replaces a stable fragment without stealing focus", asyn
if (heading === null) throw new Error("Missing experience heading");
const headingDocumentTop =
heading.getBoundingClientRect().top + window.scrollY;
// Place the heading just beyond the 35% activation line. Scroll offsets
// Place the heading just beyond the 60% activation line. Scroll offsets
// are integer-rounded in Chromium, so exact boundary equality is brittle.
window.scrollTo(0, headingDocumentTop - window.innerHeight * 0.34);
window.scrollTo(0, headingDocumentTop - window.innerHeight * 0.59);
});
await expect(page).toHaveURL(`${distUrl}/#experience`, { timeout: 2000 });

View File

@@ -20,14 +20,14 @@ const headings: readonly SectionHeadingGeometry[] = [
];
describe("active section selection", () => {
it("places the activation line at a literal 35 percent of the visual viewport", () => {
it("places the activation line at a literal 60 percent of the visual viewport", () => {
expect(
activationLineDocumentY({
scrollY: 200,
visualViewportOffsetTop: 25,
visualViewportHeight: 1000,
}),
).toBe(575);
).toBe(825);
});
it("forces document end within exactly two CSS pixels using current geometry", () => {
@@ -60,7 +60,7 @@ describe("active section selection", () => {
).toBe("start");
});
it("selects the last heading at or above the literal 35 percent activation line", () => {
it("selects the last heading at or above the activation line", () => {
expect(
selectActiveSection({
headings,