mirror of
https://github.com/adam-benyekkou/my_portfolio.git
synced 2026-01-15 20:20:09 +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>
|
<footer class="bg-nier-dark border-t border-nier-accent">
|
||||||
<section class="checkered-background border-t-5 border-nier-accent h-15 flex items-center bg-nier-light text-nier-light">
|
<div class="container mx-auto px-4 py-6">
|
||||||
<p class="text-1xl font-noto-jp">Made by Adam Benyekkou 2025</p>
|
<p class="text-center text-nier-light font-noto-jp text-sm">
|
||||||
</section>
|
© 2025 Adam Benyekkou. All rights reserved.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
|
|
||||||
<div class="typewriter-container">
|
<div class="typewriter-container">
|
||||||
@for (item of displayedTexts(); track item.id) {
|
@for (item of currentTexts(); track item.id) {
|
||||||
<div class="typewriter-item">
|
<div class="typewriter-item" [@slideIn]>
|
||||||
<div class="text-scroller-text font-noto-jp text-sm">
|
<div class="text-scroller-text font-noto-jp text-sm">
|
||||||
{{ item.displayed }}
|
{{ item.displayed }}
|
||||||
@if (item.isTyping) {
|
@if (item.isTyping) {
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import {
|
|||||||
OnDestroy,
|
OnDestroy,
|
||||||
OnInit,
|
OnInit,
|
||||||
signal,
|
signal,
|
||||||
|
computed,
|
||||||
|
ChangeDetectionStrategy,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import {
|
import {
|
||||||
trigger,
|
trigger,
|
||||||
@@ -16,19 +18,28 @@ import {
|
|||||||
} from '@angular/animations';
|
} from '@angular/animations';
|
||||||
|
|
||||||
interface TextItem {
|
interface TextItem {
|
||||||
id: number;
|
readonly id: number;
|
||||||
text: string;
|
readonly text: string;
|
||||||
displayed: string;
|
readonly displayed: string;
|
||||||
isTyping: boolean;
|
readonly isTyping: boolean;
|
||||||
isComplete: 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({
|
@Component({
|
||||||
selector: 'app-header-text-animate-section',
|
selector: 'app-header-text-animate-section',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [],
|
imports: [],
|
||||||
templateUrl: './header-text-animate-section.component.html',
|
templateUrl: './header-text-animate-section.component.html',
|
||||||
styleUrl: './header-text-animate-section.component.css',
|
styleUrl: './header-text-animate-section.component.css',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
animations: [
|
animations: [
|
||||||
trigger('textChange', [
|
trigger('textChange', [
|
||||||
state(
|
state(
|
||||||
@@ -45,13 +56,24 @@ interface TextItem {
|
|||||||
transform: 'translateY(20px)',
|
transform: 'translateY(20px)',
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
transition('visible => hidden', [animate('0.5s ease-out')]),
|
transition('visible => hidden', animate('300ms ease-out')),
|
||||||
transition('hidden => visible', [animate('0.5s ease-in')]),
|
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 {
|
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...',
|
'Uploading guinea pig consciousness to the cloud...',
|
||||||
'Error: Sarcasm module overloaded. Rebooting...',
|
'Error: Sarcasm module overloaded. Rebooting...',
|
||||||
'Downloading personalities... 404: Personality not found.',
|
'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...',
|
'Cybernetic guinea pigs have seized control of Sector 7...',
|
||||||
'ERROR: Reality.dll has crashed. Would you like to submit a bug report?',
|
'ERROR: Reality.dll has crashed. Would you like to submit a bug report?',
|
||||||
'Synthetic sushi generation complete. Tastes like chicken.exe...',
|
'Synthetic sushi generation complete. Tastes like chicken.exe...',
|
||||||
]);
|
] as const);
|
||||||
|
|
||||||
interval = input<number>(3000); // Default interval of 3 seconds
|
interval = input<number>(2500);
|
||||||
typingSpeed = input<number>(30); // Time in ms between each character (faster than before)
|
typingSpeed = input<number>(25);
|
||||||
maxDisplayedTexts = input<number>(4); // Maximum number of texts to display
|
maxDisplayedTexts = input<number>(4);
|
||||||
|
|
||||||
// Signals for internal state
|
// State signals
|
||||||
displayedTexts = signal<TextItem[]>([]);
|
private readonly displayedTexts = signal<readonly TextItem[]>([]);
|
||||||
nextId = signal<number>(0);
|
private readonly nextId = signal<number>(0);
|
||||||
phraseIndex = signal<number>(0);
|
private readonly phraseIndex = signal<number>(0);
|
||||||
isTypingInProgress = signal<boolean>(false);
|
private readonly isTypingInProgress = signal<boolean>(false);
|
||||||
|
|
||||||
private typewriterTimeouts: number[] = [];
|
// Computed signals for better performance
|
||||||
private nextTextTimeout: any;
|
readonly currentTexts = computed(() => this.displayedTexts());
|
||||||
|
readonly hasTexts = computed(() => this.displayedTexts().length > 0);
|
||||||
|
|
||||||
|
// Timeout management
|
||||||
|
private readonly activeTimeouts = new Set<number>();
|
||||||
|
private nextTextTimeout?: number;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
// Use effect to react to changes in phrases
|
// Initialize first text when phrases are available
|
||||||
effect(() => {
|
effect(() => {
|
||||||
const phrasesList = this.phrases();
|
const phrasesList = this.phrases();
|
||||||
if (phrasesList.length > 0 && this.displayedTexts().length === 0) {
|
if (phrasesList.length > 0 && this.displayedTexts().length === 0) {
|
||||||
this.startNextText();
|
this.scheduleNextText(0); // Start immediately
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
if (this.phrases().length) {
|
// Component initialization handled in constructor effect
|
||||||
this.startTextRotation();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
this.stopTextRotation();
|
this.cleanup();
|
||||||
this.clearTypewriterTimeouts();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private startTextRotation(): void {
|
private cleanup(): void {
|
||||||
// Start with the first text
|
// Clear all timeouts
|
||||||
this.startNextText();
|
this.activeTimeouts.forEach((id) => window.clearTimeout(id));
|
||||||
}
|
this.activeTimeouts.clear();
|
||||||
|
|
||||||
private stopTextRotation(): void {
|
|
||||||
if (this.nextTextTimeout) {
|
if (this.nextTextTimeout) {
|
||||||
clearTimeout(this.nextTextTimeout);
|
window.clearTimeout(this.nextTextTimeout);
|
||||||
|
this.nextTextTimeout = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private clearTypewriterTimeouts(): void {
|
private scheduleNextText(delay: number = this.interval()): void {
|
||||||
this.typewriterTimeouts.forEach((id) => window.clearTimeout(id));
|
if (this.nextTextTimeout) {
|
||||||
this.typewriterTimeouts = [];
|
window.clearTimeout(this.nextTextTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.nextTextTimeout = window.setTimeout(() => {
|
||||||
|
this.startNextText();
|
||||||
|
}, delay);
|
||||||
}
|
}
|
||||||
|
|
||||||
private startNextText(): void {
|
private startNextText(): void {
|
||||||
// If we're already typing, don't start a new one
|
|
||||||
if (this.isTypingInProgress()) return;
|
if (this.isTypingInProgress()) return;
|
||||||
|
|
||||||
const phrasesList = this.phrases();
|
const phrasesList = this.phrases();
|
||||||
if (phrasesList.length === 0) return;
|
if (phrasesList.length === 0) return;
|
||||||
|
|
||||||
// Get the next phrase to display
|
const currentIndex = this.phraseIndex();
|
||||||
const index = this.phraseIndex();
|
const nextText = phrasesList[currentIndex];
|
||||||
const nextText = phrasesList[index];
|
|
||||||
|
|
||||||
// Update the index for the next time
|
// Create immutable text item
|
||||||
this.phraseIndex.update((idx) => (idx + 1) % phrasesList.length);
|
|
||||||
|
|
||||||
// Create a new text item
|
|
||||||
const newItem: TextItem = {
|
const newItem: TextItem = {
|
||||||
id: this.nextId(),
|
id: this.nextId(),
|
||||||
text: nextText,
|
text: nextText,
|
||||||
@@ -137,44 +161,40 @@ export class HeaderTextAnimateSectionComponent implements OnInit, OnDestroy {
|
|||||||
isComplete: false,
|
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.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) => {
|
this.displayedTexts.update((texts) => {
|
||||||
// If we already have the maximum number of texts, remove the oldest one
|
const maxTexts = this.maxDisplayedTexts();
|
||||||
if (texts.length >= this.maxDisplayedTexts()) {
|
if (texts.length >= maxTexts) {
|
||||||
return [...texts.slice(1), newItem];
|
return [...texts.slice(texts.length - maxTexts + 1), newItem];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, just add the new text
|
|
||||||
return [...texts, newItem];
|
return [...texts, newItem];
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set typing in progress
|
// Start typing animation
|
||||||
this.isTypingInProgress.set(true);
|
this.animateText(newItem.id);
|
||||||
|
|
||||||
// Start the typewriter effect
|
|
||||||
this.typeText(newItem.id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private typeText(textId: number): void {
|
private animateText(textId: number): void {
|
||||||
const texts = this.displayedTexts();
|
const texts = this.displayedTexts();
|
||||||
const textIndex = texts.findIndex((t) => t.id === textId);
|
const textIndex = texts.findIndex((t) => t.id === textId);
|
||||||
|
|
||||||
if (textIndex === -1) return;
|
if (textIndex === -1) return;
|
||||||
|
|
||||||
const textItem = texts[textIndex];
|
const textItem = texts[textIndex];
|
||||||
const fullText = textItem.text;
|
const { text, displayed } = textItem;
|
||||||
const currentLength = textItem.displayed.length;
|
const nextCharIndex = displayed.length;
|
||||||
|
|
||||||
if (currentLength < fullText.length) {
|
if (nextCharIndex < text.length) {
|
||||||
// Add one character
|
// Update displayed text
|
||||||
const newDisplayed = fullText.substring(0, currentLength + 1);
|
const newDisplayed = text.substring(0, nextCharIndex + 1);
|
||||||
|
|
||||||
// Update the text
|
this.displayedTexts.update((currentTexts) => {
|
||||||
this.displayedTexts.update((texts) => {
|
const updatedTexts = [...currentTexts];
|
||||||
const updatedTexts = [...texts];
|
|
||||||
updatedTexts[textIndex] = {
|
updatedTexts[textIndex] = {
|
||||||
...textItem,
|
...textItem,
|
||||||
displayed: newDisplayed,
|
displayed: newDisplayed,
|
||||||
@@ -182,63 +202,76 @@ export class HeaderTextAnimateSectionComponent implements OnInit, OnDestroy {
|
|||||||
return updatedTexts;
|
return updatedTexts;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Hectic typing effect with much more randomization
|
// Schedule next character with dynamic timing
|
||||||
// Occasionally pause, sometimes type very fast, sometimes slower
|
const delay = this.calculateTypingDelay();
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
const timeoutId = window.setTimeout(() => {
|
const timeoutId = window.setTimeout(() => {
|
||||||
this.typeText(textId);
|
this.animateText(textId);
|
||||||
}, nextDelay);
|
}, delay);
|
||||||
|
|
||||||
this.typewriterTimeouts.push(timeoutId);
|
this.activeTimeouts.add(timeoutId);
|
||||||
} else {
|
} else {
|
||||||
// Text is complete
|
// Text complete
|
||||||
this.displayedTexts.update((texts) => {
|
this.completeText(textIndex, textItem);
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper method to track items
|
private calculateTypingDelay(): number {
|
||||||
trackByTextId(index: number, item: TextItem): number {
|
const random = Math.random();
|
||||||
return item.id;
|
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="">
|
<section class="h-screen relative">
|
||||||
<!-- Video Section -->
|
<app-holo-video-container
|
||||||
<article class="border-b h-[40vh] sm:h-[60vh] md:h-[50vh] flex items-center justify-center">
|
containerClasses="absolute inset-0 w-full h-full"
|
||||||
<app-holo-video-container containerClasses="w-full aspect-video" videoSrc="cyber_hands.mp4" />
|
videoSrc="cyber_hands.mp4" />
|
||||||
</article>
|
|
||||||
|
|
||||||
<!-- Display Text Section -->
|
<!-- Changed from justify-center to justify-start with top padding -->
|
||||||
<article class="flex-1 flex items-center justify-center checkered-bg-dark pt-4 sm:pt-8 md:pt-12">
|
<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">
|
||||||
<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
|
<div class="mb-8 sm:mb-12 md:mb-16 lg:mb-20 xl:mb-24 2xl:mb-32">
|
||||||
text-center px-4 leading-tight">
|
<h1 class="font-terminal-nier scramble-text-glow text-cyan-200
|
||||||
{{ displayText }}
|
text-6xl sm:text-7xl md:text-8xl lg:text-9xl xl:text-[10rem] 2xl:text-[12rem]
|
||||||
</p>
|
text-center
|
||||||
</article>
|
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>
|
</section>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { HoloVideoContainerComponent } from '../../shared/ui/holo-video-containe
|
|||||||
})
|
})
|
||||||
export class HeroDisplayComponent implements OnInit, OnDestroy {
|
export class HeroDisplayComponent implements OnInit, OnDestroy {
|
||||||
displayText: string = '';
|
displayText: string = '';
|
||||||
|
nameText: string = '';
|
||||||
|
|
||||||
private messageQueue: string[] = [
|
private messageQueue: string[] = [
|
||||||
'Web Developer',
|
'Web Developer',
|
||||||
@@ -17,8 +18,11 @@ export class HeroDisplayComponent implements OnInit, OnDestroy {
|
|||||||
];
|
];
|
||||||
|
|
||||||
private currentMessage: string = '';
|
private currentMessage: string = '';
|
||||||
|
private currentName: string = 'ADAM\nBENYEKKOU';
|
||||||
private isGlitching: boolean = false;
|
private isGlitching: boolean = false;
|
||||||
|
private isNameGlitching: boolean = false;
|
||||||
private frameRequest: number | null = null;
|
private frameRequest: number | null = null;
|
||||||
|
private nameFrameRequest: number | null = null;
|
||||||
private processTimeout: any = null;
|
private processTimeout: any = null;
|
||||||
private isInitialMount: boolean = true;
|
private isInitialMount: boolean = true;
|
||||||
|
|
||||||
@@ -34,12 +38,57 @@ export class HeroDisplayComponent implements OnInit, OnDestroy {
|
|||||||
if (this.frameRequest) {
|
if (this.frameRequest) {
|
||||||
cancelAnimationFrame(this.frameRequest);
|
cancelAnimationFrame(this.frameRequest);
|
||||||
}
|
}
|
||||||
|
if (this.nameFrameRequest) {
|
||||||
|
cancelAnimationFrame(this.nameFrameRequest);
|
||||||
|
}
|
||||||
if (this.processTimeout) {
|
if (this.processTimeout) {
|
||||||
clearTimeout(this.processTimeout);
|
clearTimeout(this.processTimeout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private initialMountAnimation(): void {
|
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];
|
const firstMessage = this.messageQueue[0];
|
||||||
let currentIndex = 0;
|
let currentIndex = 0;
|
||||||
|
|
||||||
@@ -52,7 +101,7 @@ export class HeroDisplayComponent implements OnInit, OnDestroy {
|
|||||||
output += firstMessage[i];
|
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);
|
const scrambleLength = Math.min(6, firstMessage.length - currentIndex);
|
||||||
for (let i = 0; i < scrambleLength; i++) {
|
for (let i = 0; i < scrambleLength; i++) {
|
||||||
output += Math.random() > 0.5 ? '0' : '1';
|
output += Math.random() > 0.5 ? '0' : '1';
|
||||||
@@ -62,12 +111,12 @@ export class HeroDisplayComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
if (currentIndex < firstMessage.length) {
|
if (currentIndex < firstMessage.length) {
|
||||||
currentIndex++;
|
currentIndex++;
|
||||||
setTimeout(animateNextLetter, 35 + Math.random() * 25); // Much faster: 35-60ms
|
setTimeout(animateNextLetter, 35 + Math.random() * 25);
|
||||||
} else {
|
} else {
|
||||||
// Mount animation complete, start normal cycle
|
// Mount animation complete, start normal cycle
|
||||||
this.displayText = firstMessage;
|
this.displayText = firstMessage;
|
||||||
this.currentMessage = firstMessage;
|
this.currentMessage = firstMessage;
|
||||||
this.messageQueue.shift(); // Remove the first message since we used it
|
this.messageQueue.shift();
|
||||||
this.isInitialMount = false;
|
this.isInitialMount = false;
|
||||||
|
|
||||||
// Start the regular cycle after a short pause
|
// Start the regular cycle after a short pause
|
||||||
@@ -95,7 +144,7 @@ export class HeroDisplayComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
this.processTimeout = setTimeout(() => {
|
this.processTimeout = setTimeout(() => {
|
||||||
this.processQueue();
|
this.processQueue();
|
||||||
}, 6000); // Reduced from 10000 to 6000 (faster rotation)
|
}, 6000);
|
||||||
}
|
}
|
||||||
|
|
||||||
private startScrambleAnimation(nextMessage: string): void {
|
private startScrambleAnimation(nextMessage: string): void {
|
||||||
@@ -146,7 +195,6 @@ export class HeroDisplayComponent implements OnInit, OnDestroy {
|
|||||||
const probability = Math.random();
|
const probability = Math.random();
|
||||||
|
|
||||||
if (probability < 0.05) {
|
if (probability < 0.05) {
|
||||||
// Reduced from 0.2 to 0.05
|
|
||||||
const scrambledText = this.currentMessage
|
const scrambledText = this.currentMessage
|
||||||
.split('')
|
.split('')
|
||||||
.map(() => (Math.random() > 0.5 ? '0' : '1'))
|
.map(() => (Math.random() > 0.5 ? '0' : '1'))
|
||||||
@@ -157,10 +205,8 @@ export class HeroDisplayComponent implements OnInit, OnDestroy {
|
|||||||
this.displayText = this.currentMessage;
|
this.displayText = this.currentMessage;
|
||||||
}, 25);
|
}, 25);
|
||||||
} else if (probability < 0.15) {
|
} else if (probability < 0.15) {
|
||||||
// Reduced from 0.5 to 0.15
|
|
||||||
const textArray = this.currentMessage.split('');
|
const textArray = this.currentMessage.split('');
|
||||||
for (let i = 0; i < Math.floor(textArray.length * 0.2); i++) {
|
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);
|
const idx = Math.floor(Math.random() * textArray.length);
|
||||||
textArray[idx] = Math.random() > 0.5 ? '0' : '1';
|
textArray[idx] = Math.random() > 0.5 ? '0' : '1';
|
||||||
}
|
}
|
||||||
@@ -173,11 +219,9 @@ export class HeroDisplayComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
const jitterProbability = Math.random();
|
const jitterProbability = Math.random();
|
||||||
if (jitterProbability < 0.1) {
|
if (jitterProbability < 0.1) {
|
||||||
// Reduced from 0.5 to 0.1
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const textArray = this.displayText.split('');
|
const textArray = this.displayText.split('');
|
||||||
for (let i = 0; i < 2; i++) {
|
for (let i = 0; i < 2; i++) {
|
||||||
// Reduced from 4 to 2 characters
|
|
||||||
const idx = Math.floor(Math.random() * textArray.length);
|
const idx = Math.floor(Math.random() * textArray.length);
|
||||||
if (textArray[idx] === '0' || textArray[idx] === '1') {
|
if (textArray[idx] === '0' || textArray[idx] === '1') {
|
||||||
textArray[idx] = textArray[idx] === '0' ? '1' : '0';
|
textArray[idx] = textArray[idx] === '0' ? '1' : '0';
|
||||||
@@ -192,7 +236,64 @@ export class HeroDisplayComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.frameRequest = requestAnimationFrame(() => {
|
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
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,12 +35,12 @@
|
|||||||
[interval]="2500"
|
[interval]="2500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-wrap justify-center py-3 space-y-2 sm:space-y-0 sm:space-x-4 border-b border-nier-accent/30">
|
<div class="flex flex-wrap justify-center gap-2 py-3 border-b border-nier-accent/30">
|
||||||
<app-header-nav-links />
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-wrap justify-center gap-2 py-3">
|
|
||||||
<app-header-contact-links />
|
<app-header-contact-links />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex flex-wrap justify-center py-3 space-y-2 sm:space-y-0 sm:space-x-4">
|
||||||
|
<app-header-nav-links />
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Tablet Layout (768px - 1023px) -->
|
<!-- Tablet Layout (768px - 1023px) -->
|
||||||
@@ -136,4 +136,3 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
|||||||
@@ -10,20 +10,6 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="absolute top-2 right-2 bg-black/80 border border-gray-600 rounded p-2 backdrop-blur-sm z-10">
|
|
||||||
<input
|
|
||||||
type="file"
|
|
||||||
accept="video/*"
|
|
||||||
(change)="onVideoFileSelect($event)"
|
|
||||||
#videoInput
|
|
||||||
class="hidden">
|
|
||||||
<button
|
|
||||||
class="bg-gray-700 text-white border border-gray-500 px-3 py-1 rounded text-xs transition-all duration-300 hover:bg-gray-600 hover:border-gray-400 active:translate-y-px disabled:opacity-60 disabled:cursor-not-allowed disabled:hover:bg-gray-700 disabled:hover:border-gray-500"
|
|
||||||
(click)="videoInput.click()"
|
|
||||||
[disabled]="isLoadingValue">
|
|
||||||
📁
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export class HoloVideoContainerComponent implements OnInit, OnDestroy {
|
|||||||
@ViewChild('canvas', { static: true })
|
@ViewChild('canvas', { static: true })
|
||||||
canvasRef!: ElementRef<HTMLCanvasElement>;
|
canvasRef!: ElementRef<HTMLCanvasElement>;
|
||||||
@Input() videoSrc?: string;
|
@Input() videoSrc?: string;
|
||||||
@Input() containerClasses: string = 'w-full h-96'; // Default with height
|
@Input() containerClasses: string = 'w-full h-96';
|
||||||
|
|
||||||
isLoading = signal(false);
|
isLoading = signal(false);
|
||||||
|
|
||||||
@@ -38,19 +38,16 @@ export class HoloVideoContainerComponent implements OnInit, OnDestroy {
|
|||||||
geometry: THREE.BufferGeometry;
|
geometry: THREE.BufferGeometry;
|
||||||
}> = [];
|
}> = [];
|
||||||
|
|
||||||
// Side decorations
|
|
||||||
private sideElements: THREE.Group[] = [];
|
|
||||||
|
|
||||||
private animationId: number = 0;
|
private animationId: number = 0;
|
||||||
private video!: HTMLVideoElement;
|
private video!: HTMLVideoElement;
|
||||||
private canvas2D!: HTMLCanvasElement;
|
private canvas2D!: HTMLCanvasElement;
|
||||||
private ctx2D!: CanvasRenderingContext2D;
|
private ctx2D!: CanvasRenderingContext2D;
|
||||||
|
|
||||||
readonly GRID_WIDTH = 320; // Increased from 240 for wider coverage
|
readonly GRID_WIDTH = 320;
|
||||||
readonly GRID_HEIGHT = 180;
|
readonly GRID_HEIGHT = 180;
|
||||||
readonly POINTS_COUNT = this.GRID_WIDTH * this.GRID_HEIGHT;
|
readonly POINTS_COUNT = this.GRID_WIDTH * this.GRID_HEIGHT;
|
||||||
|
|
||||||
// Mouse and effects
|
// Enhanced mouse and effects
|
||||||
private mouseX = 0;
|
private mouseX = 0;
|
||||||
private mouseY = 0;
|
private mouseY = 0;
|
||||||
private isMouseOverCanvas = false;
|
private isMouseOverCanvas = false;
|
||||||
@@ -58,10 +55,21 @@ export class HoloVideoContainerComponent implements OnInit, OnDestroy {
|
|||||||
private glitchIntensity = 0;
|
private glitchIntensity = 0;
|
||||||
private resizeObserver!: ResizeObserver;
|
private resizeObserver!: ResizeObserver;
|
||||||
|
|
||||||
|
// Enhanced scatter properties
|
||||||
|
private scatterIntensityMap = new Float32Array(this.POINTS_COUNT);
|
||||||
|
private scatterDecayMap = new Float32Array(this.POINTS_COUNT);
|
||||||
|
private readonly MAX_SCATTER_DISTANCE = 600; // Increased scatter range
|
||||||
|
private readonly SCATTER_STRENGTH = 45; // Much stronger scatter
|
||||||
|
private readonly GLOW_INTENSITY = 4.0; // Intense white glow multiplier
|
||||||
|
|
||||||
|
// Optimization: Reuse arrays
|
||||||
|
private tempPositions = new Float32Array(this.POINTS_COUNT * 3);
|
||||||
|
private tempColors = new Float32Array(this.POINTS_COUNT * 3);
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
effect(() => {
|
effect(() => {
|
||||||
if (this.material) {
|
if (this.material) {
|
||||||
this.material.size = 8.0; // Increased from 5.0 to 8.0
|
this.material.size = 6.0;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -69,12 +77,11 @@ export class HoloVideoContainerComponent implements OnInit, OnDestroy {
|
|||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.initThreeJS();
|
this.initThreeJS();
|
||||||
this.createPointCloud();
|
this.createPointCloud();
|
||||||
this.createSideDecorations();
|
|
||||||
this.setupVideo();
|
this.setupVideo();
|
||||||
this.setupEventListeners();
|
this.setupEventListeners();
|
||||||
this.setupResizeObserver();
|
this.setupResizeObserver();
|
||||||
this.animate();
|
this.animate();
|
||||||
this.onWindowResize(); // Initial resize
|
this.onWindowResize();
|
||||||
|
|
||||||
if (this.videoSrc) {
|
if (this.videoSrc) {
|
||||||
this.loadVideoFromSrc(this.videoSrc);
|
this.loadVideoFromSrc(this.videoSrc);
|
||||||
@@ -88,6 +95,14 @@ export class HoloVideoContainerComponent implements OnInit, OnDestroy {
|
|||||||
if (this.resizeObserver) {
|
if (this.resizeObserver) {
|
||||||
this.resizeObserver.disconnect();
|
this.resizeObserver.disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cleanup Three.js resources
|
||||||
|
this.geometry?.dispose();
|
||||||
|
this.material?.dispose();
|
||||||
|
this.glowLayers.forEach((layer) => {
|
||||||
|
layer.geometry.dispose();
|
||||||
|
(layer.points.material as THREE.Material).dispose();
|
||||||
|
});
|
||||||
this.renderer?.dispose();
|
this.renderer?.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,13 +111,16 @@ export class HoloVideoContainerComponent implements OnInit, OnDestroy {
|
|||||||
this.scene = new THREE.Scene();
|
this.scene = new THREE.Scene();
|
||||||
this.scene.background = new THREE.Color(0x0a0d14);
|
this.scene.background = new THREE.Color(0x0a0d14);
|
||||||
|
|
||||||
// Initialize camera with even wider FOV for closer view
|
|
||||||
this.camera = new THREE.PerspectiveCamera(40, 1, 1, 10000);
|
this.camera = new THREE.PerspectiveCamera(40, 1, 1, 10000);
|
||||||
this.camera.position.set(0, 0, 800);
|
this.camera.position.set(0, 0, 800);
|
||||||
this.camera.lookAt(0, 0, 0);
|
this.camera.lookAt(0, 0, 0);
|
||||||
|
|
||||||
this.renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
|
this.renderer = new THREE.WebGLRenderer({
|
||||||
this.renderer.setPixelRatio(window.devicePixelRatio);
|
canvas,
|
||||||
|
antialias: true,
|
||||||
|
powerPreference: 'high-performance',
|
||||||
|
});
|
||||||
|
this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
private setupResizeObserver(): void {
|
private setupResizeObserver(): void {
|
||||||
@@ -117,40 +135,25 @@ export class HoloVideoContainerComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Observe the canvas element
|
|
||||||
this.resizeObserver.observe(canvas);
|
this.resizeObserver.observe(canvas);
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleResize(width: number, height: number): void {
|
private handleResize(width: number, height: number): void {
|
||||||
// Update camera aspect ratio
|
|
||||||
this.camera.aspect = width / height;
|
this.camera.aspect = width / height;
|
||||||
this.camera.updateProjectionMatrix();
|
this.camera.updateProjectionMatrix();
|
||||||
|
|
||||||
// Update renderer size
|
|
||||||
this.renderer.setSize(width, height, false);
|
this.renderer.setSize(width, height, false);
|
||||||
|
|
||||||
// Calculate optimal camera distance
|
|
||||||
const fov = this.camera.fov * (Math.PI / 180);
|
const fov = this.camera.fov * (Math.PI / 180);
|
||||||
const gridHeight = this.GRID_HEIGHT * 12; // Updated to match new scale
|
const gridHeight = this.GRID_HEIGHT * 12;
|
||||||
const gridWidth = this.GRID_WIDTH * 12; // Updated to match new scale
|
const gridWidth = this.GRID_WIDTH * 12;
|
||||||
|
|
||||||
// Calculate visible dimensions at camera distance
|
|
||||||
const vFov = fov;
|
const vFov = fov;
|
||||||
const hFov = 2 * Math.atan(Math.tan(fov / 2) * this.camera.aspect);
|
const hFov = 2 * Math.atan(Math.tan(fov / 2) * this.camera.aspect);
|
||||||
|
|
||||||
// Calculate distance to fit width or height
|
|
||||||
const distanceHeight = gridHeight / 2 / Math.tan(vFov / 2);
|
const distanceHeight = gridHeight / 2 / Math.tan(vFov / 2);
|
||||||
const distanceWidth = gridWidth / 2 / Math.tan(hFov / 2);
|
const distanceWidth = gridWidth / 2 / Math.tan(hFov / 2);
|
||||||
|
|
||||||
// Use the distance that fits both dimensions with minimal margin
|
|
||||||
this.camera.position.z = Math.max(distanceHeight, distanceWidth) * 0.95;
|
this.camera.position.z = Math.max(distanceHeight, distanceWidth) * 0.95;
|
||||||
|
|
||||||
// Update side decorations position based on aspect ratio
|
|
||||||
if (this.sideElements.length >= 2) {
|
|
||||||
const sideOffset = this.camera.aspect * 1000;
|
|
||||||
this.sideElements[0].position.x = -sideOffset;
|
|
||||||
this.sideElements[1].position.x = sideOffset;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('window:resize')
|
@HostListener('window:resize')
|
||||||
@@ -172,8 +175,8 @@ export class HoloVideoContainerComponent implements OnInit, OnDestroy {
|
|||||||
let index = 0;
|
let index = 0;
|
||||||
for (let y = 0; y < this.GRID_HEIGHT; y++) {
|
for (let y = 0; y < this.GRID_HEIGHT; y++) {
|
||||||
for (let x = 0; x < this.GRID_WIDTH; x++) {
|
for (let x = 0; x < this.GRID_WIDTH; x++) {
|
||||||
const posX = (x - this.GRID_WIDTH / 2) * 12; // Reduced from 16 to compensate for wider grid
|
const posX = (x - this.GRID_WIDTH / 2) * 12;
|
||||||
const posY = -(y - this.GRID_HEIGHT / 2) * 12; // Reduced from 16 to maintain proportion
|
const posY = -(y - this.GRID_HEIGHT / 2) * 12;
|
||||||
|
|
||||||
positions[index * 3] = posX;
|
positions[index * 3] = posX;
|
||||||
positions[index * 3 + 1] = posY;
|
positions[index * 3 + 1] = posY;
|
||||||
@@ -183,7 +186,12 @@ export class HoloVideoContainerComponent implements OnInit, OnDestroy {
|
|||||||
originalPositions[index * 3 + 1] = posY;
|
originalPositions[index * 3 + 1] = posY;
|
||||||
originalPositions[index * 3 + 2] = 0;
|
originalPositions[index * 3 + 2] = 0;
|
||||||
|
|
||||||
colors[index * 3] = colors[index * 3 + 1] = colors[index * 3 + 2] = 1;
|
colors[index * 3] = colors[index * 3 + 1] = colors[index * 3 + 2] = 0.8;
|
||||||
|
|
||||||
|
// Initialize scatter maps
|
||||||
|
this.scatterIntensityMap[index] = 0;
|
||||||
|
this.scatterDecayMap[index] = 0;
|
||||||
|
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -199,7 +207,7 @@ export class HoloVideoContainerComponent implements OnInit, OnDestroy {
|
|||||||
);
|
);
|
||||||
|
|
||||||
this.material = new THREE.PointsMaterial({
|
this.material = new THREE.PointsMaterial({
|
||||||
size: 8.0, // Increased from 5.0 to 8.0
|
size: 6.0,
|
||||||
vertexColors: true,
|
vertexColors: true,
|
||||||
transparent: true,
|
transparent: true,
|
||||||
opacity: 1.0,
|
opacity: 1.0,
|
||||||
@@ -215,13 +223,14 @@ export class HoloVideoContainerComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private createGlowLayers(): void {
|
private createGlowLayers(): void {
|
||||||
|
// Enhanced glow layers for stronger effect
|
||||||
for (let layer = 1; layer <= 3; layer++) {
|
for (let layer = 1; layer <= 3; layer++) {
|
||||||
const glowGeometry = this.geometry.clone();
|
const glowGeometry = this.geometry.clone();
|
||||||
const glowMaterial = new THREE.PointsMaterial({
|
const glowMaterial = new THREE.PointsMaterial({
|
||||||
size: 8.0 * (1 + layer * 0.5), // Increased base size from 5.0 to 8.0
|
size: 6.0 * (1 + layer * 0.6), // Larger glow sizes
|
||||||
vertexColors: true,
|
vertexColors: true,
|
||||||
transparent: true,
|
transparent: true,
|
||||||
opacity: 0.3 / layer,
|
opacity: 0.4 / layer, // Increased opacity
|
||||||
alphaTest: 0.1,
|
alphaTest: 0.1,
|
||||||
depthWrite: false,
|
depthWrite: false,
|
||||||
blending: THREE.AdditiveBlending,
|
blending: THREE.AdditiveBlending,
|
||||||
@@ -234,145 +243,6 @@ export class HoloVideoContainerComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private createSideDecorations(): void {
|
|
||||||
// Create vertical scan lines on both sides
|
|
||||||
const createScanLines = (xPosition: number) => {
|
|
||||||
const group = new THREE.Group();
|
|
||||||
|
|
||||||
// Main vertical lines
|
|
||||||
for (let i = 0; i < 5; i++) {
|
|
||||||
const geometry = new THREE.BufferGeometry();
|
|
||||||
const positions = new Float32Array([
|
|
||||||
xPosition + (i - 2) * 30,
|
|
||||||
-1000,
|
|
||||||
0,
|
|
||||||
xPosition + (i - 2) * 30,
|
|
||||||
1000,
|
|
||||||
0,
|
|
||||||
]);
|
|
||||||
geometry.setAttribute(
|
|
||||||
'position',
|
|
||||||
new THREE.BufferAttribute(positions, 3),
|
|
||||||
);
|
|
||||||
|
|
||||||
const material = new THREE.LineBasicMaterial({
|
|
||||||
color: new THREE.Color(0.3, 0.6, 0.8),
|
|
||||||
transparent: true,
|
|
||||||
opacity: 0.3,
|
|
||||||
linewidth: 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
const line = new THREE.Line(geometry, material);
|
|
||||||
group.add(line);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Floating particles
|
|
||||||
const particleGeometry = new THREE.BufferGeometry();
|
|
||||||
const particleCount = 50;
|
|
||||||
const positions = new Float32Array(particleCount * 3);
|
|
||||||
const opacities = new Float32Array(particleCount);
|
|
||||||
|
|
||||||
for (let i = 0; i < particleCount; i++) {
|
|
||||||
positions[i * 3] = xPosition + (Math.random() - 0.5) * 150;
|
|
||||||
positions[i * 3 + 1] = (Math.random() - 0.5) * 1500;
|
|
||||||
positions[i * 3 + 2] = (Math.random() - 0.5) * 100;
|
|
||||||
opacities[i] = Math.random();
|
|
||||||
}
|
|
||||||
|
|
||||||
particleGeometry.setAttribute(
|
|
||||||
'position',
|
|
||||||
new THREE.BufferAttribute(positions, 3),
|
|
||||||
);
|
|
||||||
particleGeometry.setAttribute(
|
|
||||||
'opacity',
|
|
||||||
new THREE.BufferAttribute(opacities, 1),
|
|
||||||
);
|
|
||||||
|
|
||||||
const particleMaterial = new THREE.PointsMaterial({
|
|
||||||
size: 3,
|
|
||||||
color: new THREE.Color(0.5, 0.8, 1.0),
|
|
||||||
transparent: true,
|
|
||||||
opacity: 0.6,
|
|
||||||
blending: THREE.AdditiveBlending,
|
|
||||||
vertexColors: false,
|
|
||||||
sizeAttenuation: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const particles = new THREE.Points(particleGeometry, particleMaterial);
|
|
||||||
group.add(particles);
|
|
||||||
|
|
||||||
// Add grid pattern
|
|
||||||
const gridGroup = new THREE.Group();
|
|
||||||
const gridSize = 100;
|
|
||||||
const gridDivisions = 10;
|
|
||||||
|
|
||||||
for (let j = -5; j <= 5; j++) {
|
|
||||||
const lineGeometry = new THREE.BufferGeometry();
|
|
||||||
const linePositions = new Float32Array([
|
|
||||||
xPosition - gridSize,
|
|
||||||
(j * gridSize) / 5,
|
|
||||||
-50,
|
|
||||||
xPosition + gridSize,
|
|
||||||
(j * gridSize) / 5,
|
|
||||||
-50,
|
|
||||||
]);
|
|
||||||
lineGeometry.setAttribute(
|
|
||||||
'position',
|
|
||||||
new THREE.BufferAttribute(linePositions, 3),
|
|
||||||
);
|
|
||||||
|
|
||||||
const lineMaterial = new THREE.LineBasicMaterial({
|
|
||||||
color: new THREE.Color(0.2, 0.4, 0.6),
|
|
||||||
transparent: true,
|
|
||||||
opacity: 0.2,
|
|
||||||
});
|
|
||||||
|
|
||||||
const gridLine = new THREE.Line(lineGeometry, lineMaterial);
|
|
||||||
gridGroup.add(gridLine);
|
|
||||||
}
|
|
||||||
|
|
||||||
group.add(gridGroup);
|
|
||||||
return group;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create decorations for left and right sides
|
|
||||||
const leftDecoration = createScanLines(-2000);
|
|
||||||
const rightDecoration = createScanLines(2000);
|
|
||||||
|
|
||||||
this.sideElements.push(leftDecoration, rightDecoration);
|
|
||||||
this.scene.add(leftDecoration);
|
|
||||||
this.scene.add(rightDecoration);
|
|
||||||
}
|
|
||||||
|
|
||||||
private animateSideElements(time: number): void {
|
|
||||||
this.sideElements.forEach((group, index) => {
|
|
||||||
// Animate vertical lines
|
|
||||||
group.children.forEach((child, childIndex) => {
|
|
||||||
if (child instanceof THREE.Line && childIndex < 5) {
|
|
||||||
child.material.opacity =
|
|
||||||
0.2 + Math.sin(time * 0.001 + childIndex) * 0.1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Animate particles
|
|
||||||
if (child instanceof THREE.Points) {
|
|
||||||
child.rotation.y = time * 0.0001;
|
|
||||||
const positions = child.geometry.attributes.position
|
|
||||||
.array as Float32Array;
|
|
||||||
for (let i = 0; i < positions.length; i += 3) {
|
|
||||||
positions[i + 1] += Math.sin(time * 0.001 + i) * 0.5;
|
|
||||||
if (positions[i + 1] > 750) positions[i + 1] = -750;
|
|
||||||
}
|
|
||||||
child.geometry.attributes.position.needsUpdate = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Animate grid
|
|
||||||
if (child instanceof THREE.Group) {
|
|
||||||
child.rotation.z = Math.sin(time * 0.0005) * 0.05;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private setupVideo(): void {
|
private setupVideo(): void {
|
||||||
this.video = document.createElement('video');
|
this.video = document.createElement('video');
|
||||||
this.video.width = this.GRID_WIDTH;
|
this.video.width = this.GRID_WIDTH;
|
||||||
@@ -406,17 +276,18 @@ export class HoloVideoContainerComponent implements OnInit, OnDestroy {
|
|||||||
const centerX = this.GRID_WIDTH / 2;
|
const centerX = this.GRID_WIDTH / 2;
|
||||||
const centerY = this.GRID_HEIGHT / 2;
|
const centerY = this.GRID_HEIGHT / 2;
|
||||||
const distance = Math.sqrt((x - centerX) ** 2 + (y - centerY) ** 2);
|
const distance = Math.sqrt((x - centerX) ** 2 + (y - centerY) ** 2);
|
||||||
const wave = Math.sin(distance * 0.1 + time * 0.05) * 0.5 + 0.5;
|
const wave = Math.sin(distance * 0.08 + time * 0.04) * 0.5 + 0.5;
|
||||||
const noise =
|
const noise =
|
||||||
Math.sin(x * 0.1 + time * 0.02) * Math.cos(y * 0.1 + time * 0.03);
|
Math.sin(x * 0.05 + time * 0.015) *
|
||||||
|
Math.cos(y * 0.05 + time * 0.02);
|
||||||
const intensity = Math.max(
|
const intensity = Math.max(
|
||||||
0,
|
0,
|
||||||
Math.min(255, (wave + noise * 0.3) * 255),
|
Math.min(255, (wave + noise * 0.4) * 255),
|
||||||
);
|
);
|
||||||
|
|
||||||
data[index] = intensity;
|
data[index] = intensity;
|
||||||
data[index + 1] = intensity * 0.8;
|
data[index + 1] = intensity * 0.85;
|
||||||
data[index + 2] = intensity * 0.6;
|
data[index + 2] = intensity * 0.7;
|
||||||
data[index + 3] = 255;
|
data[index + 3] = 255;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -435,10 +306,11 @@ export class HoloVideoContainerComponent implements OnInit, OnDestroy {
|
|||||||
private updatePointCloud(): void {
|
private updatePointCloud(): void {
|
||||||
if (!this.video.videoWidth || !this.video.videoHeight) return;
|
if (!this.video.videoWidth || !this.video.videoHeight) return;
|
||||||
|
|
||||||
|
// Enhanced glitch system
|
||||||
this.glitchTime++;
|
this.glitchTime++;
|
||||||
if (Math.random() < 0.003) {
|
if (Math.random() < 0.008) {
|
||||||
this.glitchIntensity = Math.random() * 0.7 + 0.3;
|
this.glitchIntensity = Math.random() * 1.2 + 0.6;
|
||||||
setTimeout(() => (this.glitchIntensity = 0), Math.random() * 1000 + 500);
|
setTimeout(() => (this.glitchIntensity = 0), Math.random() * 500 + 150);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.ctx2D.drawImage(this.video, 0, 0, this.GRID_WIDTH, this.GRID_HEIGHT);
|
this.ctx2D.drawImage(this.video, 0, 0, this.GRID_WIDTH, this.GRID_HEIGHT);
|
||||||
@@ -458,9 +330,22 @@ export class HoloVideoContainerComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
const rect = this.canvasRef.nativeElement.getBoundingClientRect();
|
const rect = this.canvasRef.nativeElement.getBoundingClientRect();
|
||||||
const mouseWorldX =
|
const mouseWorldX =
|
||||||
((this.mouseX / rect.width) * 2 - 1) * (this.GRID_WIDTH * 6); // Adjusted for new scale
|
((this.mouseX / rect.width) * 2 - 1) * (this.GRID_WIDTH * 6);
|
||||||
const mouseWorldY =
|
const mouseWorldY =
|
||||||
-((this.mouseY / rect.height) * 2 - 1) * (this.GRID_HEIGHT * 6); // Adjusted for new scale
|
-((this.mouseY / rect.height) * 2 - 1) * (this.GRID_HEIGHT * 6);
|
||||||
|
|
||||||
|
const isGlitching = this.glitchIntensity > 0;
|
||||||
|
|
||||||
|
// Update scatter decay for all points
|
||||||
|
for (let i = 0; i < this.POINTS_COUNT; i++) {
|
||||||
|
if (this.scatterDecayMap[i] > 0) {
|
||||||
|
this.scatterDecayMap[i] *= 0.92; // Slower decay for longer effect
|
||||||
|
if (this.scatterDecayMap[i] < 0.01) {
|
||||||
|
this.scatterDecayMap[i] = 0;
|
||||||
|
this.scatterIntensityMap[i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let index = 0;
|
let index = 0;
|
||||||
for (let y = 0; y < this.GRID_HEIGHT; y++) {
|
for (let y = 0; y < this.GRID_HEIGHT; y++) {
|
||||||
@@ -491,40 +376,99 @@ export class HoloVideoContainerComponent implements OnInit, OnDestroy {
|
|||||||
Math.pow(originalY - mouseWorldY, 2),
|
Math.pow(originalY - mouseWorldY, 2),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// ENHANCED: Much stronger scatter effect with persistent intensity
|
||||||
let scatterX = 0,
|
let scatterX = 0,
|
||||||
scatterY = 0,
|
scatterY = 0,
|
||||||
scatterZ = 0;
|
scatterZ = 0;
|
||||||
if (this.isMouseOverCanvas && distanceToMouse < 50) {
|
let currentScatterIntensity = this.scatterIntensityMap[index];
|
||||||
const scatterFactor = 1 - distanceToMouse / 50;
|
|
||||||
const scatterIntensity = scatterFactor * 20;
|
if (
|
||||||
|
this.isMouseOverCanvas &&
|
||||||
|
distanceToMouse < this.MAX_SCATTER_DISTANCE
|
||||||
|
) {
|
||||||
|
// Calculate scatter factor with explosive falloff
|
||||||
|
const scatterFactor = Math.pow(
|
||||||
|
1 - distanceToMouse / this.MAX_SCATTER_DISTANCE,
|
||||||
|
1.5,
|
||||||
|
);
|
||||||
|
const newScatterIntensity = scatterFactor * this.SCATTER_STRENGTH;
|
||||||
|
|
||||||
|
// Update scatter intensity (with momentum for explosive transitions)
|
||||||
|
if (newScatterIntensity > currentScatterIntensity) {
|
||||||
|
this.scatterIntensityMap[index] = newScatterIntensity;
|
||||||
|
this.scatterDecayMap[index] = 1.0; // Full decay strength
|
||||||
|
currentScatterIntensity = newScatterIntensity;
|
||||||
|
}
|
||||||
|
|
||||||
const deltaX = originalX - mouseWorldX;
|
const deltaX = originalX - mouseWorldX;
|
||||||
const deltaY = originalY - mouseWorldY;
|
const deltaY = originalY - mouseWorldY;
|
||||||
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
||||||
|
|
||||||
if (distance > 0) {
|
if (distance > 0) {
|
||||||
scatterX = (deltaX / distance) * scatterIntensity;
|
// Explosive scatter with dramatic randomness
|
||||||
scatterY = (deltaY / distance) * scatterIntensity;
|
const randomFactor = 0.6 + Math.random() * 0.8; // Increased randomness
|
||||||
scatterZ = scatterIntensity * 0.2;
|
const explosionMultiplier = 1.5 + Math.random() * 0.5; // Extra explosion force
|
||||||
|
|
||||||
|
scatterX =
|
||||||
|
(deltaX / distance) *
|
||||||
|
currentScatterIntensity *
|
||||||
|
randomFactor *
|
||||||
|
explosionMultiplier;
|
||||||
|
scatterY =
|
||||||
|
(deltaY / distance) *
|
||||||
|
currentScatterIntensity *
|
||||||
|
randomFactor *
|
||||||
|
explosionMultiplier;
|
||||||
|
scatterZ = currentScatterIntensity * 0.6 * randomFactor;
|
||||||
|
|
||||||
|
// Add chaotic perpendicular scatter for spiral explosion
|
||||||
|
const perpX = -deltaY / distance;
|
||||||
|
const perpY = deltaX / distance;
|
||||||
|
const perpScatter =
|
||||||
|
(Math.random() - 0.5) * currentScatterIntensity * 0.5;
|
||||||
|
scatterX += perpX * perpScatter;
|
||||||
|
scatterY += perpY * perpScatter;
|
||||||
|
}
|
||||||
|
} else if (currentScatterIntensity > 0) {
|
||||||
|
// Apply decaying scatter effect
|
||||||
|
const decayFactor = this.scatterDecayMap[index];
|
||||||
|
const deltaX = originalX - mouseWorldX;
|
||||||
|
const deltaY = originalY - mouseWorldY;
|
||||||
|
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
||||||
|
|
||||||
|
if (distance > 0) {
|
||||||
|
scatterX =
|
||||||
|
(deltaX / distance) * currentScatterIntensity * decayFactor;
|
||||||
|
scatterY =
|
||||||
|
(deltaY / distance) * currentScatterIntensity * decayFactor;
|
||||||
|
scatterZ = currentScatterIntensity * 0.4 * decayFactor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Glitch effect
|
// Enhanced glitch effect
|
||||||
let glitchX = 0,
|
let glitchX = 0,
|
||||||
glitchY = 0;
|
glitchY = 0,
|
||||||
if (
|
glitchZ = 0;
|
||||||
this.glitchIntensity > 0 &&
|
if (isGlitching) {
|
||||||
Math.random() < this.glitchIntensity * 0.1
|
glitchX = (Math.random() - 0.5) * this.glitchIntensity * 35;
|
||||||
) {
|
glitchY = (Math.random() - 0.5) * this.glitchIntensity * 35;
|
||||||
glitchX = (Math.random() - 0.5) * this.glitchIntensity * 25;
|
glitchZ = (Math.random() - 0.5) * this.glitchIntensity * 30;
|
||||||
glitchY = (Math.random() - 0.5) * this.glitchIntensity * 25;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
positions[index * 3] = originalX + scatterX + glitchX;
|
positions[index * 3] = originalX + scatterX + glitchX;
|
||||||
positions[index * 3 + 1] = originalY + scatterY + glitchY;
|
positions[index * 3 + 1] = originalY + scatterY + glitchY;
|
||||||
positions[index * 3 + 2] = baseZ + scatterZ;
|
positions[index * 3 + 2] = baseZ + scatterZ + glitchZ;
|
||||||
|
|
||||||
|
// Apply enhanced colors with strong white glow for scattered points
|
||||||
|
this.applyEnhancedHolographicColors(
|
||||||
|
colors,
|
||||||
|
index,
|
||||||
|
brightness,
|
||||||
|
distanceToMouse,
|
||||||
|
currentScatterIntensity,
|
||||||
|
isGlitching,
|
||||||
|
);
|
||||||
|
|
||||||
// Apply holographic colors
|
|
||||||
this.applyHolographicColors(colors, index, brightness, distanceToMouse);
|
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -534,33 +478,68 @@ export class HoloVideoContainerComponent implements OnInit, OnDestroy {
|
|||||||
this.updateGlowLayers();
|
this.updateGlowLayers();
|
||||||
}
|
}
|
||||||
|
|
||||||
private applyHolographicColors(
|
private applyEnhancedHolographicColors(
|
||||||
colors: Float32Array,
|
colors: Float32Array,
|
||||||
index: number,
|
index: number,
|
||||||
brightness: number,
|
brightness: number,
|
||||||
distanceToMouse: number,
|
distanceToMouse: number,
|
||||||
|
scatterIntensity: number,
|
||||||
|
isGlitching: boolean,
|
||||||
): void {
|
): void {
|
||||||
let finalColor = [0.5, 0.5, 0.5]; // Default grey
|
let finalColor = [0.5, 0.5, 0.5];
|
||||||
|
|
||||||
if (brightness > 220) finalColor = [1.0, 1.0, 1.0];
|
// Base color mapping
|
||||||
else if (brightness > 180) finalColor = [0.9, 0.9, 0.9];
|
if (brightness > 220) finalColor = [0.95, 0.95, 0.95];
|
||||||
|
else if (brightness > 180) finalColor = [0.85, 0.85, 0.85];
|
||||||
else if (brightness > 140) finalColor = [0.7, 0.7, 0.7];
|
else if (brightness > 140) finalColor = [0.7, 0.7, 0.7];
|
||||||
else if (brightness > 100) finalColor = [0.5, 0.5, 0.5];
|
else if (brightness > 100) finalColor = [0.55, 0.55, 0.55];
|
||||||
else if (brightness > 60) finalColor = [0.3, 0.3, 0.3];
|
else if (brightness > 60) finalColor = [0.4, 0.4, 0.4];
|
||||||
else finalColor = [0.1, 0.1, 0.1];
|
else finalColor = [0.25, 0.25, 0.25];
|
||||||
|
|
||||||
// Add subtle color variations
|
// Subtle color variations
|
||||||
const variation = (index % 7) / 20;
|
const variation = (index % 5) / 25;
|
||||||
finalColor[0] += variation * 0.1;
|
finalColor[0] += variation * 0.08;
|
||||||
finalColor[1] += variation * 0.05;
|
finalColor[1] += variation * 0.04;
|
||||||
finalColor[2] += variation * 0.15;
|
finalColor[2] += variation * 0.12;
|
||||||
|
|
||||||
// Mouse glow effect
|
// DRAMATIC WHITE GLOW for scattered points
|
||||||
if (this.isMouseOverCanvas && distanceToMouse < 50) {
|
if (scatterIntensity > 0) {
|
||||||
const glowFactor = (1 - distanceToMouse / 50) * 0.6;
|
const glowFactor =
|
||||||
finalColor[0] = Math.min(1.0, finalColor[0] + glowFactor * 0.7);
|
(scatterIntensity / this.SCATTER_STRENGTH) * this.GLOW_INTENSITY;
|
||||||
finalColor[1] = Math.min(1.0, finalColor[1] + glowFactor * 0.8);
|
|
||||||
finalColor[2] = Math.min(1.0, finalColor[2] + glowFactor * 1.0);
|
// Progressive white explosion effect
|
||||||
|
if (glowFactor > 2.0) {
|
||||||
|
// Complete white explosion for heavily scattered points
|
||||||
|
finalColor = [1.0, 1.0, 1.0];
|
||||||
|
} else if (glowFactor > 1.0) {
|
||||||
|
// Intense white glow
|
||||||
|
finalColor[0] = Math.min(1.0, finalColor[0] + glowFactor * 0.8);
|
||||||
|
finalColor[1] = Math.min(1.0, finalColor[1] + glowFactor * 0.9);
|
||||||
|
finalColor[2] = Math.min(1.0, finalColor[2] + glowFactor * 1.0);
|
||||||
|
} else {
|
||||||
|
// Moderate white glow with blue tint
|
||||||
|
finalColor[0] = Math.min(1.0, finalColor[0] + glowFactor * 0.6);
|
||||||
|
finalColor[1] = Math.min(1.0, finalColor[1] + glowFactor * 0.8);
|
||||||
|
finalColor[2] = Math.min(1.0, finalColor[2] + glowFactor * 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add extra white flash for very scattered points
|
||||||
|
if (scatterIntensity > this.SCATTER_STRENGTH * 0.7) {
|
||||||
|
finalColor[0] = finalColor[1] = finalColor[2] = 1.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enhanced glitch effect with more white flashing
|
||||||
|
if (isGlitching) {
|
||||||
|
const glitchWhiteness = this.glitchIntensity * 0.8;
|
||||||
|
finalColor[0] = Math.min(1.0, finalColor[0] + glitchWhiteness);
|
||||||
|
finalColor[1] = Math.min(1.0, finalColor[1] + glitchWhiteness);
|
||||||
|
finalColor[2] = Math.min(1.0, finalColor[2] + glitchWhiteness);
|
||||||
|
|
||||||
|
// More frequent pure white flashes during glitch
|
||||||
|
if (Math.random() < 0.25) {
|
||||||
|
finalColor[0] = finalColor[1] = finalColor[2] = 1.0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
colors[index * 3] = finalColor[0];
|
colors[index * 3] = finalColor[0];
|
||||||
@@ -569,27 +548,35 @@ export class HoloVideoContainerComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private updateGlowLayers(): void {
|
private updateGlowLayers(): void {
|
||||||
|
const mainPositions = this.geometry.attributes['position']
|
||||||
|
.array as Float32Array;
|
||||||
|
const mainColors = this.geometry.attributes['color'].array as Float32Array;
|
||||||
|
|
||||||
this.glowLayers.forEach((layer, layerIndex) => {
|
this.glowLayers.forEach((layer, layerIndex) => {
|
||||||
const layerPositions = layer.geometry.attributes['position']
|
const layerPositions = layer.geometry.attributes['position']
|
||||||
.array as Float32Array;
|
.array as Float32Array;
|
||||||
const layerColors = layer.geometry.attributes['color']
|
const layerColors = layer.geometry.attributes['color']
|
||||||
.array as Float32Array;
|
.array as Float32Array;
|
||||||
const mainPositions = this.geometry.attributes['position']
|
|
||||||
.array as Float32Array;
|
|
||||||
const mainColors = this.geometry.attributes['color']
|
|
||||||
.array as Float32Array;
|
|
||||||
|
|
||||||
|
// Copy positions with slight offset for depth
|
||||||
for (let i = 0; i < mainPositions.length; i += 3) {
|
for (let i = 0; i < mainPositions.length; i += 3) {
|
||||||
layerPositions[i] = mainPositions[i];
|
layerPositions[i] = mainPositions[i];
|
||||||
layerPositions[i + 1] = mainPositions[i + 1];
|
layerPositions[i + 1] = mainPositions[i + 1];
|
||||||
layerPositions[i + 2] = mainPositions[i + 2] - (layerIndex + 1) * 2;
|
layerPositions[i + 2] = mainPositions[i + 2] - (layerIndex + 1) * 4; // Increased offset
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Enhanced glow colors with stronger intensity for white particles
|
||||||
|
const glowIntensity = 0.9 / (layerIndex + 1); // Increased from 0.7
|
||||||
for (let i = 0; i < mainColors.length; i += 3) {
|
for (let i = 0; i < mainColors.length; i += 3) {
|
||||||
const glowIntensity = 0.8 / (layerIndex + 1);
|
// Amplify white/bright colors dramatically in glow layers
|
||||||
layerColors[i] = mainColors[i] * glowIntensity;
|
const brightnessFactor =
|
||||||
layerColors[i + 1] = mainColors[i + 1] * glowIntensity;
|
(mainColors[i] + mainColors[i + 1] + mainColors[i + 2]) / 3;
|
||||||
layerColors[i + 2] = mainColors[i + 2] * glowIntensity;
|
const isWhite = brightnessFactor > 0.9;
|
||||||
|
const glowBoost = isWhite ? 2.5 : brightnessFactor > 0.8 ? 1.8 : 1.0;
|
||||||
|
|
||||||
|
layerColors[i] = mainColors[i] * glowIntensity * glowBoost;
|
||||||
|
layerColors[i + 1] = mainColors[i + 1] * glowIntensity * glowBoost;
|
||||||
|
layerColors[i + 2] = mainColors[i + 2] * glowIntensity * glowBoost;
|
||||||
}
|
}
|
||||||
|
|
||||||
layer.geometry.attributes['position'].needsUpdate = true;
|
layer.geometry.attributes['position'].needsUpdate = true;
|
||||||
@@ -610,19 +597,15 @@ export class HoloVideoContainerComponent implements OnInit, OnDestroy {
|
|||||||
'mouseenter',
|
'mouseenter',
|
||||||
() => (this.isMouseOverCanvas = true),
|
() => (this.isMouseOverCanvas = true),
|
||||||
);
|
);
|
||||||
canvas.addEventListener(
|
canvas.addEventListener('mouseleave', () => {
|
||||||
'mouseleave',
|
this.isMouseOverCanvas = false;
|
||||||
() => (this.isMouseOverCanvas = false),
|
// Don't reset scatter immediately - let it decay naturally
|
||||||
);
|
});
|
||||||
|
|
||||||
// Removed wheel event listener - no more zooming
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private animate(): void {
|
private animate(): void {
|
||||||
this.animationId = requestAnimationFrame(() => this.animate());
|
this.animationId = requestAnimationFrame(() => this.animate());
|
||||||
const time = Date.now();
|
|
||||||
this.updatePointCloud();
|
this.updatePointCloud();
|
||||||
this.animateSideElements(time);
|
|
||||||
this.renderer.render(this.scene, this.camera);
|
this.renderer.render(this.scene, this.camera);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user