signature: restore lean dots, reduced-motion redraw, above-fold reveal and map focus rings

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-25 17:49:57 +02:00
parent 7813499ee3
commit 6c1572bf3c
5 changed files with 130 additions and 8 deletions

View File

@@ -209,4 +209,73 @@ describe('DotBackground', () => {
expect(raf).toHaveBeenCalled();
});
it('starts with a bounded initial dot count on a large desktop viewport', async () => {
const context = mockContext();
vi.spyOn(HTMLCanvasElement.prototype, 'getContext').mockReturnValue(context);
const widthStub = vi.spyOn(window, 'innerWidth', 'get').mockReturnValue(2560);
const heightStub = vi.spyOn(window, 'innerHeight', 'get').mockReturnValue(1440);
const scheduled: FrameRequestCallback[] = [];
vi.spyOn(window, 'requestAnimationFrame').mockImplementation((callback) => {
scheduled.push(callback);
return scheduled.length;
});
try {
const fixture = await createFixture();
const pending = scheduled.splice(0);
for (const callback of pending) {
callback(0);
}
const arcCalls = vi.mocked(context.arc).mock.calls.length;
expect(arcCalls).toBeGreaterThanOrEqual(6);
expect(arcCalls).toBeLessThanOrEqual(24);
fixture.destroy();
} finally {
widthStub.mockRestore();
heightStub.mockRestore();
}
});
it('redraws the static frame after resize under reduced motion without starting a loop', async () => {
mockMatchMedia((query) => query.includes('prefers-reduced-motion'));
const context = mockContext();
vi.spyOn(HTMLCanvasElement.prototype, 'getContext').mockReturnValue(context);
const scheduled: FrameRequestCallback[] = [];
vi.spyOn(window, 'requestAnimationFrame').mockImplementation((callback) => {
scheduled.push(callback);
return scheduled.length;
});
TestBed.resetTestingModule();
await TestBed.configureTestingModule({
imports: [DotBackground],
}).compileComponents();
const fixture = TestBed.createComponent(DotBackground);
fixture.detectChanges();
await fixture.whenStable();
TestBed.inject(ApplicationRef).tick();
const pending = scheduled.splice(0);
for (const callback of pending) {
callback(0);
}
vi.mocked(context.clearRect).mockClear();
vi.mocked(context.arc).mockClear();
window.dispatchEvent(new Event('resize'));
const resizeFrames = scheduled.splice(0);
for (const callback of resizeFrames) {
callback(0);
}
expect(context.clearRect).toHaveBeenCalled();
expect(context.arc).toHaveBeenCalled();
expect(scheduled).toEqual([]);
fixture.destroy();
});
});

View File

@@ -43,7 +43,10 @@ export class DotBackground {
private tornDown = false;
private readonly teardowns: Array<() => void> = [];
private readonly MIN_DOT_COUNT = 8;
private readonly INITIAL_DOT_COUNT_MIN = 6;
private readonly INITIAL_DOT_COUNT_MAX = 24;
private readonly INITIAL_DOT_COUNT_MAX_COARSE = 12;
private readonly INITIAL_DOT_AREA_DIVISOR = 130_000;
private readonly MAX_DOT_COUNT = 100;
private readonly MAX_DOT_COUNT_MOBILE = 40;
private readonly COLORS = ['#6366f1', '#8b5cf6', '#a855f7', '#3b82f6'];
@@ -213,6 +216,10 @@ export class DotBackground {
dot.x = Math.max(dot.radius, Math.min(width - dot.radius, dot.x));
dot.y = Math.max(dot.radius, Math.min(height - dot.radius, dot.y));
}
if (this.reducedMotion && this.ctx) {
this.drawFrame();
}
}
};
@@ -239,10 +246,12 @@ export class DotBackground {
};
private targetDotCount(width: number, height: number): number {
const maxCount = this.coarsePointer ? this.MAX_DOT_COUNT_MOBILE : this.MAX_DOT_COUNT;
const maxInitial = this.coarsePointer
? this.INITIAL_DOT_COUNT_MAX_COARSE
: this.INITIAL_DOT_COUNT_MAX;
const area = Math.max(0, width) * Math.max(0, height);
const fromArea = Math.round(area / 20_000);
return Math.min(maxCount, Math.max(this.MIN_DOT_COUNT, fromArea));
const fromArea = Math.round(area / this.INITIAL_DOT_AREA_DIVISOR);
return Math.min(maxInitial, Math.max(this.INITIAL_DOT_COUNT_MIN, fromArea));
}
private spawnDot(): Dot {