signature: add systems map, command palette and motion primitives

Ship the Signature UX as standalone, SSR-safe pieces: bilingual copy, a dual SVG/list systems map, a Map-backed command palette in the shell, opt-in reveal and metric primitives, and a leak-free decorative canvas.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-25 17:37:25 +02:00
parent 9ea2885c7b
commit 7813499ee3
25 changed files with 2902 additions and 73 deletions

View File

@@ -0,0 +1,75 @@
import { DOCUMENT } from '@angular/common';
import {
afterNextRender,
DestroyRef,
Directive,
ElementRef,
inject,
Injector,
input,
} from '@angular/core';
import { isBrowserPlatform, prefersReducedMotion } from '../../core/platform/browser';
@Directive({
selector: '[appReveal]',
})
export class RevealDirective {
readonly appRevealThreshold = input(0.12);
private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);
private readonly document = inject(DOCUMENT);
private readonly injector = inject(Injector);
private readonly destroyRef = inject(DestroyRef);
private readonly isBrowser = isBrowserPlatform();
private readonly reducedMotion = prefersReducedMotion();
private observer: IntersectionObserver | null = null;
constructor() {
if (!this.isBrowser || this.reducedMotion || !this.canObserve()) {
this.revealNow();
return;
}
afterNextRender(() => this.observe(), { injector: this.injector });
this.destroyRef.onDestroy(() => this.disconnect());
}
private canObserve(): boolean {
const view = this.document.defaultView;
return !!view && typeof view.IntersectionObserver === 'function';
}
private observe(): void {
const view = this.document.defaultView;
if (!view || typeof view.IntersectionObserver !== 'function') {
this.revealNow();
return;
}
this.host.nativeElement.classList.add('reveal-pending');
this.observer = new view.IntersectionObserver(
(entries) => {
if (entries.some((entry) => entry.isIntersecting)) {
this.host.nativeElement.classList.remove('reveal-pending');
this.host.nativeElement.classList.add('is-revealed');
this.disconnect();
}
},
{
rootMargin: '0px 0px -8% 0px',
threshold: this.appRevealThreshold(),
},
);
this.observer.observe(this.host.nativeElement);
}
private revealNow(): void {
this.host.nativeElement.classList.add('is-revealed');
}
private disconnect(): void {
this.observer?.disconnect();
this.observer = null;
}
}