Added Logo and Header Text Animate Animation Typewriter and Glitch

This commit is contained in:
AdamBtech
2025-05-20 17:17:47 +02:00
parent 83cc2aec1c
commit 76a19d30b6
10 changed files with 672 additions and 21 deletions

View File

@@ -0,0 +1,264 @@
.nier-logo-container {
position: relative;
display: inline-block;
padding: 0.25rem;
overflow: hidden;
}
.nier-logo {
position: relative;
color: var(--nier-dark, #2C2A21); /* Using nier-dark color with fallback */
text-shadow: 0 0 2px rgba(44, 42, 33, 0.8);
animation: nier-fade-in 2.5s ease-out forwards, nier-subtle-pulse 4s 2.5s infinite;
}
.nier-logo::before {
content: attr(data-text);
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
text-shadow: 0 0 3px var(--nier-dark, #2C2A21);
opacity: 0;
animation: nier-glitch 6s 3s infinite;
}
.nier-logo::after {
content: attr(data-text);
position: absolute;
left: -2px;
top: 0;
width: 100%;
height: 100%;
text-shadow: -1px 0 1px rgba(44, 42, 33, 0.6);
opacity: 0;
animation: nier-glitch-2 5s 3s infinite;
}
.nier-scan-line {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(
to bottom,
transparent 0%,
rgba(44, 42, 33, 0.05) 50%,
transparent 100%
);
animation: nier-scan 3s linear infinite;
z-index: 2;
pointer-events: none;
}
.nier-blocks {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
pointer-events: none;
}
.nier-blocks::before,
.nier-blocks::after {
content: "";
position: absolute;
background: rgba(44, 42, 33, 0.1);
width: 10px;
height: 6px;
animation: nier-blocks 10s linear infinite;
}
.nier-blocks::before {
top: 20%;
left: 10%;
animation-delay: 1s;
}
.nier-blocks::after {
bottom: 40%;
right: 10%;
width: 15px;
height: 4px;
animation-delay: 3s;
}
/* Animations */
@keyframes nier-fade-in {
0% {
opacity: 0;
clip-path: inset(0 100% 0 0);
}
20% {
opacity: 0.3;
clip-path: inset(0 80% 0 0);
}
40% {
opacity: 0.5;
clip-path: inset(0 60% 0 0);
}
60% {
opacity: 0.7;
clip-path: inset(0 40% 0 0);
}
80% {
opacity: 0.9;
clip-path: inset(0 20% 0 0);
}
95% {
opacity: 1;
clip-path: inset(0 5% 0 0);
}
100% {
opacity: 1;
clip-path: inset(0 0 0 0);
}
}
@keyframes nier-subtle-pulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.9;
}
}
@keyframes nier-glitch {
0%, 100% {
opacity: 0;
transform: translateX(0);
}
10.5% {
opacity: 0.5;
transform: translateX(3px);
}
11% {
opacity: 0;
transform: translateX(0);
}
29.5% {
opacity: 0;
transform: translateX(0);
}
30% {
opacity: 0.4;
transform: translateX(-3px);
}
30.5% {
opacity: 0;
transform: translateX(0);
}
80% {
opacity: 0;
transform: translateX(0);
}
80.5% {
opacity: 0.6;
transform: translateX(5px);
}
81% {
opacity: 0;
transform: translateX(0);
}
}
@keyframes nier-glitch-2 {
0%, 100% {
opacity: 0;
transform: translateX(0);
}
10.5% {
opacity: 0;
transform: translateX(0);
}
11% {
opacity: 0.4;
transform: translateX(-2px);
}
11.5% {
opacity: 0;
transform: translateX(0);
}
50% {
opacity: 0;
transform: translateX(0);
}
50.5% {
opacity: 0.4;
transform: translateX(2px);
}
51% {
opacity: 0;
transform: translateX(0);
}
}
@keyframes nier-scan {
0% {
transform: translateY(-100%);
}
100% {
transform: translateY(100%);
}
}
@keyframes nier-blocks {
0% {
opacity: 0;
transform: translateY(0) translateX(0);
}
10% {
opacity: 0.8;
}
30% {
opacity: 0.6;
transform: translateY(20px) translateX(10px);
}
50% {
opacity: 0.4;
transform: translateY(40px) translateX(20px);
}
70% {
opacity: 0.2;
transform: translateY(60px) translateX(30px);
}
100% {
opacity: 0;
transform: translateY(100px) translateX(40px);
}
}
/* Add this for the small border and interface details */
.nier-logo-container::before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: 1px solid rgba(44, 42, 33, 0.3);
pointer-events: none;
}
/* Optional: Add small interface dots in the corner */
.nier-logo-container::after {
content: "";
position: absolute;
top: 5px;
right: 5px;
width: 3px;
height: 3px;
background-color: rgba(44, 42, 33, 0.6);
box-shadow:
-6px 0 0 rgba(44, 42, 33, 0.6),
-12px 0 0 rgba(44, 42, 33, 0.6),
0 6px 0 rgba(44, 42, 33, 0.6),
-6px 6px 0 rgba(44, 42, 33, 0.6),
-12px 6px 0 rgba(44, 42, 33, 0.6);
pointer-events: none;
}

View File

@@ -1 +1,5 @@
<p class="font-terminal-nier text-8xl">AB</p>
<div class="nier-logo-container">
<p class="font-terminal-nier text-8xl nier-logo" data-text="AB">AB</p>
<div class="nier-scan-line"></div>
<div class="nier-blocks"></div>
</div>

View File

@@ -1,8 +1,8 @@
<nav class="flex items-center justify-center">
<section class="grid grid-cols-2 gap-4 mt-4">
<app-button class="flex items-center" label="Neural Profile" (click)="onClick()"/>
<app-button class="flex items-center" label="Execute//Directory" (click)="onClick()"/>
<app-button class="flex items-center" label="Operative History" (click)="onClick()"/>
<app-button class="flex items-center" label="Transmission Link" (click)="onClick()"/>
<section class="grid grid-cols-2 gap-x-4 gap-y-5 my-4">
<app-button class="flex items-center h-10" label="Neural Profile" (click)="onClick()"/>
<app-button class="flex items-center h-10" label="Execute//Directory" (click)="onClick()"/>
<app-button class="flex items-center h-10" label="Operative History" (click)="onClick()"/>
<app-button class="flex items-center h-10" label="Transmission Link" (click)="onClick()"/>
</section>
</nav>

View File

@@ -0,0 +1,79 @@
.typewriter-container {
display: flex;
flex-direction: column;
gap: 10px;
font-family: 'Noto Sans JP', monospace;
}
.typewriter-item {
opacity: 0.9;
transition: opacity 0.3s ease;
position: relative;
}
.text-scroller-text {
display: inline-block;
letter-spacing: 0.08em;
}
.cursor {
display: inline-block;
animation: blink-and-glitch 1.2s infinite;
position: relative;
}
@keyframes blink-and-glitch {
0%, 100% {
opacity: 1;
transform: translateY(0);
}
40% {
opacity: 1;
}
50% {
opacity: 0;
}
75% {
opacity: 1;
transform: translateY(0);
}
76% {
opacity: 1;
transform: translateY(-3px);
}
78% {
opacity: 0.5;
transform: translateY(2px);
}
80% {
opacity: 1;
transform: translateY(0);
}
}
/* Occasional text flicker effect */
.typewriter-item:nth-child(2n+1) .text-scroller-text {
animation: text-flicker 4s infinite;
animation-delay: calc(var(--i, 0) * 1s);
}
@keyframes text-flicker {
0%, 100% {
opacity: 0.9;
}
92% {
opacity: 0.9;
}
92.5% {
opacity: 0.2;
}
92.8% {
opacity: 0.9;
}
93.5% {
opacity: 0.2;
}
94% {
opacity: 0.9;
}
}

View File

@@ -1 +1,13 @@
<p>header-text-animate-section works!</p>
<div class="typewriter-container">
@for (item of displayedTexts(); track item.id) {
<div class="typewriter-item">
<div class="text-scroller-text font-noto-jp text-sm">
{{ item.displayed }}
@if (item.isTyping) {
<span class="cursor">|</span>
}
</div>
</div>
}
</div>

View File

@@ -1,11 +1,244 @@
import { Component } from '@angular/core';
// header-text-animate-section.component.ts
import {
Component,
effect,
input,
OnDestroy,
OnInit,
signal,
} from '@angular/core';
import {
trigger,
state,
style,
transition,
animate,
} from '@angular/animations';
interface TextItem {
id: number;
text: string;
displayed: string;
isTyping: boolean;
isComplete: boolean;
}
@Component({
selector: 'app-header-text-animate-section',
standalone: true,
imports: [],
templateUrl: './header-text-animate-section.component.html',
styleUrl: './header-text-animate-section.component.css'
styleUrl: './header-text-animate-section.component.css',
animations: [
trigger('textChange', [
state(
'visible',
style({
opacity: 1,
transform: 'translateY(0)',
}),
),
state(
'hidden',
style({
opacity: 0,
transform: 'translateY(20px)',
}),
),
transition('visible => hidden', [animate('0.5s ease-out')]),
transition('hidden => visible', [animate('0.5s ease-in')]),
]),
],
})
export class HeaderTextAnimateSectionComponent {
export class HeaderTextAnimateSectionComponent implements OnInit, OnDestroy {
phrases = input<string[]>([
'Uploading guinea pig consciousness to the cloud...',
'Error: Sarcasm module overloaded. Rebooting...',
'Downloading personalities... 404: Personality not found.',
'Coffee.exe has stopped working. Attempting to reboot human...',
'Converting existential dread to binary...',
'Hacking the mainframe with a rubber duck...',
"Neural implant reports: You're still not cool enough...",
'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...',
]);
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
// Signals for internal state
displayedTexts = signal<TextItem[]>([]);
nextId = signal<number>(0);
phraseIndex = signal<number>(0);
isTypingInProgress = signal<boolean>(false);
private typewriterTimeouts: number[] = [];
private nextTextTimeout: any;
constructor() {
// Use effect to react to changes in phrases
effect(() => {
const phrasesList = this.phrases();
if (phrasesList.length > 0 && this.displayedTexts().length === 0) {
this.startNextText();
}
});
}
ngOnInit(): void {
if (this.phrases().length) {
this.startTextRotation();
}
}
ngOnDestroy(): void {
this.stopTextRotation();
this.clearTypewriterTimeouts();
}
private startTextRotation(): void {
// Start with the first text
this.startNextText();
}
private stopTextRotation(): void {
if (this.nextTextTimeout) {
clearTimeout(this.nextTextTimeout);
}
}
private clearTypewriterTimeouts(): void {
this.typewriterTimeouts.forEach((id) => window.clearTimeout(id));
this.typewriterTimeouts = [];
}
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];
// Update the index for the next time
this.phraseIndex.update((idx) => (idx + 1) % phrasesList.length);
// Create a new text item
const newItem: TextItem = {
id: this.nextId(),
text: nextText,
displayed: '',
isTyping: true,
isComplete: false,
};
// Update the nextId for the next time
this.nextId.update((id) => id + 1);
// Add the new text to the displayed texts list
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];
}
// 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);
}
private typeText(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;
if (currentLength < fullText.length) {
// Add one character
const newDisplayed = fullText.substring(0, currentLength + 1);
// Update the text
this.displayedTexts.update((texts) => {
const updatedTexts = [...texts];
updatedTexts[textIndex] = {
...textItem,
displayed: newDisplayed,
};
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;
}
const timeoutId = window.setTimeout(() => {
this.typeText(textId);
}, nextDelay);
this.typewriterTimeouts.push(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());
}
}
// Helper method to track items
trackByTextId(index: number, item: TextItem): number {
return item.id;
}
}