import { Directive, ElementRef, Input, OnChanges, OnDestroy } from '@angular/core'; const OBFUSCATION_CHARS = '!@#$%^&*()_+-=[]{}|;:,.<>?/~`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; /** * Apply as `[mmObfuscated]="style.obfuscated"` on an element whose textContent is the * text to obfuscate. While active, cycles every character (except spaces) through random * glyphs, mimicking Minecraft's "magic" formatting. The real text is preserved in an * aria-label so it stays accessible to screen readers, and is restored to the DOM when * obfuscation is turned off. */ @Directive({ selector: '[mmObfuscated]', standalone: true, }) export class ObfuscatedDirective implements OnChanges, OnDestroy { @Input('mmObfuscated') active = false; private original: string | null = null; private intervalId: ReturnType | undefined; constructor(private readonly el: ElementRef) {} ngOnChanges(): void { if (this.original === null) { this.original = this.el.nativeElement.textContent ?? ''; this.el.nativeElement.setAttribute('aria-label', this.original); } this.sync(); } ngOnDestroy(): void { this.stop(); } private sync(): void { if (this.active && !this.intervalId) { this.start(); } else if (!this.active && this.intervalId) { this.stop(); } } private start(): void { this.intervalId = setInterval(() => { const text = this.original ?? ''; const scrambled = text .split('') .map((ch) => (ch === ' ' ? ' ' : OBFUSCATION_CHARS[Math.floor(Math.random() * OBFUSCATION_CHARS.length)])) .join(''); this.el.nativeElement.textContent = scrambled; }, 50); } private stop(): void { if (this.intervalId) { clearInterval(this.intervalId); this.intervalId = undefined; } if (this.original !== null) { this.el.nativeElement.textContent = this.original; } } }