foundation: project guide, tooling, design tokens, bilingual shell and routing contracts
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,6 +1,14 @@
|
||||
import {afterNextRender, Component, ElementRef, NgZone, OnDestroy, ViewChild} from '@angular/core';
|
||||
import {Dot} from '../../models/dot';
|
||||
import {DeviceDetectionService} from '../../service/device-detection-service';
|
||||
import {
|
||||
afterNextRender,
|
||||
Component,
|
||||
ElementRef,
|
||||
inject,
|
||||
NgZone,
|
||||
OnDestroy,
|
||||
ViewChild,
|
||||
} from '@angular/core';
|
||||
import { isBrowserPlatform, prefersCoarsePointer } from '../../core/platform/browser';
|
||||
import { Dot } from '../../models/dot';
|
||||
|
||||
@Component({
|
||||
selector: 'app-dot-background',
|
||||
@@ -11,7 +19,10 @@ import {DeviceDetectionService} from '../../service/device-detection-service';
|
||||
export class DotBackground implements OnDestroy {
|
||||
@ViewChild('canvas') canvasRef!: ElementRef<HTMLCanvasElement>;
|
||||
|
||||
private ctx!: CanvasRenderingContext2D;
|
||||
private readonly ngZone = inject(NgZone);
|
||||
private readonly coarsePointer = prefersCoarsePointer();
|
||||
|
||||
private ctx: CanvasRenderingContext2D | undefined;
|
||||
private dots: Dot[] = [];
|
||||
private mouse = { x: -1000, y: -1000 };
|
||||
private animationId = 0;
|
||||
@@ -26,23 +37,41 @@ export class DotBackground implements OnDestroy {
|
||||
private ballSpawnId = 0;
|
||||
private ballSpawnNextColor = 0;
|
||||
|
||||
constructor(private ngZone: NgZone, private mobileService: DeviceDetectionService) {
|
||||
constructor() {
|
||||
afterNextRender(() => {
|
||||
this.init();
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (!this.initialized) return;
|
||||
if (!this.initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
cancelAnimationFrame(this.animationId);
|
||||
window.removeEventListener('resize', this.resize);
|
||||
window.removeEventListener('mousemove', this.onMouseMove);
|
||||
|
||||
if (isBrowserPlatform()) {
|
||||
window.removeEventListener('resize', this.resize);
|
||||
window.removeEventListener('mousemove', this.onMouseMove);
|
||||
window.removeEventListener('click', this.onMouseClick);
|
||||
}
|
||||
}
|
||||
|
||||
private init() {
|
||||
const canvas = this.canvasRef.nativeElement;
|
||||
this.ctx = canvas.getContext('2d')!;
|
||||
let ctx: CanvasRenderingContext2D | null = null;
|
||||
|
||||
try {
|
||||
ctx = canvas.getContext('2d');
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.ctx = ctx;
|
||||
this.resize();
|
||||
this.initDots();
|
||||
|
||||
@@ -62,8 +91,7 @@ export class DotBackground implements OnDestroy {
|
||||
const dx = Math.abs(width - canvas.width) / width;
|
||||
const dy = Math.abs(height - canvas.height) / height;
|
||||
|
||||
if (!this.mobileService.mobileCheck() || dy > 0.2 || dx > 0.05) {
|
||||
//sync canvas size to screen size
|
||||
if (!this.coarsePointer || dy > 0.2 || dx > 0.05) {
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
|
||||
@@ -74,13 +102,10 @@ export class DotBackground implements OnDestroy {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
private onMouseMove = (e: MouseEvent) => {
|
||||
const canvas = this.canvasRef.nativeElement;
|
||||
// map real res to canvas res
|
||||
this.mouse.x = e.clientX / window.innerWidth * canvas.width;
|
||||
this.mouse.y = e.clientY / window.innerHeight * canvas.height;
|
||||
this.mouse.x = (e.clientX / window.innerWidth) * canvas.width;
|
||||
this.mouse.y = (e.clientY / window.innerHeight) * canvas.height;
|
||||
};
|
||||
|
||||
private onMouseClick = () => {
|
||||
@@ -91,16 +116,17 @@ export class DotBackground implements OnDestroy {
|
||||
|
||||
private spawnDot(): Dot {
|
||||
const dotId = this.ballSpawnId++;
|
||||
const max_count = this.mobileService.mobileCheck() ? this.MAX_DOT_COUNT_MOBILE : this.MAX_DOT_COUNT;
|
||||
let dot;
|
||||
if (dotId < max_count) {
|
||||
const maxCount = this.coarsePointer ? this.MAX_DOT_COUNT_MOBILE : this.MAX_DOT_COUNT;
|
||||
let dot: Dot;
|
||||
|
||||
if (dotId < maxCount) {
|
||||
dot = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
vx: 0,
|
||||
vy: 0,
|
||||
radius: 1,
|
||||
color: "#000000",
|
||||
color: '#000000',
|
||||
};
|
||||
|
||||
this.dots.push(dot);
|
||||
@@ -114,7 +140,7 @@ export class DotBackground implements OnDestroy {
|
||||
}
|
||||
|
||||
private populateDot(dot: Dot) {
|
||||
const {width, height} = this.canvasRef.nativeElement;
|
||||
const { width, height } = this.canvasRef.nativeElement;
|
||||
|
||||
dot.x = Math.random() * width;
|
||||
dot.y = Math.random() * height;
|
||||
@@ -132,8 +158,14 @@ export class DotBackground implements OnDestroy {
|
||||
|
||||
private animate = () => {
|
||||
const canvas = this.canvasRef.nativeElement;
|
||||
const ctx = this.ctx;
|
||||
|
||||
if (!ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { width, height } = canvas;
|
||||
this.ctx.clearRect(0, 0, width, height);
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
|
||||
for (const dot of this.dots) {
|
||||
const dx = dot.x - this.mouse.x;
|
||||
@@ -161,25 +193,24 @@ export class DotBackground implements OnDestroy {
|
||||
dot.vy = Math.random() * 0.2 - 0.1;
|
||||
}
|
||||
|
||||
// Bounce off edges (accounting for radius)
|
||||
if (dot.x < dot.radius || dot.x > width - dot.radius) dot.vx *= -1;
|
||||
if (dot.y < dot.radius || dot.y > height - dot.radius) dot.vy *= -1;
|
||||
if (dot.x < dot.radius || dot.x > width - dot.radius) {
|
||||
dot.vx *= -1;
|
||||
}
|
||||
if (dot.y < dot.radius || dot.y > height - dot.radius) {
|
||||
dot.vy *= -1;
|
||||
}
|
||||
|
||||
// Clamp to bounds
|
||||
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));
|
||||
|
||||
const gradient = this.ctx.createRadialGradient(
|
||||
dot.x, dot.y, 0,
|
||||
dot.x, dot.y, dot.radius
|
||||
);
|
||||
const gradient = ctx.createRadialGradient(dot.x, dot.y, 0, dot.x, dot.y, dot.radius);
|
||||
gradient.addColorStop(0, dot.color + '40');
|
||||
gradient.addColorStop(1, 'transparent');
|
||||
|
||||
this.ctx.beginPath();
|
||||
this.ctx.arc(dot.x, dot.y, dot.radius, 0, Math.PI * 2);
|
||||
this.ctx.fillStyle = gradient;
|
||||
this.ctx.fill();
|
||||
ctx.beginPath();
|
||||
ctx.arc(dot.x, dot.y, dot.radius, 0, Math.PI * 2);
|
||||
ctx.fillStyle = gradient;
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
this.animationId = requestAnimationFrame(this.animate);
|
||||
|
||||
Reference in New Issue
Block a user