diff --git a/src/components/interactive-background/interactive-background.css b/src/components/interactive-background/interactive-background.css index 07508b6..f1e0daf 100644 --- a/src/components/interactive-background/interactive-background.css +++ b/src/components/interactive-background/interactive-background.css @@ -16,34 +16,3 @@ height: 100%; display: block; } - -.custom-cursor { - position: fixed; - top: 0; - left: 0; - width: 26px; - height: 26px; - border: 1px solid rgba(147, 197, 253, 0.9); - border-radius: 50%; - transform: translate3d(-9999px, -9999px, 0); - margin-left: -13px; - margin-top: -13px; - box-shadow: 0 0 24px rgba(56, 189, 248, 0.28); - background: radial-gradient(circle, rgba(96, 165, 250, 0.25) 0%, rgba(96, 165, 250, 0) 70%); - transition: width 130ms ease, height 130ms ease, margin 130ms ease, border-color 130ms ease; -} - -.custom-cursor.pointer-down { - width: 38px; - height: 38px; - margin-left: -19px; - margin-top: -19px; - border-color: rgba(196, 181, 253, 0.95); -} - -@media (prefers-reduced-motion: reduce) { - .custom-cursor { - transition: none; - } -} - diff --git a/src/components/interactive-background/interactive-background.html b/src/components/interactive-background/interactive-background.html index 17cb4f8..f8ec183 100644 --- a/src/components/interactive-background/interactive-background.html +++ b/src/components/interactive-background/interactive-background.html @@ -1,11 +1,3 @@ - diff --git a/src/components/interactive-background/interactive-background.ts b/src/components/interactive-background/interactive-background.ts index 2831d55..d02c93e 100644 --- a/src/components/interactive-background/interactive-background.ts +++ b/src/components/interactive-background/interactive-background.ts @@ -1,5 +1,4 @@ -import { AfterViewInit, Component, ElementRef, OnDestroy, ViewChild, inject } from '@angular/core'; -import { NavigationStart, Router } from '@angular/router'; +import { AfterViewInit, Component, ElementRef, OnDestroy, ViewChild } from '@angular/core'; type PointerState = { x: number; @@ -277,11 +276,8 @@ class ParticleEngine { }) export class InteractiveBackground implements AfterViewInit, OnDestroy { @ViewChild('canvas', { static: true }) private readonly canvasRef!: ElementRef; - private readonly router = inject(Router); - isCursorVisible = InteractiveBackground.detectFinePointer(); isPointerDown = false; - cursorTransform = 'translate3d(-9999px, -9999px, 0)'; private particleEngine: ParticleEngine | null = null; private destroyCallbacks: Array<() => void> = []; @@ -293,6 +289,7 @@ export class InteractiveBackground implements AfterViewInit, OnDestroy { private activeTouchId: number | null = null; private reducedMotion = false; private mediaQuery!: MediaQueryList; + private readonly pointerExcludeSelector = '[data-disable-bg-pointer]'; ngAfterViewInit(): void { const canvas = this.canvasRef.nativeElement; @@ -332,11 +329,6 @@ export class InteractiveBackground implements AfterViewInit, OnDestroy { const onTouchCancel = () => this.handlePointerLeave(); const onMotionChange = (event: MediaQueryListEvent) => this.handleMotionPreferenceChange(event.matches); - const routerSubscription = this.router.events.subscribe((event) => { - if (event instanceof NavigationStart) { - this.resetPointerInteraction(); - } - }); window.addEventListener('resize', onResize); @@ -375,7 +367,6 @@ export class InteractiveBackground implements AfterViewInit, OnDestroy { () => window.removeEventListener('touchend', onTouchEnd), () => window.removeEventListener('touchcancel', onTouchCancel), () => this.mediaQuery.removeEventListener('change', onMotionChange), - () => routerSubscription.unsubscribe(), ]; } @@ -435,6 +426,11 @@ export class InteractiveBackground implements AfterViewInit, OnDestroy { } private handlePointerMove(event: PointerEvent): void { + if (this.isPointerExcluded(event.target)) { + this.handlePointerLeave(); + return; + } + if (event.pointerType === 'touch' && this.activeTouchId !== null && event.pointerId !== this.activeTouchId) { return; } @@ -443,6 +439,11 @@ export class InteractiveBackground implements AfterViewInit, OnDestroy { } private handlePointerDown(event: PointerEvent): void { + if (this.isPointerExcluded(event.target)) { + this.handlePointerLeave(); + return; + } + if (event.pointerType === 'touch') { this.activeTouchId = event.pointerId; } @@ -452,6 +453,11 @@ export class InteractiveBackground implements AfterViewInit, OnDestroy { } private handlePointerUp(event: PointerEvent): void { + if (this.isPointerExcluded(event.target)) { + this.handlePointerLeave(); + return; + } + this.isPointerDown = false; if (event.pointerType === 'touch') { @@ -460,27 +466,42 @@ export class InteractiveBackground implements AfterViewInit, OnDestroy { } this.activeTouchId = null; - this.resetPointerInteraction(); + this.pointerActive = false; return; } - this.resetPointerInteraction(); + this.updatePointer(event.clientX, event.clientY, true); } private handleMouseMove(event: MouseEvent): void { + if (this.isPointerExcluded(event.target)) { + this.handlePointerLeave(); + return; + } + this.updatePointer(event.clientX, event.clientY, true); } private handleMouseDown(event: MouseEvent): void { + if (this.isPointerExcluded(event.target)) { + this.handlePointerLeave(); + return; + } + this.isPointerDown = true; this.updatePointer(event.clientX, event.clientY, true); } private handleMouseUp(): void { - this.resetPointerInteraction(); + this.isPointerDown = false; } private handleTouchStart(event: TouchEvent): void { + if (this.isPointerExcluded(event.target)) { + this.handlePointerLeave(); + return; + } + const touch = event.changedTouches.item(0); if (!touch) { return; @@ -492,6 +513,11 @@ export class InteractiveBackground implements AfterViewInit, OnDestroy { } private handleTouchMove(event: TouchEvent): void { + if (this.isPointerExcluded(event.target)) { + this.handlePointerLeave(); + return; + } + if (this.activeTouchId === null) { return; } @@ -505,6 +531,11 @@ export class InteractiveBackground implements AfterViewInit, OnDestroy { } private handleTouchEnd(event: TouchEvent): void { + if (this.isPointerExcluded(event.target)) { + this.handlePointerLeave(); + return; + } + if (this.activeTouchId === null) { return; } @@ -515,25 +546,20 @@ export class InteractiveBackground implements AfterViewInit, OnDestroy { } this.isPointerDown = false; - this.resetPointerInteraction(); + this.activeTouchId = null; + this.pointerActive = false; } private handlePointerLeave(): void { - this.resetPointerInteraction(); - } - - private resetPointerInteraction(): void { this.pointerActive = false; this.isPointerDown = false; this.activeTouchId = null; - this.cursorTransform = 'translate3d(-9999px, -9999px, 0)'; } private updatePointer(x: number, y: number, active: boolean): void { this.pointerX = x; this.pointerY = y; this.pointerActive = active; - this.cursorTransform = `translate3d(${x}px, ${y}px, 0)`; } private findTouch(touches: TouchList, identifier: number): Touch | null { @@ -547,6 +573,14 @@ export class InteractiveBackground implements AfterViewInit, OnDestroy { return null; } + private isPointerExcluded(target: EventTarget | null): boolean { + if (!(target instanceof Element)) { + return false; + } + + return target.closest(this.pointerExcludeSelector) !== null; + } + private syncPointer(): void { this.particleEngine?.setPointer({ x: this.pointerX, @@ -591,13 +625,6 @@ export class InteractiveBackground implements AfterViewInit, OnDestroy { }; } - private static detectFinePointer(): boolean { - if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') { - return false; - } - - return window.matchMedia('(pointer: fine)').matches; - } } diff --git a/src/pages/home/home.css b/src/pages/home/home.css index c14b749..4fd31d6 100644 --- a/src/pages/home/home.css +++ b/src/pages/home/home.css @@ -237,7 +237,19 @@ h2 { background: rgba(33, 38, 45, 0.8); color: #e6edf3; cursor: pointer; - transition: border-color 120ms ease, background-color 120ms ease, transform 120ms ease; + opacity: 0; + visibility: hidden; + pointer-events: none; + transform: translateY(2px); + transition: border-color 120ms ease, background-color 120ms ease, transform 120ms ease, opacity 120ms ease; +} + +.stack-carousel-shell:hover .carousel-btn, +.stack-carousel-shell:focus-within .carousel-btn { + opacity: 1; + visibility: visible; + pointer-events: auto; + transform: translateY(0); } .carousel-btn:hover { diff --git a/src/pages/home/home.html b/src/pages/home/home.html index eac0311..2ecbc06 100644 --- a/src/pages/home/home.html +++ b/src/pages/home/home.html @@ -34,7 +34,16 @@

Tech Stack

Primary technologies I use to deliver production-ready software.

-