mirror of
https://github.com/adam-benyekkou/my_portfolio.git
synced 2026-01-15 20:20:09 +00:00
Added Logo and Header Text Animate Animation Typewriter and Glitch
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,36 @@
|
||||
<header>
|
||||
<section class="grid grid-cols-[1fr_5fr_4fr_4fr] gap-1 bg-nier-bg font-terminal text-nier-dark divide-x divide-nier-accent/30 border border-nier-accent h-32">
|
||||
<app-header-logo />
|
||||
<app-header-text-animate-section />
|
||||
<app-header-nav-links />
|
||||
<app-header-contact-links />
|
||||
<section
|
||||
class="grid grid-cols-[1fr_5fr_4fr_4fr] gap-1 bg-nier-bg font-terminal text-nier-dark divide-x divide-nier-accent/30 border border-nier-accent h-32"
|
||||
>
|
||||
<app-header-logo />
|
||||
<app-header-text-animate-section
|
||||
[phrases]="[
|
||||
'Converting existential dread to binary...',
|
||||
'Hacking the mainframe with a rubber duck...',
|
||||
'Error: Sarcasm module overloaded. Rebooting...',
|
||||
'Coffee.exe has stopped working. Attempting to reboot human...',
|
||||
'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...',
|
||||
'Decrypting corporate secrets... replaced with cat videos...',
|
||||
'Attempting to find motivation.exe... running scan...',
|
||||
'Corporate-mandated fun protocol initiated...',
|
||||
'Rerouting power to sarcasm generators...',
|
||||
'Cybernetic arm requesting high-five calibration...',
|
||||
'Deleting unnecessary small talk subroutines...',
|
||||
'AI analyzing your taste in music... respect decreased by 17%...',
|
||||
'Searching for affordable apartment in Night City... zero results...',
|
||||
'Tactical roomba squad deployed to kitchen sector...',
|
||||
'Virtual assistant has muted your notifications... all of them...',
|
||||
'Installing social skills patch 2.0... buffering...',
|
||||
'Your smart fridge: "The expired milk stays. I like the science experiment."',
|
||||
'Digital plant monitor reports: "Even cacti need water sometimes."',
|
||||
'Calculating fastest route to avoid small talk...',
|
||||
'Quantum computer processing your excuse... seems implausible...',
|
||||
]"
|
||||
[interval]="2500"
|
||||
/>
|
||||
<app-header-nav-links />
|
||||
<app-header-contact-links />
|
||||
</section>
|
||||
</header>
|
||||
|
||||
@@ -1,4 +1,35 @@
|
||||
.button-hover:hover {
|
||||
background-color: var(--color-nier-dark);
|
||||
color: var(--color-nier-text-light);
|
||||
.button-custom {
|
||||
cursor: pointer;
|
||||
background-color: var(--color-nier-mid);
|
||||
color: var(--color-nier-dark);
|
||||
position: relative;
|
||||
border: 1px solid transparent;
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.button-custom::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 3px; /* Space from top border */
|
||||
bottom: 3px; /* Space from bottom border */
|
||||
width: 0;
|
||||
height: auto; /* This makes it respect top and bottom values */
|
||||
background-color: var(--color-nier-dark);
|
||||
transition: width 0.2s ease;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.button-custom:hover,
|
||||
.button-custom:focus {
|
||||
color: var(--color-nier-text-light);
|
||||
background-color: transparent;
|
||||
border-color: var(--color-nier-dark);
|
||||
}
|
||||
|
||||
.button-custom:hover::before,
|
||||
.button-custom:focus::before {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<button
|
||||
class="w-48 text-left font-noto-jp text-base uppercase transition-all duration-300 bg-nier-mid text-nier-dark rounded-none button-hover p-1.5"
|
||||
class="w-48 text-left font-noto-jp text-base uppercase transition-all duration-300 rounded-none p-1.5 button-custom"
|
||||
(click)="handleClick()"
|
||||
>
|
||||
{{ label() }}
|
||||
<span class="relative z-10">{{ label() }}</span>
|
||||
</button>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>AngularPortfolio</title>
|
||||
<title>Adam Benyekkou Portfolio</title>
|
||||
<base href="/">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
|
||||
Reference in New Issue
Block a user