mirror of
https://github.com/adam-benyekkou/my_portfolio.git
synced 2026-01-16 04:30:08 +00:00
Finished hero with text scramble on init and occasional glitching, changed header layout on mobile for better UX, finished minimal footer
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
<footer>
|
||||
<section class="checkered-background border-t-5 border-nier-accent h-15 flex items-center bg-nier-light text-nier-light">
|
||||
<p class="text-1xl font-noto-jp">Made by Adam Benyekkou 2025</p>
|
||||
</section>
|
||||
<footer class="bg-nier-dark border-t border-nier-accent">
|
||||
<div class="container mx-auto px-4 py-6">
|
||||
<p class="text-center text-nier-light font-noto-jp text-sm">
|
||||
© 2025 Adam Benyekkou. All rights reserved.
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
|
||||
<div class="typewriter-container">
|
||||
@for (item of displayedTexts(); track item.id) {
|
||||
<div class="typewriter-item">
|
||||
@for (item of currentTexts(); track item.id) {
|
||||
<div class="typewriter-item" [@slideIn]>
|
||||
<div class="text-scroller-text font-noto-jp text-sm">
|
||||
{{ item.displayed }}
|
||||
@if (item.isTyping) {
|
||||
|
||||
@@ -6,6 +6,8 @@ import {
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
signal,
|
||||
computed,
|
||||
ChangeDetectionStrategy,
|
||||
} from '@angular/core';
|
||||
import {
|
||||
trigger,
|
||||
@@ -16,19 +18,28 @@ import {
|
||||
} from '@angular/animations';
|
||||
|
||||
interface TextItem {
|
||||
id: number;
|
||||
text: string;
|
||||
displayed: string;
|
||||
isTyping: boolean;
|
||||
isComplete: boolean;
|
||||
readonly id: number;
|
||||
readonly text: string;
|
||||
readonly displayed: string;
|
||||
readonly isTyping: boolean;
|
||||
readonly isComplete: boolean;
|
||||
}
|
||||
|
||||
// Constants for better maintainability
|
||||
const TYPING_DELAYS = {
|
||||
GLITCH: { min: 150, max: 200, probability: 0.03 },
|
||||
RAPID: { min: 5, max: 15, probability: 0.5 },
|
||||
SLOW: { min: 50, max: 80, probability: 0.15 },
|
||||
STUTTER: { delay: 200, probability: 0.02 },
|
||||
} as const;
|
||||
|
||||
@Component({
|
||||
selector: 'app-header-text-animate-section',
|
||||
standalone: true,
|
||||
imports: [],
|
||||
templateUrl: './header-text-animate-section.component.html',
|
||||
styleUrl: './header-text-animate-section.component.css',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
animations: [
|
||||
trigger('textChange', [
|
||||
state(
|
||||
@@ -45,13 +56,24 @@ interface TextItem {
|
||||
transform: 'translateY(20px)',
|
||||
}),
|
||||
),
|
||||
transition('visible => hidden', [animate('0.5s ease-out')]),
|
||||
transition('hidden => visible', [animate('0.5s ease-in')]),
|
||||
transition('visible => hidden', animate('300ms ease-out')),
|
||||
transition('hidden => visible', animate('300ms ease-in')),
|
||||
]),
|
||||
// Add fade-in animation for new text items
|
||||
trigger('slideIn', [
|
||||
transition(':enter', [
|
||||
style({ opacity: 0, transform: 'translateY(10px)' }),
|
||||
animate(
|
||||
'200ms ease-out',
|
||||
style({ opacity: 1, transform: 'translateY(0)' }),
|
||||
),
|
||||
]),
|
||||
]),
|
||||
],
|
||||
})
|
||||
export class HeaderTextAnimateSectionComponent implements OnInit, OnDestroy {
|
||||
phrases = input<string[]>([
|
||||
// Input signals with better defaults
|
||||
phrases = input<readonly string[]>([
|
||||
'Uploading guinea pig consciousness to the cloud...',
|
||||
'Error: Sarcasm module overloaded. Rebooting...',
|
||||
'Downloading personalities... 404: Personality not found.',
|
||||
@@ -62,73 +84,75 @@ export class HeaderTextAnimateSectionComponent implements OnInit, OnDestroy {
|
||||
'Cybernetic guinea pigs have seized control of Sector 7...',
|
||||
'ERROR: Reality.dll has crashed. Would you like to submit a bug report?',
|
||||
'Synthetic sushi generation complete. Tastes like chicken.exe...',
|
||||
]);
|
||||
] as const);
|
||||
|
||||
interval = input<number>(3000); // Default interval of 3 seconds
|
||||
typingSpeed = input<number>(30); // Time in ms between each character (faster than before)
|
||||
maxDisplayedTexts = input<number>(4); // Maximum number of texts to display
|
||||
interval = input<number>(2500);
|
||||
typingSpeed = input<number>(25);
|
||||
maxDisplayedTexts = input<number>(4);
|
||||
|
||||
// Signals for internal state
|
||||
displayedTexts = signal<TextItem[]>([]);
|
||||
nextId = signal<number>(0);
|
||||
phraseIndex = signal<number>(0);
|
||||
isTypingInProgress = signal<boolean>(false);
|
||||
// State signals
|
||||
private readonly displayedTexts = signal<readonly TextItem[]>([]);
|
||||
private readonly nextId = signal<number>(0);
|
||||
private readonly phraseIndex = signal<number>(0);
|
||||
private readonly isTypingInProgress = signal<boolean>(false);
|
||||
|
||||
private typewriterTimeouts: number[] = [];
|
||||
private nextTextTimeout: any;
|
||||
// Computed signals for better performance
|
||||
readonly currentTexts = computed(() => this.displayedTexts());
|
||||
readonly hasTexts = computed(() => this.displayedTexts().length > 0);
|
||||
|
||||
// Timeout management
|
||||
private readonly activeTimeouts = new Set<number>();
|
||||
private nextTextTimeout?: number;
|
||||
|
||||
constructor() {
|
||||
// Use effect to react to changes in phrases
|
||||
// Initialize first text when phrases are available
|
||||
effect(() => {
|
||||
const phrasesList = this.phrases();
|
||||
if (phrasesList.length > 0 && this.displayedTexts().length === 0) {
|
||||
this.startNextText();
|
||||
this.scheduleNextText(0); // Start immediately
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
if (this.phrases().length) {
|
||||
this.startTextRotation();
|
||||
}
|
||||
// Component initialization handled in constructor effect
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.stopTextRotation();
|
||||
this.clearTypewriterTimeouts();
|
||||
this.cleanup();
|
||||
}
|
||||
|
||||
private startTextRotation(): void {
|
||||
// Start with the first text
|
||||
this.startNextText();
|
||||
}
|
||||
private cleanup(): void {
|
||||
// Clear all timeouts
|
||||
this.activeTimeouts.forEach((id) => window.clearTimeout(id));
|
||||
this.activeTimeouts.clear();
|
||||
|
||||
private stopTextRotation(): void {
|
||||
if (this.nextTextTimeout) {
|
||||
clearTimeout(this.nextTextTimeout);
|
||||
window.clearTimeout(this.nextTextTimeout);
|
||||
this.nextTextTimeout = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private clearTypewriterTimeouts(): void {
|
||||
this.typewriterTimeouts.forEach((id) => window.clearTimeout(id));
|
||||
this.typewriterTimeouts = [];
|
||||
private scheduleNextText(delay: number = this.interval()): void {
|
||||
if (this.nextTextTimeout) {
|
||||
window.clearTimeout(this.nextTextTimeout);
|
||||
}
|
||||
|
||||
this.nextTextTimeout = window.setTimeout(() => {
|
||||
this.startNextText();
|
||||
}, delay);
|
||||
}
|
||||
|
||||
private startNextText(): void {
|
||||
// If we're already typing, don't start a new one
|
||||
if (this.isTypingInProgress()) return;
|
||||
|
||||
const phrasesList = this.phrases();
|
||||
if (phrasesList.length === 0) return;
|
||||
|
||||
// Get the next phrase to display
|
||||
const index = this.phraseIndex();
|
||||
const nextText = phrasesList[index];
|
||||
const currentIndex = this.phraseIndex();
|
||||
const nextText = phrasesList[currentIndex];
|
||||
|
||||
// Update the index for the next time
|
||||
this.phraseIndex.update((idx) => (idx + 1) % phrasesList.length);
|
||||
|
||||
// Create a new text item
|
||||
// Create immutable text item
|
||||
const newItem: TextItem = {
|
||||
id: this.nextId(),
|
||||
text: nextText,
|
||||
@@ -137,44 +161,40 @@ export class HeaderTextAnimateSectionComponent implements OnInit, OnDestroy {
|
||||
isComplete: false,
|
||||
};
|
||||
|
||||
// Update the nextId for the next time
|
||||
// Update state atomically
|
||||
this.phraseIndex.update((idx) => (idx + 1) % phrasesList.length);
|
||||
this.nextId.update((id) => id + 1);
|
||||
this.isTypingInProgress.set(true);
|
||||
|
||||
// Add the new text to the displayed texts list
|
||||
// Update displayed texts with proper cleanup
|
||||
this.displayedTexts.update((texts) => {
|
||||
// If we already have the maximum number of texts, remove the oldest one
|
||||
if (texts.length >= this.maxDisplayedTexts()) {
|
||||
return [...texts.slice(1), newItem];
|
||||
const maxTexts = this.maxDisplayedTexts();
|
||||
if (texts.length >= maxTexts) {
|
||||
return [...texts.slice(texts.length - maxTexts + 1), newItem];
|
||||
}
|
||||
|
||||
// Otherwise, just add the new text
|
||||
return [...texts, newItem];
|
||||
});
|
||||
|
||||
// Set typing in progress
|
||||
this.isTypingInProgress.set(true);
|
||||
|
||||
// Start the typewriter effect
|
||||
this.typeText(newItem.id);
|
||||
// Start typing animation
|
||||
this.animateText(newItem.id);
|
||||
}
|
||||
|
||||
private typeText(textId: number): void {
|
||||
private animateText(textId: number): void {
|
||||
const texts = this.displayedTexts();
|
||||
const textIndex = texts.findIndex((t) => t.id === textId);
|
||||
|
||||
if (textIndex === -1) return;
|
||||
|
||||
const textItem = texts[textIndex];
|
||||
const fullText = textItem.text;
|
||||
const currentLength = textItem.displayed.length;
|
||||
const { text, displayed } = textItem;
|
||||
const nextCharIndex = displayed.length;
|
||||
|
||||
if (currentLength < fullText.length) {
|
||||
// Add one character
|
||||
const newDisplayed = fullText.substring(0, currentLength + 1);
|
||||
if (nextCharIndex < text.length) {
|
||||
// Update displayed text
|
||||
const newDisplayed = text.substring(0, nextCharIndex + 1);
|
||||
|
||||
// Update the text
|
||||
this.displayedTexts.update((texts) => {
|
||||
const updatedTexts = [...texts];
|
||||
this.displayedTexts.update((currentTexts) => {
|
||||
const updatedTexts = [...currentTexts];
|
||||
updatedTexts[textIndex] = {
|
||||
...textItem,
|
||||
displayed: newDisplayed,
|
||||
@@ -182,63 +202,76 @@ export class HeaderTextAnimateSectionComponent implements OnInit, OnDestroy {
|
||||
return updatedTexts;
|
||||
});
|
||||
|
||||
// Hectic typing effect with much more randomization
|
||||
// Occasionally pause, sometimes type very fast, sometimes slower
|
||||
let nextDelay: number;
|
||||
|
||||
// Random chance for different typing behaviors
|
||||
const randomFactor = Math.random();
|
||||
|
||||
if (randomFactor < 0.05) {
|
||||
// Longer pause (glitch effect) - reduced probability and duration
|
||||
nextDelay = 200 + Math.random() * 150;
|
||||
} else if (randomFactor < 0.5) {
|
||||
// Rapid typing burst - increased probability
|
||||
nextDelay = 5 + Math.random() * 15;
|
||||
} else if (randomFactor < 0.65) {
|
||||
// Slow typing - reduced probability and duration
|
||||
nextDelay = 70 + Math.random() * 50;
|
||||
} else {
|
||||
// Normal typing speed with some variation - faster base speed
|
||||
nextDelay = this.typingSpeed() + Math.random() * 40 - 20;
|
||||
}
|
||||
|
||||
// Occasionally "glitch" and add brief pause before continuing - reduced probability
|
||||
if (Math.random() < 0.03) {
|
||||
// Add a brief stutter (simulate buffering) - shorter pause
|
||||
nextDelay += 250;
|
||||
}
|
||||
|
||||
// Schedule next character with dynamic timing
|
||||
const delay = this.calculateTypingDelay();
|
||||
const timeoutId = window.setTimeout(() => {
|
||||
this.typeText(textId);
|
||||
}, nextDelay);
|
||||
this.animateText(textId);
|
||||
}, delay);
|
||||
|
||||
this.typewriterTimeouts.push(timeoutId);
|
||||
this.activeTimeouts.add(timeoutId);
|
||||
} else {
|
||||
// Text is complete
|
||||
this.displayedTexts.update((texts) => {
|
||||
const updatedTexts = [...texts];
|
||||
updatedTexts[textIndex] = {
|
||||
...textItem,
|
||||
isTyping: false,
|
||||
isComplete: true,
|
||||
};
|
||||
return updatedTexts;
|
||||
});
|
||||
|
||||
// Typing is no longer in progress
|
||||
this.isTypingInProgress.set(false);
|
||||
|
||||
// Schedule the next text after the interval
|
||||
this.nextTextTimeout = setTimeout(() => {
|
||||
this.clearTypewriterTimeouts(); // Clear any remaining timeouts
|
||||
this.startNextText();
|
||||
}, this.interval());
|
||||
// Text complete
|
||||
this.completeText(textIndex, textItem);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper method to track items
|
||||
trackByTextId(index: number, item: TextItem): number {
|
||||
return item.id;
|
||||
private calculateTypingDelay(): number {
|
||||
const random = Math.random();
|
||||
const baseSpeed = this.typingSpeed();
|
||||
|
||||
// Apply different typing patterns based on probability
|
||||
if (random < TYPING_DELAYS.GLITCH.probability) {
|
||||
return (
|
||||
TYPING_DELAYS.GLITCH.min +
|
||||
Math.random() * (TYPING_DELAYS.GLITCH.max - TYPING_DELAYS.GLITCH.min)
|
||||
);
|
||||
}
|
||||
|
||||
if (random < TYPING_DELAYS.RAPID.probability) {
|
||||
return (
|
||||
TYPING_DELAYS.RAPID.min +
|
||||
Math.random() * (TYPING_DELAYS.RAPID.max - TYPING_DELAYS.RAPID.min)
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
random <
|
||||
TYPING_DELAYS.RAPID.probability + TYPING_DELAYS.SLOW.probability
|
||||
) {
|
||||
return (
|
||||
TYPING_DELAYS.SLOW.min +
|
||||
Math.random() * (TYPING_DELAYS.SLOW.max - TYPING_DELAYS.SLOW.min)
|
||||
);
|
||||
}
|
||||
|
||||
// Normal typing with variation
|
||||
let delay = baseSpeed + (Math.random() * 30 - 15);
|
||||
|
||||
// Add occasional stutter effect
|
||||
if (Math.random() < TYPING_DELAYS.STUTTER.probability) {
|
||||
delay += TYPING_DELAYS.STUTTER.delay;
|
||||
}
|
||||
|
||||
return Math.max(delay, 1); // Ensure positive delay
|
||||
}
|
||||
|
||||
private completeText(textIndex: number, textItem: TextItem): void {
|
||||
// Mark text as complete
|
||||
this.displayedTexts.update((texts) => {
|
||||
const updatedTexts = [...texts];
|
||||
updatedTexts[textIndex] = {
|
||||
...textItem,
|
||||
isTyping: false,
|
||||
isComplete: true,
|
||||
};
|
||||
return updatedTexts;
|
||||
});
|
||||
|
||||
// Reset typing state and schedule next text
|
||||
this.isTypingInProgress.set(false);
|
||||
this.scheduleNextText();
|
||||
}
|
||||
|
||||
// Optimized tracking function
|
||||
trackByTextId = (index: number, item: TextItem): number => item.id;
|
||||
}
|
||||
|
||||
@@ -1,15 +1,31 @@
|
||||
<section class="">
|
||||
<!-- Video Section -->
|
||||
<article class="border-b h-[40vh] sm:h-[60vh] md:h-[50vh] flex items-center justify-center">
|
||||
<app-holo-video-container containerClasses="w-full aspect-video" videoSrc="cyber_hands.mp4" />
|
||||
</article>
|
||||
<section class="h-screen relative">
|
||||
<app-holo-video-container
|
||||
containerClasses="absolute inset-0 w-full h-full"
|
||||
videoSrc="cyber_hands.mp4" />
|
||||
|
||||
<!-- Display Text Section -->
|
||||
<article class="flex-1 flex items-center justify-center checkered-bg-dark pt-4 sm:pt-8 md:pt-12">
|
||||
<p class="font-terminal-nier scramble-text-glow
|
||||
text-6xl sm:text-5xl md:text-6xl lg:text-7xl xl:text-8xl 2xl:text-9xl
|
||||
text-center px-4 leading-tight">
|
||||
{{ displayText }}
|
||||
</p>
|
||||
</article>
|
||||
<!-- Changed from justify-center to justify-start with top padding -->
|
||||
<div class="absolute inset-0 flex flex-col items-center justify-start pt-20 sm:pt-24 md:pt-28 lg:pt-32 xl:pt-36 2xl:pt-40 pointer-events-none px-6 sm:px-8 md:px-12 lg:px-16 xl:px-20">
|
||||
|
||||
<div class="mb-8 sm:mb-12 md:mb-16 lg:mb-20 xl:mb-24 2xl:mb-32">
|
||||
<h1 class="font-terminal-nier scramble-text-glow text-cyan-200
|
||||
text-6xl sm:text-7xl md:text-8xl lg:text-9xl xl:text-[10rem] 2xl:text-[12rem]
|
||||
text-center
|
||||
leading-[1.1] sm:leading-[1.05] md:leading-[1.0] lg:leading-[0.95] xl:leading-[0.9] 2xl:leading-[0.85]
|
||||
font-black tracking-[-0.02em] pointer-events-auto
|
||||
drop-shadow-[0_0_30px_rgba(34,211,238,0.6)]
|
||||
max-w-[90vw] sm:max-w-[85vw] md:max-w-[80vw] lg:max-w-none break-words whitespace-pre-line">
|
||||
{{ nameText }}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div class="max-w-[95vw] sm:max-w-[90vw] md:max-w-[85vw] lg:max-w-[80vw] xl:max-w-none">
|
||||
<p class="font-terminal-nier scramble-text-glow text-cyan-100
|
||||
text-4xl sm:text-5xl md:text-6xl lg:text-7xl xl:text-8xl 2xl:text-9xl
|
||||
text-center leading-[0.8] font-bold tracking-wide uppercase pointer-events-auto
|
||||
drop-shadow-[0_0_25px_rgba(34,211,238,0.5)]
|
||||
break-words px-4 sm:px-6 md:px-8 lg:px-10">
|
||||
{{ displayText }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -9,6 +9,7 @@ import { HoloVideoContainerComponent } from '../../shared/ui/holo-video-containe
|
||||
})
|
||||
export class HeroDisplayComponent implements OnInit, OnDestroy {
|
||||
displayText: string = '';
|
||||
nameText: string = '';
|
||||
|
||||
private messageQueue: string[] = [
|
||||
'Web Developer',
|
||||
@@ -17,8 +18,11 @@ export class HeroDisplayComponent implements OnInit, OnDestroy {
|
||||
];
|
||||
|
||||
private currentMessage: string = '';
|
||||
private currentName: string = 'ADAM\nBENYEKKOU';
|
||||
private isGlitching: boolean = false;
|
||||
private isNameGlitching: boolean = false;
|
||||
private frameRequest: number | null = null;
|
||||
private nameFrameRequest: number | null = null;
|
||||
private processTimeout: any = null;
|
||||
private isInitialMount: boolean = true;
|
||||
|
||||
@@ -34,12 +38,57 @@ export class HeroDisplayComponent implements OnInit, OnDestroy {
|
||||
if (this.frameRequest) {
|
||||
cancelAnimationFrame(this.frameRequest);
|
||||
}
|
||||
if (this.nameFrameRequest) {
|
||||
cancelAnimationFrame(this.nameFrameRequest);
|
||||
}
|
||||
if (this.processTimeout) {
|
||||
clearTimeout(this.processTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
private initialMountAnimation(): void {
|
||||
// Start both animations simultaneously
|
||||
this.animateNameOnInit();
|
||||
this.animateDisplayTextOnInit();
|
||||
}
|
||||
|
||||
private animateNameOnInit(): void {
|
||||
const targetName = this.currentName;
|
||||
let currentIndex = 0;
|
||||
|
||||
const animateNextLetter = (): void => {
|
||||
if (currentIndex <= targetName.length) {
|
||||
let output = '';
|
||||
|
||||
// Build the confirmed part
|
||||
for (let i = 0; i < currentIndex; i++) {
|
||||
output += targetName[i];
|
||||
}
|
||||
|
||||
// Add intense scrambling for upcoming letters
|
||||
const scrambleLength = Math.min(8, targetName.length - currentIndex);
|
||||
for (let i = 0; i < scrambleLength; i++) {
|
||||
output += Math.random() > 0.5 ? '0' : '1';
|
||||
}
|
||||
|
||||
this.nameText = output;
|
||||
|
||||
if (currentIndex < targetName.length) {
|
||||
currentIndex++;
|
||||
setTimeout(animateNextLetter, 45 + Math.random() * 35); // Slightly slower for name
|
||||
} else {
|
||||
// Name animation complete, start glitching
|
||||
this.nameText = targetName;
|
||||
this.isNameGlitching = true;
|
||||
this.glitchName();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
animateNextLetter();
|
||||
}
|
||||
|
||||
private animateDisplayTextOnInit(): void {
|
||||
const firstMessage = this.messageQueue[0];
|
||||
let currentIndex = 0;
|
||||
|
||||
@@ -52,7 +101,7 @@ export class HeroDisplayComponent implements OnInit, OnDestroy {
|
||||
output += firstMessage[i];
|
||||
}
|
||||
|
||||
// Add intense scrambling for upcoming letters (very fast scramble)
|
||||
// Add intense scrambling for upcoming letters
|
||||
const scrambleLength = Math.min(6, firstMessage.length - currentIndex);
|
||||
for (let i = 0; i < scrambleLength; i++) {
|
||||
output += Math.random() > 0.5 ? '0' : '1';
|
||||
@@ -62,12 +111,12 @@ export class HeroDisplayComponent implements OnInit, OnDestroy {
|
||||
|
||||
if (currentIndex < firstMessage.length) {
|
||||
currentIndex++;
|
||||
setTimeout(animateNextLetter, 35 + Math.random() * 25); // Much faster: 35-60ms
|
||||
setTimeout(animateNextLetter, 35 + Math.random() * 25);
|
||||
} else {
|
||||
// Mount animation complete, start normal cycle
|
||||
this.displayText = firstMessage;
|
||||
this.currentMessage = firstMessage;
|
||||
this.messageQueue.shift(); // Remove the first message since we used it
|
||||
this.messageQueue.shift();
|
||||
this.isInitialMount = false;
|
||||
|
||||
// Start the regular cycle after a short pause
|
||||
@@ -95,7 +144,7 @@ export class HeroDisplayComponent implements OnInit, OnDestroy {
|
||||
|
||||
this.processTimeout = setTimeout(() => {
|
||||
this.processQueue();
|
||||
}, 6000); // Reduced from 10000 to 6000 (faster rotation)
|
||||
}, 6000);
|
||||
}
|
||||
|
||||
private startScrambleAnimation(nextMessage: string): void {
|
||||
@@ -146,7 +195,6 @@ export class HeroDisplayComponent implements OnInit, OnDestroy {
|
||||
const probability = Math.random();
|
||||
|
||||
if (probability < 0.05) {
|
||||
// Reduced from 0.2 to 0.05
|
||||
const scrambledText = this.currentMessage
|
||||
.split('')
|
||||
.map(() => (Math.random() > 0.5 ? '0' : '1'))
|
||||
@@ -157,10 +205,8 @@ export class HeroDisplayComponent implements OnInit, OnDestroy {
|
||||
this.displayText = this.currentMessage;
|
||||
}, 25);
|
||||
} else if (probability < 0.15) {
|
||||
// Reduced from 0.5 to 0.15
|
||||
const textArray = this.currentMessage.split('');
|
||||
for (let i = 0; i < Math.floor(textArray.length * 0.2); i++) {
|
||||
// Reduced from 0.4 to 0.2
|
||||
const idx = Math.floor(Math.random() * textArray.length);
|
||||
textArray[idx] = Math.random() > 0.5 ? '0' : '1';
|
||||
}
|
||||
@@ -173,11 +219,9 @@ export class HeroDisplayComponent implements OnInit, OnDestroy {
|
||||
|
||||
const jitterProbability = Math.random();
|
||||
if (jitterProbability < 0.1) {
|
||||
// Reduced from 0.5 to 0.1
|
||||
setTimeout(() => {
|
||||
const textArray = this.displayText.split('');
|
||||
for (let i = 0; i < 2; i++) {
|
||||
// Reduced from 4 to 2 characters
|
||||
const idx = Math.floor(Math.random() * textArray.length);
|
||||
if (textArray[idx] === '0' || textArray[idx] === '1') {
|
||||
textArray[idx] = textArray[idx] === '0' ? '1' : '0';
|
||||
@@ -192,7 +236,64 @@ export class HeroDisplayComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
this.frameRequest = requestAnimationFrame(() => {
|
||||
setTimeout(() => this.glitchText(), Math.random() * 500); // Increased from 150 to 500
|
||||
setTimeout(() => this.glitchText(), Math.random() * 500);
|
||||
});
|
||||
}
|
||||
|
||||
private glitchName(): void {
|
||||
if (!this.isNameGlitching) return;
|
||||
|
||||
const probability = Math.random();
|
||||
|
||||
if (probability < 0.03) {
|
||||
// Less frequent than displayText glitching
|
||||
const scrambledName = this.currentName
|
||||
.split('')
|
||||
.map((char) => (char === '\n' ? '\n' : Math.random() > 0.5 ? '0' : '1'))
|
||||
.join('');
|
||||
this.nameText = scrambledName;
|
||||
|
||||
setTimeout(() => {
|
||||
this.nameText = this.currentName;
|
||||
}, 30);
|
||||
} else if (probability < 0.08) {
|
||||
const nameArray = this.currentName.split('');
|
||||
for (let i = 0; i < Math.floor(nameArray.length * 0.15); i++) {
|
||||
const idx = Math.floor(Math.random() * nameArray.length);
|
||||
if (nameArray[idx] !== '\n') {
|
||||
// Don't replace line breaks
|
||||
nameArray[idx] = Math.random() > 0.5 ? '0' : '1';
|
||||
}
|
||||
}
|
||||
this.nameText = nameArray.join('');
|
||||
|
||||
setTimeout(() => {
|
||||
this.nameText = this.currentName;
|
||||
}, 25);
|
||||
}
|
||||
|
||||
// Subtle character jitter
|
||||
const jitterProbability = Math.random();
|
||||
if (jitterProbability < 0.05) {
|
||||
setTimeout(() => {
|
||||
const nameArray = this.nameText.split('');
|
||||
for (let i = 0; i < 1; i++) {
|
||||
// Just 1 character at a time for name
|
||||
const idx = Math.floor(Math.random() * nameArray.length);
|
||||
if (nameArray[idx] === '0' || nameArray[idx] === '1') {
|
||||
nameArray[idx] = nameArray[idx] === '0' ? '1' : '0';
|
||||
}
|
||||
}
|
||||
this.nameText = nameArray.join('');
|
||||
|
||||
setTimeout(() => {
|
||||
this.nameText = this.currentName;
|
||||
}, 20);
|
||||
}, 20);
|
||||
}
|
||||
|
||||
this.nameFrameRequest = requestAnimationFrame(() => {
|
||||
setTimeout(() => this.glitchName(), Math.random() * 800); // Slower glitching for name
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user