From 06bb9df500c9d54099f5f598e9d9ff797cd72f13 Mon Sep 17 00:00:00 2001 From: AdamBtech <60339324+AdamBtech@users.noreply.github.com> Date: Tue, 27 May 2025 12:03:10 +0200 Subject: [PATCH] Added optimization / patched memory leaks for several animation and Threejs component --- src/app/components/button/nav-button.css | 144 +-- .../container-decorator.component.css | 0 .../container-decorator.component.html | 1 - .../container-decorator.component.ts | 11 - .../header-contact-links.component.css | 275 ++---- .../header-switch-theme-button.component.css | 54 +- .../header-logo/header-logo.component.css | 240 +++-- .../header-text-animate-section.component.ts | 234 ++--- .../holo-video-container.component.css | 17 +- .../holo-video-container.component.ts | 144 ++- .../infocard/infocard.component.css | 0 .../infocard/infocard.component.html | 1 - .../components/infocard/infocard.component.ts | 11 - .../long-button/long-button.component.css | 0 .../long-button/long-button.component.html | 1 - .../long-button/long-button.component.ts | 11 - src/app/pages/about/about.component.css | 635 ++++--------- .../neural-profile-tree.component.css | 191 ++-- .../contact-content.component.css | 248 +++++ .../contact-content.component.html | 99 ++ .../contact-content.component.ts | 11 + src/app/pages/contact/contact.component.html | 10 +- src/app/pages/contact/contact.component.ts | 3 +- src/app/pages/projects/projects.component.css | 884 +++--------------- src/styles.css | 192 ++-- 25 files changed, 1326 insertions(+), 2091 deletions(-) delete mode 100644 src/app/components/container-decorator/container-decorator.component.css delete mode 100644 src/app/components/container-decorator/container-decorator.component.html delete mode 100644 src/app/components/container-decorator/container-decorator.component.ts delete mode 100644 src/app/components/infocard/infocard.component.css delete mode 100644 src/app/components/infocard/infocard.component.html delete mode 100644 src/app/components/infocard/infocard.component.ts delete mode 100644 src/app/components/long-button/long-button.component.css delete mode 100644 src/app/components/long-button/long-button.component.html delete mode 100644 src/app/components/long-button/long-button.component.ts create mode 100644 src/app/pages/contact/contact-content/contact-content.component.css create mode 100644 src/app/pages/contact/contact-content/contact-content.component.html create mode 100644 src/app/pages/contact/contact-content/contact-content.component.ts diff --git a/src/app/components/button/nav-button.css b/src/app/components/button/nav-button.css index 1e74e0d..b5dbdbe 100644 --- a/src/app/components/button/nav-button.css +++ b/src/app/components/button/nav-button.css @@ -1,3 +1,24 @@ +/* ===================== OPTIMIZED NIER BUTTON ===================== */ + +/* Core keyframes - simplified and optimized */ +@keyframes glitchEffect { + 0%, 100% { opacity: 0; transform: translateX(0); } + 10% { opacity: 0.2; transform: translateX(-2px); clip-path: inset(10% 0 80% 0); } + 20% { opacity: 0.2; transform: translateX(2px); clip-path: inset(30% 0 50% 0); } + 30% { opacity: 0.1; transform: translateX(-1px); clip-path: inset(50% 0 20% 0); } +} + +@keyframes scanLine { + from { opacity: 0.5; left: -100%; } + to { opacity: 0; left: 100%; } +} + +@keyframes flash { + from { opacity: 0.2; } + to { opacity: 0; } +} + +/* Button base styles */ .button-custom { cursor: pointer; background-color: var(--color-nier-mid); @@ -7,43 +28,25 @@ border-left: none; border-right: none; overflow: hidden; - transition: color 0.2s ease, background-color 0.2s ease, border-color 0.2s ease; + transition: all 0.2s ease; } +/* Fill effect */ .button-custom::before { content: ""; position: absolute; - left: 0; - top: 3px; /* Space from top border */ - bottom: 3px; /* Space from bottom border */ + inset: 3px 0; 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; } -/* Normal hover/focus state - keep your original behavior */ -.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%; -} - -/* Add NieR-style glitch effects on hover */ +/* Glitch text overlay */ .button-custom::after { - content: attr(data-label); /* Use the button's text from data-label attribute */ + content: attr(data-label); position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; + inset: 0; display: flex; align-items: center; justify-content: center; @@ -53,11 +56,7 @@ pointer-events: none; } -.button-custom:hover::after { - animation: nier-button-glitch 0.6s ease forwards; -} - -/* Add subtle scan line on hover */ +/* Scan line element */ .button-custom .scan-line { position: absolute; top: 0; @@ -70,80 +69,35 @@ pointer-events: none; } +/* ===================== HOVER STATES ===================== */ + +.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%; +} + +.button-custom:hover::after { + animation: glitchEffect 0.6s ease forwards; +} + .button-custom:hover .scan-line { - animation: nier-button-scan 0.3s ease forwards; + animation: scanLine 0.3s ease forwards; } -/* The glitch effect animation */ -@keyframes nier-button-glitch { - 0%, 100% { - opacity: 0; - transform: translateX(0); - clip-path: inset(0 0 0 0); - } - 10% { - opacity: 0.2; - transform: translateX(-2px); - clip-path: inset(10% 0 80% 0); - } - 12% { - opacity: 0; - transform: translateX(0); - } - 20% { - opacity: 0.2; - transform: translateX(2px); - clip-path: inset(30% 0 50% 0); - } - 22% { - opacity: 0; - transform: translateX(0); - } - 30% { - opacity: 0.1; - transform: translateX(-1px); - clip-path: inset(50% 0 20% 0); - } - 32% { - opacity: 0; - transform: translateX(0); - } -} +/* ===================== ACTIVE STATES ===================== */ -/* The scan line animation */ -@keyframes nier-button-scan { - 0% { - opacity: 0.5; - left: -100%; - } - 100% { - opacity: 0; - left: 100%; - } -} - -/* Add a brief effect on button click */ .button-custom:active { transform: scale(0.98); } .button-custom:active::after { - content: ""; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; background-color: rgba(255, 255, 255, 0.1); - opacity: 0; - animation: nier-button-flash 0.2s ease; -} - -@keyframes nier-button-flash { - 0% { - opacity: 0.2; - } - 100% { - opacity: 0; - } + animation: flash 0.2s ease; } diff --git a/src/app/components/container-decorator/container-decorator.component.css b/src/app/components/container-decorator/container-decorator.component.css deleted file mode 100644 index e69de29..0000000 diff --git a/src/app/components/container-decorator/container-decorator.component.html b/src/app/components/container-decorator/container-decorator.component.html deleted file mode 100644 index 61a89b6..0000000 --- a/src/app/components/container-decorator/container-decorator.component.html +++ /dev/null @@ -1 +0,0 @@ -

container-decorator works!

diff --git a/src/app/components/container-decorator/container-decorator.component.ts b/src/app/components/container-decorator/container-decorator.component.ts deleted file mode 100644 index fdfe1f3..0000000 --- a/src/app/components/container-decorator/container-decorator.component.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Component } from '@angular/core'; - -@Component({ - selector: 'app-container-decorator', - imports: [], - templateUrl: './container-decorator.component.html', - styleUrl: './container-decorator.component.css' -}) -export class ContainerDecoratorComponent { - -} diff --git a/src/app/components/header/header-contact-links/header-contact-links.component.css b/src/app/components/header/header-contact-links/header-contact-links.component.css index 74750da..c61042e 100644 --- a/src/app/components/header/header-contact-links/header-contact-links.component.css +++ b/src/app/components/header/header-contact-links/header-contact-links.component.css @@ -1,3 +1,25 @@ +/* ===================== OPTIMIZED SVG BUTTONS & LINKS ===================== */ + +/* Core keyframes - reused across components */ +@keyframes glitchEffect { + 0%, 100% { opacity: 0; transform: translateX(0); clip-path: inset(0); } + 10% { opacity: 0.2; transform: translateX(-2px); clip-path: inset(10% 0 80% 0); } + 20% { opacity: 0.2; transform: translateX(2px); clip-path: inset(30% 0 50% 0); } + 30% { opacity: 0.1; transform: translateX(-1px); clip-path: inset(50% 0 20% 0); } +} + +@keyframes scanLine { + from { opacity: 0.5; left: -100%; } + to { opacity: 0; left: 100%; } +} + +@keyframes flash { + from { opacity: 0.2; } + to { opacity: 0; } +} + +/* ===================== SVG BUTTON CONTAINER ===================== */ + .svg-button-container { position: relative; cursor: pointer; @@ -5,14 +27,11 @@ transition: transform 0.2s ease; -webkit-tap-highlight-color: transparent; user-select: none; - - /* Add these properties to match button-custom */ border: 1px solid transparent; border-left: none; border-right: none; } -/* SVG icon styling */ .svg-icon { fill: var(--color-nier-dark, #5a5a50); position: relative; @@ -20,45 +39,22 @@ transition: fill 0.3s ease; } -/* Background fill effect - matching button-custom */ +/* Fill effect */ .svg-button-container::before { content: ""; position: absolute; - left: 0; - top: 3px; /* Space from top border */ - bottom: 3px; /* Space from bottom border */ + inset: 3px 0; width: 0; - height: auto; /* This makes it respect top and bottom values */ background-color: var(--color-nier-dark, rgba(30, 30, 30, 0.8)); transition: width 0.2s ease; z-index: 0; } -/* Hover state */ -.svg-button-container:hover, -.svg-button-container:focus { - border-color: var(--color-nier-dark); -} - -.svg-button-container:hover::before, -.svg-button-container:focus::before { - width: 100%; -} - -/* Change SVG fill on hover */ -.svg-button-container:hover .svg-icon, -.svg-button-container:active .svg-icon { - fill: var(--color-nier-text-light, #dcd8c0); -} - -/* Glitch effect on hover */ +/* Glitch text overlay */ .svg-button-container::after { content: attr(data-label); position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; + inset: 0; display: flex; align-items: center; justify-content: center; @@ -71,15 +67,10 @@ font-family: "Noto Sans JP", monospace; } -.svg-button-container:hover::after { - animation: nier-button-glitch 0.6s ease forwards; -} - -/* Scan line effect */ +/* Scan line */ .svg-button-container .scan-line { position: absolute; - top: 0; - left: -100%; + inset: 0 0 auto -100%; width: 100%; height: 1px; background-color: var(--color-nier-text-light, rgba(230, 230, 230, 0.9)); @@ -88,100 +79,42 @@ pointer-events: none; } +/* Hover states */ +.svg-button-container:hover, +.svg-button-container:focus { + border-color: var(--color-nier-dark); +} + +.svg-button-container:hover::before, +.svg-button-container:focus::before { + width: 100%; +} + +.svg-button-container:hover .svg-icon, +.svg-button-container:active .svg-icon { + fill: var(--color-nier-text-light, #dcd8c0); +} + +.svg-button-container:hover::after { + animation: glitchEffect 0.6s ease forwards; +} + .svg-button-container:hover .scan-line { - animation: nier-button-scan 0.3s ease forwards; + animation: scanLine 0.3s ease forwards; } -/* The glitch effect animation */ -@keyframes nier-button-glitch { - 0%, 100% { - opacity: 0; - transform: translateX(0); - clip-path: inset(0 0 0 0); - } - 10% { - opacity: 0.2; - transform: translateX(-2px); - clip-path: inset(10% 0 80% 0); - } - 12% { - opacity: 0; - transform: translateX(0); - } - 20% { - opacity: 0.2; - transform: translateX(2px); - clip-path: inset(30% 0 50% 0); - } - 22% { - opacity: 0; - transform: translateX(0); - } - 30% { - opacity: 0.1; - transform: translateX(-1px); - clip-path: inset(50% 0 20% 0); - } - 32% { - opacity: 0; - transform: translateX(0); - } -} - -/* The scan line animation */ -@keyframes nier-button-scan { - 0% { - opacity: 0.5; - left: -100%; - } - 100% { - opacity: 0; - left: 100%; - } -} - -/* Button click effect */ +/* Active state */ .svg-button-container:active { transform: scale(0.98); } .svg-button-container:active::after { - content: ""; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; background-color: rgba(255, 255, 255, 0.1); - opacity: 0; - animation: nier-button-flash 0.2s ease; + animation: flash 0.2s ease; } -@keyframes nier-button-flash { - 0% { - opacity: 0.2; - } - 100% { - opacity: 0; - } -} +/* ===================== TEXT LINKS ===================== */ -/* Touch device optimization */ -@media (hover: none) { - .svg-button-container:active::before { - width: 100%; - } - - .svg-button-container:active .scan-line { - animation: nier-button-scan 0.3s ease forwards; - } - - .svg-button-container:active .svg-icon { - fill: var(--color-nier-dark, #dcd8c0); - } -} - -/* Styling for text links with NieR aesthetic */ a[data-label] { position: relative; cursor: pointer; @@ -194,7 +127,6 @@ a[data-label] { border-bottom: 1px solid transparent; } -/* Underline effect */ a[data-label]::before { content: ""; position: absolute; @@ -207,27 +139,9 @@ a[data-label]::before { z-index: 0; } -/* Text color change on hover */ -a[data-label]:hover { - color: var(--color-nier-dark, #dcd8c0); - border-bottom-color: var(--color-nier-dark); -} - -/* Underline animation on hover */ -a[data-label]:hover::before { - width: 100%; -} - -/* Glitch effect on hover (similar to buttons) */ -a[data-label]:hover span:first-child { - animation: nier-text-glitch 0.6s ease; -} - -/* Scan line for links */ a[data-label] .scan-line { position: absolute; - top: 0; - left: -100%; + inset: 0 0 auto -100%; width: 100%; height: 1px; background-color: var(--color-nier-dark, rgba(230, 230, 230, 0.9)); @@ -236,65 +150,62 @@ a[data-label] .scan-line { pointer-events: none; } +/* Link hover states */ +a[data-label]:hover { + color: var(--color-nier-dark, #dcd8c0); + border-bottom-color: var(--color-nier-dark); +} + +a[data-label]:hover::before { + width: 100%; +} + +a[data-label]:hover span:first-child { + animation: glitchEffect 0.6s ease; +} + a[data-label]:hover .scan-line { - animation: nier-button-scan 0.3s ease forwards; + animation: scanLine 0.3s ease forwards; } -/* Text glitch animation for links */ -@keyframes nier-text-glitch { - 0%, 100% { - transform: translateX(0); - clip-path: inset(0 0 0 0); - } - 10% { - transform: translateX(-2px); - clip-path: inset(0 0 40% 0); - } - 15% { - transform: translateX(0); - } - 20% { - transform: translateX(1px); - clip-path: inset(40% 0 0 0); - } - 25% { - transform: translateX(0); - } - 30% { - transform: translateX(-1px); - clip-path: inset(20% 0 20% 0); - } - 35% { - transform: translateX(0); - } -} - -/* Active/click state for links */ a[data-label]:active { transform: scale(0.98); } -/* Touch device optimization */ +/* ===================== ICON STYLES ===================== */ + +.icon-email { + width: 150px; + height: 150px; + background-color: var(--color-nier-text-dark); + -webkit-mask: url('/email_qr_black.svg') no-repeat center / contain; + mask: url('/email_qr_black.svg') no-repeat center / contain; +} + +/* ===================== TOUCH DEVICES ===================== */ + @media (hover: none) { + .svg-button-container:active::before { + width: 100%; + } + + .svg-button-container:active .scan-line { + animation: scanLine 0.3s ease forwards; + } + + .svg-button-container:active .svg-icon { + fill: var(--color-nier-dark, #dcd8c0); + } + a[data-label]:active::before { width: 100%; } a[data-label]:active .scan-line { - animation: nier-button-scan 0.3s ease forwards; + animation: scanLine 0.3s ease forwards; } a[data-label]:active { color: var(--color-nier-dark, #dcd8c0); } } - -.icon-email { - width: 150px; - height: 150px; - background-color: var(--color-nier-text-dark); - -webkit-mask: url('/email_qr_black.svg') no-repeat center; - mask: url('/email_qr_black.svg') no-repeat center; - -webkit-mask-size: contain; - mask-size: contain; -} diff --git a/src/app/components/header/header-contact-links/header-switch-theme-button/header-switch-theme-button.component.css b/src/app/components/header/header-contact-links/header-switch-theme-button/header-switch-theme-button.component.css index b3576dc..c5acc52 100644 --- a/src/app/components/header/header-contact-links/header-switch-theme-button/header-switch-theme-button.component.css +++ b/src/app/components/header/header-contact-links/header-switch-theme-button/header-switch-theme-button.component.css @@ -1,62 +1,52 @@ -/* header-switch-theme-button.component.scss */ +/* ===================== OPTIMIZED THEME SWITCH BUTTON ===================== */ -/* Base styling for SVG elements */ +/* Base SVG styling */ .svg-icon { - transition: fill 0.3s ease, color 0.3s ease, transform 0.3s ease; + transition: all 0.3s ease; } -/* Moon cutout styling - hidden by default in light mode */ -.moon-cutout { - transition: fill 0.3s ease, transform 0.3s ease, opacity 0.3s ease; - fill: #1a1a18; /* Default darker color */ - opacity: 0; /* Hidden in light mode */ -} - -/* LIGHT MODE (default) - show sun */ -/* Base circle acts as the sun */ .svg-icon circle[cx="12"][cy="12"][r="5"] { fill: currentColor; } -/* DARK MODE - show moon */ -/* Show the moon cutout in dark mode */ +/* Moon cutout - hidden by default */ +.moon-cutout { + fill: #1a1a18; + opacity: 0; + transition: all 0.3s ease; +} + +/* ===================== THEME STATES ===================== */ + +/* Dark mode - show moon */ :host-context(.dark) .moon-cutout, body[data-theme="dark"] .moon-cutout { fill: var(--color-nier-dark-bg, #292925); - opacity: 1; /* Visible in dark mode */ -} - -/* HOVER STATE - always show moon with rotation */ -/* Rotation animation on hover */ -:host-context(.svg-button-container:hover) .svg-icon, -.svg-button-container:hover .svg-icon { - transform: rotate(45deg); - color: var(--color-nier-text-light, #dcd8c0); - fill: var(--color-nier-text-light, #dcd8c0); -} - -/* Always show moon cutout on hover */ -:host-context(.svg-button-container:hover) .moon-cutout, -.svg-button-container:hover .moon-cutout { opacity: 1; - fill: #3a3a34; /* Dark color for cutout on hover */ } -/* ACTIVE STATE - same as hover */ +/* ===================== INTERACTIVE STATES ===================== */ + +/* Hover and active states */ +:host-context(.svg-button-container:hover) .svg-icon, :host-context(.svg-button-container:active) .svg-icon, +.svg-button-container:hover .svg-icon, .svg-button-container:active .svg-icon { transform: rotate(45deg); color: var(--color-nier-text-light, #dcd8c0); fill: var(--color-nier-text-light, #dcd8c0); } +:host-context(.svg-button-container:hover) .moon-cutout, :host-context(.svg-button-container:active) .moon-cutout, +.svg-button-container:hover .moon-cutout, .svg-button-container:active .moon-cutout { opacity: 1; fill: #3a3a34; } -/* Touch device support */ +/* ===================== TOUCH DEVICES ===================== */ + @media (hover: none) { :host-context(.svg-button-container:active) .svg-icon, .svg-button-container:active .svg-icon { diff --git a/src/app/components/header/header-logo/header-logo.component.css b/src/app/components/header/header-logo/header-logo.component.css index 9992e41..8e677a5 100644 --- a/src/app/components/header/header-logo/header-logo.component.css +++ b/src/app/components/header/header-logo/header-logo.component.css @@ -1,4 +1,35 @@ -/* This simpler approach uses your existing global variables directly */ +/* ===================== OPTIMIZED NIER LOGO ===================== */ + +/* Core keyframes - simplified and optimized */ +@keyframes fadeInReveal { + 0% { opacity: 0; clip-path: inset(0 100% 0 0); } + 100% { opacity: 1; clip-path: inset(0 0 0 0); } +} + +@keyframes pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.9; } +} + +@keyframes glitch { + 0%, 100% { opacity: 0; transform: translateX(0); } + 10.5% { opacity: 0.5; transform: translateX(3px); } + 30% { opacity: 0.4; transform: translateX(-3px); } + 80.5% { opacity: 0.6; transform: translateX(5px); } +} + +@keyframes scanLine { + from { transform: translateY(-100%); } + to { transform: translateY(100%); } +} + +@keyframes floatingBlocks { + 0% { opacity: 0; transform: translate(0, 0); } + 10% { opacity: 0.8; } + 100% { opacity: 0; transform: translate(40px, 100px); } +} + +/* ===================== LOGO CONTAINER ===================== */ .nier-logo-container { position: relative; @@ -8,107 +39,15 @@ transition: all 0.3s ease; } -.nier-logo { - position: relative; - color: var(--color-nier-text-dark); /* Use your global variable */ - text-shadow: 0 0 2px var(--color-nier-text-dark); - animation: nier-fade-in 2.5s ease-out forwards, nier-subtle-pulse 4s 2.5s infinite; - transition: color 0.3s ease, text-shadow 0.3s ease; -} - -.nier-logo::before { - content: attr(data-text); - position: absolute; - left: 0; - top: 0; - width: 100%; - height: 100%; - text-shadow: 0 0 3px var(--color-nier-text-dark); - opacity: 0; - animation: nier-glitch 6s 3s infinite; - transition: text-shadow 0.3s ease; -} - -.nier-logo::after { - content: attr(data-text); - position: absolute; - left: -2px; - top: 0; - width: 100%; - height: 100%; - text-shadow: -1px 0 1px var(--color-nier-text-dark); - opacity: 0; - animation: nier-glitch-2 5s 3s infinite; - transition: text-shadow 0.3s ease; -} - -.nier-scan-line { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: linear-gradient( - to bottom, - transparent 0%, - color-mix(in srgb, var(--color-nier-text-dark) 5%, transparent) 50%, - transparent 100% - ); - animation: nier-scan 3s linear infinite; - z-index: 2; - pointer-events: none; - transition: background 0.3s ease; -} - -.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; - width: 10px; - height: 6px; - background: color-mix(in srgb, var(--color-nier-text-dark) 10%, transparent); - animation: nier-blocks 10s linear infinite; - transition: background 0.3s ease; -} - -.nier-blocks::before { - top: 20%; - left: 10%; - animation-delay: 1s; -} - -.nier-blocks::after { - bottom: 40%; - right: 10%; - width: 15px; - height: 4px; - animation-delay: 3s; -} - -/* Border and interface details */ .nier-logo-container::before { content: ""; position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; + inset: 0; border: 1px solid var(--color-nier-border); pointer-events: none; transition: border-color 0.3s ease; } -/* Interface dots in the corner */ .nier-logo-container::after { content: ""; position: absolute; @@ -124,57 +63,88 @@ -6px 6px 0 var(--color-nier-text-dark), -12px 6px 0 var(--color-nier-text-dark); pointer-events: none; - transition: background-color 0.3s ease, box-shadow 0.3s ease; + transition: all 0.3s ease; } -/* Animations unchanged */ -@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); } +/* ===================== LOGO TEXT ===================== */ + +.nier-logo { + position: relative; + color: var(--color-nier-text-dark); + text-shadow: 0 0 2px var(--color-nier-text-dark); + animation: fadeInReveal 2.5s ease-out forwards, pulse 4s 2.5s infinite; + transition: all 0.3s ease; } -@keyframes nier-subtle-pulse { - 0%, 100% { opacity: 1; } - 50% { opacity: 0.9; } +.nier-logo::before, +.nier-logo::after { + content: attr(data-text); + position: absolute; + top: 0; + width: 100%; + height: 100%; + opacity: 0; + animation: glitch 6s 3s infinite; + transition: text-shadow 0.3s ease; } -@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); } +.nier-logo::before { + left: 0; + text-shadow: 0 0 3px var(--color-nier-text-dark); } -@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); } +.nier-logo::after { + left: -2px; + text-shadow: -1px 0 1px var(--color-nier-text-dark); + animation-delay: 3s; + animation-duration: 5s; } -@keyframes nier-scan { - 0% { transform: translateY(-100%); } - 100% { transform: translateY(100%); } +/* ===================== EFFECTS ===================== */ + +.nier-scan-line { + position: absolute; + inset: 0; + background: linear-gradient( + to bottom, + transparent 0%, + color-mix(in srgb, var(--color-nier-text-dark) 5%, transparent) 50%, + transparent 100% + ); + animation: scanLine 3s linear infinite; + z-index: 2; + pointer-events: none; + transition: background 0.3s ease; } -@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); } +.nier-blocks { + position: absolute; + inset: 0; + z-index: 1; + pointer-events: none; +} + +.nier-blocks::before, +.nier-blocks::after { + content: ""; + position: absolute; + background: color-mix(in srgb, var(--color-nier-text-dark) 10%, transparent); + animation: floatingBlocks 10s linear infinite; + transition: background 0.3s ease; +} + +.nier-blocks::before { + top: 20%; + left: 10%; + width: 10px; + height: 6px; + animation-delay: 1s; +} + +.nier-blocks::after { + bottom: 40%; + right: 10%; + width: 15px; + height: 4px; + animation-delay: 3s; } diff --git a/src/app/components/header/header-text-animate-section/header-text-animate-section.component.ts b/src/app/components/header/header-text-animate-section/header-text-animate-section.component.ts index 334660f..9dd088a 100644 --- a/src/app/components/header/header-text-animate-section/header-text-animate-section.component.ts +++ b/src/app/components/header/header-text-animate-section/header-text-animate-section.component.ts @@ -8,6 +8,8 @@ import { signal, computed, ChangeDetectionStrategy, + DestroyRef, + inject, } from '@angular/core'; import { trigger, @@ -16,14 +18,15 @@ import { transition, animate, } from '@angular/animations'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { timer, Subject } from 'rxjs'; import { type TextItem } from '../../../shared/models/header.model'; -// 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 }, +// Simplified constants +const TYPING_PATTERNS = { + GLITCH: { delay: 150, probability: 0.03 }, + RAPID: { delay: 10, probability: 0.3 }, + SLOW: { delay: 60, probability: 0.1 }, } as const; @Component({ @@ -35,24 +38,11 @@ const TYPING_DELAYS = { changeDetection: ChangeDetectionStrategy.OnPush, animations: [ trigger('textChange', [ - state( - 'visible', - style({ - opacity: 1, - transform: 'translateY(0)', - }), - ), - state( - 'hidden', - style({ - opacity: 0, - transform: 'translateY(20px)', - }), - ), + state('visible', style({ opacity: 1, transform: 'translateY(0)' })), + state('hidden', style({ opacity: 0, transform: 'translateY(20px)' })), transition('visible => hidden', animate('300ms ease-out')), transition('hidden => visible', animate('300ms ease-in')), ]), - // Add fade-in animation for new text items trigger('slideIn', [ transition(':enter', [ style({ opacity: 0, transform: 'translateY(10px)' }), @@ -65,7 +55,9 @@ const TYPING_DELAYS = { ], }) export class HeaderTextAnimateSectionComponent implements OnInit, OnDestroy { - // Input signals with better defaults + private readonly destroyRef = inject(DestroyRef); + + // Input signals phrases = input([ 'Uploading guinea pig consciousness to the cloud...', 'Error: Sarcasm module overloaded. Rebooting...', @@ -83,32 +75,40 @@ export class HeaderTextAnimateSectionComponent implements OnInit, OnDestroy { typingSpeed = input(25); maxDisplayedTexts = input(4); - // State signals - private readonly displayedTexts = signal([]); - private readonly nextId = signal(0); - private readonly phraseIndex = signal(0); - private readonly isTypingInProgress = signal(false); + // State signals - simplified + private readonly displayedTexts = signal([]); + private nextId = 0; + private phraseIndex = 0; + private isTyping = false; - // Computed signals for better performance + // Computed signals readonly currentTexts = computed(() => this.displayedTexts()); readonly hasTexts = computed(() => this.displayedTexts().length > 0); - // Timeout management - private readonly activeTimeouts = new Set(); - private nextTextTimeout?: number; + // Single RAF handle for all animations + private animationFrame: number | null = null; + private nextTextTimer: number | null = null; + + // Performance optimization: reuse objects + private readonly typingState = { + textId: -1, + charIndex: 0, + lastUpdate: 0, + nextDelay: 0, + }; constructor() { - // Initialize first text when phrases are available + // Initialize when phrases are available effect(() => { const phrasesList = this.phrases(); if (phrasesList.length > 0 && this.displayedTexts().length === 0) { - this.scheduleNextText(0); // Start immediately + this.scheduleNextText(0); } }); } ngOnInit(): void { - // Component initialization handled in constructor effect + // Handled in constructor } ngOnDestroy(): void { @@ -116,94 +116,108 @@ export class HeaderTextAnimateSectionComponent implements OnInit, OnDestroy { } private cleanup(): void { - // Clear all timeouts - this.activeTimeouts.forEach((id) => window.clearTimeout(id)); - this.activeTimeouts.clear(); - - if (this.nextTextTimeout) { - window.clearTimeout(this.nextTextTimeout); - this.nextTextTimeout = undefined; + if (this.animationFrame) { + cancelAnimationFrame(this.animationFrame); + this.animationFrame = null; } + + if (this.nextTextTimer) { + clearTimeout(this.nextTextTimer); + this.nextTextTimer = null; + } + + this.isTyping = false; } private scheduleNextText(delay: number = this.interval()): void { - if (this.nextTextTimeout) { - window.clearTimeout(this.nextTextTimeout); + if (this.nextTextTimer) { + clearTimeout(this.nextTextTimer); } - this.nextTextTimeout = window.setTimeout(() => { + this.nextTextTimer = setTimeout(() => { this.startNextText(); + this.nextTextTimer = null; }, delay); } private startNextText(): void { - if (this.isTypingInProgress()) return; + if (this.isTyping) return; const phrasesList = this.phrases(); if (phrasesList.length === 0) return; - const currentIndex = this.phraseIndex(); - const nextText = phrasesList[currentIndex]; + const nextText = phrasesList[this.phraseIndex]; + this.phraseIndex = (this.phraseIndex + 1) % phrasesList.length; - // Create immutable text item + // Create new text item const newItem: TextItem = { - id: this.nextId(), + id: this.nextId++, text: nextText, displayed: '', isTyping: true, isComplete: false, }; - // Update state atomically - this.phraseIndex.update((idx) => (idx + 1) % phrasesList.length); - this.nextId.update((id) => id + 1); - this.isTypingInProgress.set(true); - - // Update displayed texts with proper cleanup + // Update displayed texts efficiently this.displayedTexts.update((texts) => { const maxTexts = this.maxDisplayedTexts(); - if (texts.length >= maxTexts) { - return [...texts.slice(texts.length - maxTexts + 1), newItem]; - } - return [...texts, newItem]; + const newTexts = + texts.length >= maxTexts + ? [...texts.slice(-maxTexts + 1), newItem] + : [...texts, newItem]; + return newTexts; }); - // Start typing animation - this.animateText(newItem.id); + // Start typing with RAF + this.isTyping = true; + this.typingState.textId = newItem.id; + this.typingState.charIndex = 0; + this.typingState.lastUpdate = performance.now(); + this.typingState.nextDelay = this.calculateTypingDelay(); + + this.animateWithRAF(); } - private animateText(textId: number): void { + private animateWithRAF(): void { + if (!this.isTyping) return; + + const now = performance.now(); + const elapsed = now - this.typingState.lastUpdate; + + if (elapsed >= this.typingState.nextDelay) { + this.updateTypingCharacter(); + this.typingState.lastUpdate = now; + this.typingState.nextDelay = this.calculateTypingDelay(); + } + + if (this.isTyping) { + this.animationFrame = requestAnimationFrame(() => this.animateWithRAF()); + } + } + + private updateTypingCharacter(): void { const texts = this.displayedTexts(); - const textIndex = texts.findIndex((t) => t.id === textId); + const textIndex = texts.findIndex((t) => t.id === this.typingState.textId); if (textIndex === -1) return; const textItem = texts[textIndex]; - const { text, displayed } = textItem; - const nextCharIndex = displayed.length; + const { text } = textItem; - if (nextCharIndex < text.length) { - // Update displayed text - const newDisplayed = text.substring(0, nextCharIndex + 1); + if (this.typingState.charIndex < text.length) { + // Update character + const newDisplayed = text.substring(0, this.typingState.charIndex + 1); + // Batch update for better performance this.displayedTexts.update((currentTexts) => { - const updatedTexts = [...currentTexts]; - updatedTexts[textIndex] = { - ...textItem, - displayed: newDisplayed, - }; - return updatedTexts; + const updated = [...currentTexts]; + updated[textIndex] = { ...textItem, displayed: newDisplayed }; + return updated; }); - // Schedule next character with dynamic timing - const delay = this.calculateTypingDelay(); - const timeoutId = window.setTimeout(() => { - this.animateText(textId); - }, delay); - - this.activeTimeouts.add(timeoutId); + this.typingState.charIndex++; } else { - // Text complete + // Complete text this.completeText(textIndex, textItem); } } @@ -212,59 +226,45 @@ export class HeaderTextAnimateSectionComponent implements OnInit, OnDestroy { const random = Math.random(); const baseSpeed = this.typingSpeed(); - // Apply different typing patterns based on probability - if (random < TYPING_DELAYS.GLITCH.probability) { - return ( - TYPING_DELAYS.GLITCH.min + - Math.random() * (TYPING_DELAYS.GLITCH.max - TYPING_DELAYS.GLITCH.min) - ); + // Simplified delay calculation + if (random < TYPING_PATTERNS.GLITCH.probability) { + return TYPING_PATTERNS.GLITCH.delay + Math.random() * 50; } - if (random < TYPING_DELAYS.RAPID.probability) { - return ( - TYPING_DELAYS.RAPID.min + - Math.random() * (TYPING_DELAYS.RAPID.max - TYPING_DELAYS.RAPID.min) - ); + if (random < TYPING_PATTERNS.RAPID.probability) { + return TYPING_PATTERNS.RAPID.delay + Math.random() * 10; } - 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) - ); + if (random < TYPING_PATTERNS.SLOW.probability) { + return TYPING_PATTERNS.SLOW.delay + Math.random() * 20; } - // 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 + // Normal typing with minimal variation + return baseSpeed + (Math.random() * 10 - 5); } private completeText(textIndex: number, textItem: TextItem): void { - // Mark text as complete + // Stop animation + if (this.animationFrame) { + cancelAnimationFrame(this.animationFrame); + this.animationFrame = null; + } + + // Mark complete this.displayedTexts.update((texts) => { - const updatedTexts = [...texts]; - updatedTexts[textIndex] = { + const updated = [...texts]; + updated[textIndex] = { ...textItem, isTyping: false, isComplete: true, }; - return updatedTexts; + return updated; }); - // Reset typing state and schedule next text - this.isTypingInProgress.set(false); + this.isTyping = false; this.scheduleNextText(); } - // Optimized tracking function + // Optimized tracking trackByTextId = (index: number, item: TextItem): number => item.id; } diff --git a/src/app/components/holo-video-container/holo-video-container.component.css b/src/app/components/holo-video-container/holo-video-container.component.css index 6818c4e..e903c31 100644 --- a/src/app/components/holo-video-container/holo-video-container.component.css +++ b/src/app/components/holo-video-container/holo-video-container.component.css @@ -1,24 +1,28 @@ +/* ===================== OPTIMIZED COMPONENT STYLES ===================== */ + +/* Host element */ :host { display: block; width: 100%; height: 100%; } -/* Custom spinner border width for Tailwind */ + +/* Custom utility classes */ .border-3 { border-width: 3px; } -/* Backdrop blur fallback for older browsers */ .backdrop-blur-sm { backdrop-filter: blur(4px); } -/* Custom shadow glow effect */ .shadow-cyan-500\/30 { box-shadow: 0 0 20px rgba(6, 182, 212, 0.3); } -/* Responsive container for smaller screens */ +/* ===================== RESPONSIVE DESIGN ===================== */ + +/* Tablet and below */ @media (max-width: 840px) { .w-\[800px\] { width: 100%; @@ -31,11 +35,10 @@ } } +/* Mobile */ @media (max-width: 768px) { .absolute.top-5.right-5 { - top: 0.625rem; - right: 0.625rem; - left: 0.625rem; + inset: 0.625rem 0.625rem auto 0.625rem; } .absolute.top-5.right-5 button { diff --git a/src/app/components/holo-video-container/holo-video-container.component.ts b/src/app/components/holo-video-container/holo-video-container.component.ts index f055993..d155652 100644 --- a/src/app/components/holo-video-container/holo-video-container.component.ts +++ b/src/app/components/holo-video-container/holo-video-container.component.ts @@ -58,14 +58,18 @@ export class HoloVideoContainerComponent implements OnInit, OnDestroy { // 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 + private readonly MAX_SCATTER_DISTANCE = 600; + private readonly SCATTER_STRENGTH = 45; + private readonly GLOW_INTENSITY = 4.0; // Optimization: Reuse arrays private tempPositions = new Float32Array(this.POINTS_COUNT * 3); private tempColors = new Float32Array(this.POINTS_COUNT * 3); + // MEMORY LEAK FIX: Add pattern animation cleanup + private patternAnimationId: number = 0; + private isDestroyed = false; + constructor() { effect(() => { if (this.material) { @@ -74,36 +78,86 @@ export class HoloVideoContainerComponent implements OnInit, OnDestroy { }); } - ngOnInit(): void { + async ngOnInit(): Promise { this.initThreeJS(); this.createPointCloud(); - this.setupVideo(); + await this.setupVideo(); this.setupEventListeners(); this.setupResizeObserver(); this.animate(); this.onWindowResize(); if (this.videoSrc) { - this.loadVideoFromSrc(this.videoSrc); + await this.loadVideoFromSrc(this.videoSrc); } } ngOnDestroy(): void { + // MEMORY LEAK FIX: Comprehensive cleanup + this.cleanup(); + } + + // MEMORY LEAK FIX: Proper cleanup method + private cleanup(): void { + this.isDestroyed = true; + + // Cancel all animations if (this.animationId) { cancelAnimationFrame(this.animationId); + this.animationId = 0; } + + if (this.patternAnimationId) { + cancelAnimationFrame(this.patternAnimationId); + this.patternAnimationId = 0; + } + + // Disconnect resize observer if (this.resizeObserver) { this.resizeObserver.disconnect(); } + // Stop and cleanup video + if (this.video) { + this.video.pause(); + if (this.video.srcObject) { + const stream = this.video.srcObject as MediaStream; + stream.getTracks().forEach((track) => track.stop()); + } + this.video.src = ''; + this.video.srcObject = null; + this.video.load(); + } + // Cleanup Three.js resources - this.geometry?.dispose(); - this.material?.dispose(); + if (this.geometry) { + this.geometry.dispose(); + } + if (this.material) { + this.material.dispose(); + } + this.glowLayers.forEach((layer) => { - layer.geometry.dispose(); - (layer.points.material as THREE.Material).dispose(); + if (layer.geometry) { + layer.geometry.dispose(); + } + if (layer.points.material) { + (layer.points.material as THREE.Material).dispose(); + } + if (this.scene) { + this.scene.remove(layer.points); + } }); - this.renderer?.dispose(); + this.glowLayers = []; + + if (this.points && this.scene) { + this.scene.remove(this.points); + } + + if (this.renderer) { + this.renderer.dispose(); + this.renderer.forceContextLoss(); + } } private initThreeJS(): void { @@ -243,7 +297,7 @@ export class HoloVideoContainerComponent implements OnInit, OnDestroy { } } - private setupVideo(): void { + private async setupVideo(): Promise { this.video = document.createElement('video'); this.video.width = this.GRID_WIDTH; this.video.height = this.GRID_HEIGHT; @@ -256,17 +310,24 @@ export class HoloVideoContainerComponent implements OnInit, OnDestroy { this.canvas2D.height = this.GRID_HEIGHT; this.ctx2D = this.canvas2D.getContext('2d')!; - this.createAnimatedPattern(); + await this.createAnimatedPattern(); } - private createAnimatedPattern(): void { + private async createAnimatedPattern(): Promise { const canvas = document.createElement('canvas'); canvas.width = this.GRID_WIDTH; canvas.height = this.GRID_HEIGHT; const ctx = canvas.getContext('2d')!; let time = 0; + + // MEMORY LEAK FIX: Store animation ID and check for component destruction const animate = () => { + // MEMORY LEAK FIX: Stop animation if component is destroyed + if (this.isDestroyed) { + return; + } + const imageData = ctx.createImageData(this.GRID_WIDTH, this.GRID_HEIGHT); const data = imageData.data; @@ -294,13 +355,22 @@ export class HoloVideoContainerComponent implements OnInit, OnDestroy { ctx.putImageData(imageData, 0, 0); time++; - requestAnimationFrame(animate); + + // MEMORY LEAK FIX: Store the animation ID for proper cleanup + if (!this.isDestroyed) { + this.patternAnimationId = requestAnimationFrame(animate); + } }; animate(); - const stream = canvas.captureStream(30); - this.video.srcObject = stream; - this.video.play(); + + try { + const stream = canvas.captureStream(30); + this.video.srcObject = stream; + await this.video.play(); + } catch (error) { + console.warn('Pattern video setup failed:', error); + } } private updatePointCloud(): void { @@ -336,6 +406,17 @@ export class HoloVideoContainerComponent implements OnInit, OnDestroy { const isGlitching = this.glitchIntensity > 0; + // MEMORY LEAK FIX: Add periodic cleanup of scatter arrays + if (this.glitchTime % 300 === 0) { + // Every 5 seconds at 60fps + for (let i = 0; i < this.POINTS_COUNT; i++) { + if (this.scatterDecayMap[i] < 0.01) { + this.scatterDecayMap[i] = 0; + this.scatterIntensityMap[i] = 0; + } + } + } + // Update scatter decay for all points for (let i = 0; i < this.POINTS_COUNT; i++) { if (this.scatterDecayMap[i] > 0) { @@ -604,19 +685,32 @@ export class HoloVideoContainerComponent implements OnInit, OnDestroy { } private animate(): void { + // MEMORY LEAK FIX: Check if component is destroyed + if (this.isDestroyed) return; + this.animationId = requestAnimationFrame(() => this.animate()); this.updatePointCloud(); this.renderer.render(this.scene, this.camera); } private async loadVideoFromSrc(src: string): Promise { - this.video.srcObject = null; - this.video.src = src; - this.video.loop = true; // Ensure loop is set after changing source - this.video.muted = true; // Also ensure muted is maintained - this.video.autoplay = true; // Ensure autoplay is maintained - this.video.load(); - await this.video.play(); + try { + // MEMORY LEAK FIX: Properly stop existing stream before loading new one + if (this.video.srcObject) { + const stream = this.video.srcObject as MediaStream; + stream.getTracks().forEach((track) => track.stop()); + this.video.srcObject = null; + } + + this.video.src = src; + this.video.loop = true; // Ensure loop is set after changing source + this.video.muted = true; // Also ensure muted is maintained + this.video.autoplay = true; // Ensure autoplay is maintained + this.video.load(); + await this.video.play(); + } catch (error) { + console.warn('Video load failed:', error); + } } get isLoadingValue(): boolean { diff --git a/src/app/components/infocard/infocard.component.css b/src/app/components/infocard/infocard.component.css deleted file mode 100644 index e69de29..0000000 diff --git a/src/app/components/infocard/infocard.component.html b/src/app/components/infocard/infocard.component.html deleted file mode 100644 index 6959224..0000000 --- a/src/app/components/infocard/infocard.component.html +++ /dev/null @@ -1 +0,0 @@ -

infocard works!

diff --git a/src/app/components/infocard/infocard.component.ts b/src/app/components/infocard/infocard.component.ts deleted file mode 100644 index c01012b..0000000 --- a/src/app/components/infocard/infocard.component.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Component } from '@angular/core'; - -@Component({ - selector: 'app-infocard', - imports: [], - templateUrl: './infocard.component.html', - styleUrl: './infocard.component.css' -}) -export class InfocardComponent { - -} diff --git a/src/app/components/long-button/long-button.component.css b/src/app/components/long-button/long-button.component.css deleted file mode 100644 index e69de29..0000000 diff --git a/src/app/components/long-button/long-button.component.html b/src/app/components/long-button/long-button.component.html deleted file mode 100644 index 891b15d..0000000 --- a/src/app/components/long-button/long-button.component.html +++ /dev/null @@ -1 +0,0 @@ -

long-button works!

diff --git a/src/app/components/long-button/long-button.component.ts b/src/app/components/long-button/long-button.component.ts deleted file mode 100644 index 9579960..0000000 --- a/src/app/components/long-button/long-button.component.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Component } from '@angular/core'; - -@Component({ - selector: 'app-long-button', - imports: [], - templateUrl: './long-button.component.html', - styleUrl: './long-button.component.css' -}) -export class LongButtonComponent { - -} diff --git a/src/app/pages/about/about.component.css b/src/app/pages/about/about.component.css index 4bf9d26..1ca6353 100644 --- a/src/app/pages/about/about.component.css +++ b/src/app/pages/about/about.component.css @@ -1,551 +1,248 @@ -/* ===================== NEURAL PROFILE SECTION ANIMATIONS ===================== */ +/* ===================== NEURAL PROFILE ANIMATIONS ===================== */ -/* Section Container Animation */ +/* Core keyframes - reused across components */ +@keyframes slideInLeft { + from { + opacity: 0; + transform: translate3d(-30px, 0, 0); + } + to { + opacity: 1; + transform: translate3d(0, 0, 0); + } +} + +@keyframes slideInRight { + from { + opacity: 0; + transform: translate3d(30px, 0, 0); + } + to { + opacity: 1; + transform: translate3d(0, 0, 0); + } +} + +@keyframes slideInUp { + from { + opacity: 0; + transform: translate3d(0, 30px, 0); + } + to { + opacity: 1; + transform: translate3d(0, 0, 0); + } +} + +@keyframes scaleIn { + from { + opacity: 0; + transform: scale(0.95); + } + to { + opacity: 1; + transform: scale(1); + } +} + +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +/* ===================== NEURAL SECTION ELEMENTS ===================== */ + +/* Section container - no animation */ .neural-section { - opacity: 0; - transform: translateY(30px); - animation: neuralSectionMaterialize 1.2s ease-out 0.1s forwards; + opacity: 1; } -@keyframes neuralSectionMaterialize { - 0% { - opacity: 0; - transform: translateY(30px); - filter: blur(2px); - } - 60% { - opacity: 0.8; - transform: translateY(-5px); - filter: blur(0.5px); - } - 100% { - opacity: 1; - transform: translateY(0); - filter: blur(0px); - } -} - -/* Header Section Animation */ +/* Header - no animation */ .neural-header { - opacity: 0; - transform: translateY(-20px); - animation: neuralHeaderSlideDown 1s ease-out 0.3s forwards; + opacity: 1; } -@keyframes neuralHeaderSlideDown { - 0% { - opacity: 0; - transform: translateY(-20px); - filter: blur(1px); - } - 70% { - opacity: 0.9; - transform: translateY(3px); - filter: blur(0.3px); - } - 100% { - opacity: 1; - transform: translateY(0); - filter: blur(0px); - } -} - -/* Divider Line Animation */ +/* Divider - static */ .neural-divider { - opacity: 0; - transform: scaleX(0); - transform-origin: left; - animation: neuralDividerExpand 0.8s ease-out 0.8s forwards; + opacity: 0.5; } -@keyframes neuralDividerExpand { - 0% { - opacity: 0; - transform: scaleX(0); - } - 50% { - opacity: 0.3; - transform: scaleX(0.7); - } - 100% { - opacity: 0.5; - transform: scaleX(1); - } -} - -/* Main Content Grid Animation */ +/* Main content grid */ .neural-content-grid { - opacity: 0; - transform: translateY(40px); - animation: neuralContentReveal 1.2s ease-out 0.5s forwards; + opacity: 0; + animation: slideInUp 0.8s ease-out 0.3s forwards; } -@keyframes neuralContentReveal { - 0% { - opacity: 0; - transform: translateY(40px); - } - 100% { - opacity: 1; - transform: translateY(0); - } -} - -/* Left Panel - Neural Profile Tree Animation */ +/* Left panel */ .neural-left-panel { - opacity: 0; - transform: translateX(-40px) scale(0.95); - animation: neuralLeftPanelSlideIn 1.2s ease-out 0.7s forwards; + opacity: 0; + animation: slideInLeft 0.8s ease-out 0.5s forwards; } -@keyframes neuralLeftPanelSlideIn { - 0% { - opacity: 0; - transform: translateX(-40px) scale(0.95); - border-color: transparent; - background-color: rgba(41, 41, 37, 0); - } - 40% { - opacity: 0.6; - transform: translateX(-10px) scale(0.98); - border-color: var(--color-nier-border); - } - 80% { - opacity: 0.9; - transform: translateX(2px) scale(1.01); - background-color: var(--color-nier-bg); - } - 100% { - opacity: 1; - transform: translateX(0) scale(1); - border-color: var(--color-nier-border); - background-color: var(--color-nier-bg); - } -} - -/* Neural Profile Tree Content */ +/* Tree content */ .neural-tree-container { - opacity: 0; - transform: translateY(20px); - animation: neuralTreeContentFadeUp 0.8s ease-out 1.2s forwards; + opacity: 0; + animation: slideInUp 0.6s ease-out 0.8s forwards; } -@keyframes neuralTreeContentFadeUp { - 0% { - opacity: 0; - transform: translateY(20px); - } - 100% { - opacity: 1; - transform: translateY(0); - } -} - -/* Right Panel Container Animation */ +/* Right panel */ .neural-right-panel { - opacity: 0; - transform: translateX(40px); - animation: neuralRightPanelSlideIn 1s ease-out 0.9s forwards; + opacity: 0; + animation: slideInRight 0.8s ease-out 0.6s forwards; } -@keyframes neuralRightPanelSlideIn { - 0% { - opacity: 0; - transform: translateX(40px); - } - 100% { - opacity: 1; - transform: translateX(0); - } -} - -/* Video Display Panel Animation */ +/* Video panel */ .neural-video-panel { - opacity: 0; - transform: scale(0.95) translateY(20px); - animation: neuralVideoPanelMaterialize 1.2s ease-out 1.1s forwards; + opacity: 0; + animation: scaleIn 0.8s ease-out 0.7s forwards; } -@keyframes neuralVideoPanelMaterialize { - 0% { - opacity: 0; - transform: scale(0.95) translateY(20px); - border-color: transparent; - background-color: rgba(41, 41, 37, 0); - } - 30% { - opacity: 0.4; - transform: scale(0.98) translateY(10px); - border-color: var(--color-nier-accent); - } - 70% { - opacity: 0.8; - transform: scale(1.01) translateY(-2px); - background-color: var(--color-nier-dark); - } - 100% { - opacity: 1; - transform: scale(1) translateY(0); - border-color: var(--color-nier-accent); - background-color: var(--color-nier-dark); - } -} - -/* Video Container Inner Animation */ +/* Video inner container */ .neural-video-inner { - opacity: 0; - transform: scale(0.9); - animation: neuralVideoInnerExpand 0.8s ease-out 1.5s forwards; + opacity: 0; + animation: scaleIn 0.6s ease-out 1.0s forwards; } -@keyframes neuralVideoInnerExpand { - 0% { - opacity: 0; - transform: scale(0.9); - border-color: transparent; - background-color: rgba(0, 0, 0, 0); - } - 60% { - opacity: 0.7; - transform: scale(1.02); - border-color: rgba(255, 201, 102, 0.3); - } - 100% { - opacity: 1; - transform: scale(1); - border-color: rgba(255, 201, 102, 0.5); - background-color: rgba(41, 41, 37, 0.1); - } -} - -/* Holo Video Component Animation */ +/* Holo video */ .neural-holo-video { - opacity: 0; - animation: neuralHoloVideoFadeIn 1s ease-out 1.8s forwards; + opacity: 0; + animation: fadeIn 0.8s ease-out 1.2s forwards; } -@keyframes neuralHoloVideoFadeIn { - 0% { - opacity: 0; - filter: blur(2px) brightness(0.5); - } - 50% { - opacity: 0.7; - filter: blur(1px) brightness(0.8); - } - 100% { - opacity: 1; - filter: blur(0px) brightness(1); - } -} - -/* Status Panel Animation */ +/* Status panel */ .neural-status-panel { - opacity: 0; - transform: translateY(30px) scale(0.95); - animation: neuralStatusPanelRise 1s ease-out 1.3s forwards; + opacity: 0; + animation: slideInUp 0.8s ease-out 0.9s forwards; } -@keyframes neuralStatusPanelRise { - 0% { - opacity: 0; - transform: translateY(30px) scale(0.95); - border-color: transparent; - background-color: rgba(41, 41, 37, 0); - } - 40% { - opacity: 0.6; - transform: translateY(10px) scale(0.98); - border-color: var(--color-nier-border); - } - 80% { - opacity: 0.9; - transform: translateY(-2px) scale(1.01); - background-color: var(--color-nier-mid); - } - 100% { - opacity: 1; - transform: translateY(0) scale(1); - border-color: var(--color-nier-border); - background-color: var(--color-nier-mid); - } -} - -/* Status Grid Content Animation */ +/* Status grid */ .neural-status-grid { - opacity: 0; - transform: translateY(15px); - animation: neuralStatusGridFadeUp 0.6s ease-out 1.7s forwards; + opacity: 0; + animation: slideInUp 0.5s ease-out 1.2s forwards; } -@keyframes neuralStatusGridFadeUp { - 0% { - opacity: 0; - transform: translateY(15px); - } - 100% { - opacity: 1; - transform: translateY(0); - } -} - -/* Status Items Staggered Animation */ +/* Status items */ .neural-status-item { - opacity: 0; - transform: translateX(-15px); - animation: neuralStatusItemSlideIn 0.5s ease-out forwards; + opacity: 0; + animation: slideInLeft 0.4s ease-out forwards; } -.neural-status-item:nth-child(1) { animation-delay: 1.9s; } -.neural-status-item:nth-child(2) { animation-delay: 2.1s; } +.neural-status-item:nth-child(1) { animation-delay: 1.4s; } +.neural-status-item:nth-child(2) { animation-delay: 1.5s; } -@keyframes neuralStatusItemSlideIn { - 0% { - opacity: 0; - transform: translateX(-15px); - } - 100% { - opacity: 1; - transform: translateX(0); - } -} - -/* Status Labels Animation */ +/* Status labels */ .neural-status-label { - opacity: 0; - transform: translateY(-5px); - animation: neuralStatusLabelFadeDown 0.4s ease-out forwards; + opacity: 0; + animation: fadeIn 0.3s ease-out forwards; } -.neural-status-item:nth-child(1) .neural-status-label { animation-delay: 2.0s; } -.neural-status-item:nth-child(2) .neural-status-label { animation-delay: 2.2s; } +.neural-status-item:nth-child(1) .neural-status-label { animation-delay: 1.5s; } +.neural-status-item:nth-child(2) .neural-status-label { animation-delay: 1.6s; } -@keyframes neuralStatusLabelFadeDown { - 0% { - opacity: 0; - transform: translateY(-5px); - } - 100% { - opacity: 0.6; - transform: translateY(0); - } -} - -/* Status Values Animation */ +/* Status values */ .neural-status-value { - opacity: 0; - transform: scale(0.9); - animation: neuralStatusValuePop 0.4s ease-out forwards; + opacity: 0; + animation: scaleIn 0.3s ease-out forwards; } -.neural-status-item:nth-child(1) .neural-status-value { animation-delay: 2.1s; } -.neural-status-item:nth-child(2) .neural-status-value { animation-delay: 2.3s; } +.neural-status-item:nth-child(1) .neural-status-value { animation-delay: 1.6s; } +.neural-status-item:nth-child(2) .neural-status-value { animation-delay: 1.7s; } -@keyframes neuralStatusValuePop { - 0% { - opacity: 0; - transform: scale(0.9); - } - 50% { - opacity: 0.8; - transform: scale(1.05); - } - 100% { - opacity: 1; - transform: scale(1); - } -} - -/* Pulse Animation for Status Indicator */ +/* Pulse dot */ .neural-pulse-dot { - opacity: 0; - animation: neuralPulseDotAppear 0.6s ease-out forwards; + opacity: 0; + animation: scaleIn 0.4s ease-out forwards; } -.neural-status-item:nth-child(1) .neural-pulse-dot { animation-delay: 2.2s; } +.neural-status-item:nth-child(1) .neural-pulse-dot { animation-delay: 1.7s; } -@keyframes neuralPulseDotAppear { - 0% { - opacity: 0; - transform: scale(0.5); - } - 50% { - opacity: 0.8; - transform: scale(1.2); - } - 100% { - opacity: 1; - transform: scale(1); - } -} +/* ===================== BACKGROUND ELEMENTS ===================== */ -/* Background Checkered Pattern Enhancement */ +/* Checkered background - static */ .neural-checkered-bg { - position: relative; + position: relative; } .neural-checkered-bg::before { - content: ''; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - opacity: 0; - background-image: - linear-gradient(45deg, rgba(255, 201, 102, 0.01) 25%, transparent 25%), - linear-gradient(-45deg, rgba(255, 201, 102, 0.01) 25%, transparent 25%), - linear-gradient(45deg, transparent 75%, rgba(255, 201, 102, 0.01) 75%), - linear-gradient(-45deg, transparent 75%, rgba(255, 201, 102, 0.01) 75%); - background-size: 30px 30px; - background-position: 0 0, 0 15px, 15px -15px, -15px 0px; - animation: neuralBgPatternFadeIn 3s ease-out 0.2s forwards; - pointer-events: none; - z-index: 0; + content: ''; + position: absolute; + inset: 0; + opacity: 1; + background-image: + linear-gradient(45deg, rgba(255, 201, 102, 0.01) 25%, transparent 25%), + linear-gradient(-45deg, rgba(255, 201, 102, 0.01) 25%, transparent 25%), + linear-gradient(45deg, transparent 75%, rgba(255, 201, 102, 0.01) 75%), + linear-gradient(-45deg, transparent 75%, rgba(255, 201, 102, 0.01) 75%); + background-size: 30px 30px; + background-position: 0 0, 0 15px, 15px -15px, -15px 0px; + pointer-events: none; + z-index: 0; } -@keyframes neuralBgPatternFadeIn { - 0% { - opacity: 0; - } - 100% { - opacity: 1; - } -} - -/* Scanning Line Effect for Neural Section */ +/* Scan line - hidden */ .neural-scan-line { - position: absolute; - top: 0; - left: -100%; - width: 100%; - height: 2px; - background: linear-gradient(90deg, - transparent, - rgba(255, 201, 102, 0.6), - transparent - ); - animation: neuralScanLine 4s ease-in-out 1s forwards; - z-index: 1; -} - -@keyframes neuralScanLine { - 0% { - left: -100%; - opacity: 0; - } - 20% { - opacity: 1; - } - 80% { - opacity: 1; - } - 100% { - left: 100%; - opacity: 0; - } + display: none; } /* ===================== RESPONSIVE DESIGN ===================== */ @media (max-width: 1024px) { - /* Faster animations on tablets */ - .neural-section { - animation-duration: 1s; - } - - .neural-left-panel, - .neural-video-panel { - animation-duration: 1s; - } - - .neural-right-panel { - animation-duration: 0.8s; - animation-delay: 0.7s; - } + .neural-content-grid, + .neural-left-panel, + .neural-video-panel, + .neural-right-panel { + animation-duration: 0.6s; + } } @media (max-width: 768px) { - /* Much faster animations on mobile */ - .neural-section { - animation-duration: 0.8s; - animation-delay: 0.05s; - } + .neural-content-grid { animation-delay: 0.2s; } + .neural-left-panel { animation-delay: 0.3s; } + .neural-right-panel { animation-delay: 0.4s; } + .neural-video-panel { animation-delay: 0.5s; } + .neural-status-panel { animation-delay: 0.6s; } - .neural-header { - animation-duration: 0.6s; - animation-delay: 0.2s; - } + .neural-status-item:nth-child(1) { animation-delay: 0.8s; } + .neural-status-item:nth-child(2) { animation-delay: 0.9s; } - .neural-divider { - animation-duration: 0.5s; - animation-delay: 0.4s; - } + .neural-status-item:nth-child(1) .neural-status-label { animation-delay: 0.9s; } + .neural-status-item:nth-child(2) .neural-status-label { animation-delay: 1.0s; } - .neural-content-grid { - animation-duration: 0.8s; - animation-delay: 0.3s; - } + .neural-status-item:nth-child(1) .neural-status-value { animation-delay: 1.0s; } + .neural-status-item:nth-child(2) .neural-status-value { animation-delay: 1.1s; } - .neural-left-panel { - animation-duration: 0.8s; - animation-delay: 0.4s; - } + .neural-status-item:nth-child(1) .neural-pulse-dot { animation-delay: 1.1s; } - .neural-right-panel { - animation-duration: 0.6s; - animation-delay: 0.5s; - } + /* Faster animations on mobile */ + .neural-content-grid, + .neural-left-panel, + .neural-right-panel, + .neural-video-panel, + .neural-status-panel { + animation-duration: 0.5s; + } - .neural-video-panel { - animation-duration: 0.8s; - animation-delay: 0.6s; - } + .neural-tree-container, + .neural-video-inner, + .neural-holo-video, + .neural-status-grid { + animation-duration: 0.4s; + } - .neural-status-panel { - animation-duration: 0.6s; - animation-delay: 0.7s; - } - - .neural-status-item:nth-child(1) { animation-delay: 0.9s; } - .neural-status-item:nth-child(2) { animation-delay: 1.0s; } - - .neural-status-item:nth-child(1) .neural-status-label { animation-delay: 1.0s; } - .neural-status-item:nth-child(2) .neural-status-label { animation-delay: 1.1s; } - - .neural-status-item:nth-child(1) .neural-status-value { animation-delay: 1.1s; } - .neural-status-item:nth-child(2) .neural-status-value { animation-delay: 1.2s; } - - .neural-status-item:nth-child(1) .neural-pulse-dot { animation-delay: 1.2s; } + .neural-status-item, + .neural-status-label, + .neural-status-value, + .neural-pulse-dot { + animation-duration: 0.3s; + } } /* ===================== ACCESSIBILITY ===================== */ -/* Respect reduced motion preferences */ @media (prefers-reduced-motion: reduce) { - .neural-section, - .neural-header, - .neural-divider, - .neural-content-grid, - .neural-left-panel, - .neural-tree-container, - .neural-right-panel, - .neural-video-panel, - .neural-video-inner, - .neural-holo-video, - .neural-status-panel, - .neural-status-grid, - .neural-status-item, - .neural-status-label, - .neural-status-value, - .neural-pulse-dot { - animation: none !important; - opacity: 1 !important; - transform: none !important; - } - - .neural-checkered-bg::before, - .neural-scan-line { - display: none !important; - } + *[class*="neural-"] { + animation: none !important; + opacity: 1 !important; + transform: none !important; + } } diff --git a/src/app/pages/about/neural-profile-tree/neural-profile-tree.component.css b/src/app/pages/about/neural-profile-tree/neural-profile-tree.component.css index 52f961f..b110685 100644 --- a/src/app/pages/about/neural-profile-tree/neural-profile-tree.component.css +++ b/src/app/pages/about/neural-profile-tree/neural-profile-tree.component.css @@ -1,4 +1,20 @@ -/* NieR-style hover effects for tree nodes */ +/* ===================== OPTIMIZED TREE & CHIP SYSTEM ===================== */ + +/* Core keyframes */ +@keyframes textGlitch { + 0%, 100% { transform: translateX(0); clip-path: inset(0); } + 10% { transform: translateX(-2px); clip-path: inset(0 0 40% 0); } + 20% { transform: translateX(1px); clip-path: inset(40% 0 0 0); } + 30% { transform: translateX(-1px); clip-path: inset(20% 0 20% 0); } +} + +@keyframes scanLine { + from { opacity: 0.5; left: -100%; } + to { opacity: 0; left: 100%; } +} + +/* ===================== TREE NODES ===================== */ + .tree-node-item { position: relative; overflow: hidden; @@ -6,7 +22,6 @@ border-bottom: 1px solid transparent; } -/* Underline effect */ .tree-node-item::before { content: ""; position: absolute; @@ -16,35 +31,11 @@ height: 1px; background-color: var(--color-nier-text-dark); transition: width 0.3s ease; - z-index: 0; } -/* Text color change on hover */ -.tree-node-item:hover { - color: var(--color-nier-text-dark); - border-bottom-color: var(--color-nier-text-dark); -} - -/* Underline animation on hover */ -.tree-node-item:hover::before { - width: 100%; -} - -/* Glitch effect on hover - target the text span directly */ -.tree-node-item:hover span.text-nier-dark { - animation: nier-text-glitch 0.6s ease; -} - -/* Alternative - target any span with text */ -.tree-node-item:hover span:last-of-type { - animation: nier-text-glitch 0.6s ease; -} - -/* Scan line for tree nodes */ .tree-node-item .scan-line { position: absolute; - top: 0; - left: -100%; + inset: 0 0 auto -100%; width: 100%; height: 1px; background-color: var(--color-nier-text-dark); @@ -53,16 +44,30 @@ pointer-events: none; } -.tree-node-item:hover .scan-line { - animation: nier-button-scan 0.3s ease forwards; +/* Tree node hover effects */ +.tree-node-item:hover { + color: var(--color-nier-text-dark); + border-bottom-color: var(--color-nier-text-dark); +} + +.tree-node-item:hover::before { + width: 100%; +} + +.tree-node-item:hover span:last-of-type { + animation: textGlitch 0.6s ease; +} + +.tree-node-item:hover .scan-line { + animation: scanLine 0.3s ease forwards; } -/* Active/click state for tree nodes */ .tree-node-item:active { transform: scale(0.98); } -/* NieR-style Chip Container System - NO INTERACTIONS */ +/* ===================== CHIP SYSTEM ===================== */ + .chip-container { position: relative; } @@ -70,33 +75,19 @@ .chip-parent { position: relative; padding: 16px; - border-radius: 0; - border: 1px solid rgba(0, 0, 0, 0.15); /* Very thin border */ - box-shadow: none; + border: 1px solid rgba(0, 0, 0, 0.15); overflow: hidden; - transition: transform 0.2s ease; /* Allow parent scaling */ + transition: transform 0.2s ease; } -/* Parent Chip Colors - Complete NieR Earth Palette */ -.tools-parent { - background: linear-gradient(135deg, #a67c63 0%, #8b6b52 100%); -} +/* Parent chip gradients */ +.tools-parent { background: linear-gradient(135deg, #a67c63, #8b6b52); } +.frontend-parent { background: linear-gradient(135deg, #b8916f, #a67c63); } +.backend-parent { background: linear-gradient(135deg, #c9b896, #b8916f); } +.database-parent { background: linear-gradient(135deg, #e6d7b8, #d4c4a0); } -.frontend-parent { - background: linear-gradient(135deg, #b8916f 0%, #a67c63 100%); -} - -.backend-parent { - background: linear-gradient(135deg, #c9b896 0%, #b8916f 100%); -} - -.database-parent { - background: linear-gradient(135deg, #e6d7b8 0%, #d4c4a0 100%); -} - -/* Locked Parent Category */ .locked-parent { - background: linear-gradient(135deg, #606060 0%, #4a4a4a 100%); + background: linear-gradient(135deg, #606060, #4a4a4a); border: none; cursor: not-allowed; height: 100%; @@ -111,14 +102,12 @@ min-height: 0; } -/* Chip Cells Column Layout */ .chip-cells-column { display: flex; flex-direction: column; gap: 4px; } -/* Locked Content Styling */ .locked-content { display: flex; align-items: center; @@ -136,58 +125,46 @@ filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.3)); } -/* Individual Chip Cells - Keep original grayish overlay */ +/* Chip cells */ .chip-cell { position: relative; width: 100%; height: 16px; - border-radius: 0; - border: 1px solid rgba(0, 0, 0, 0.2); /* Keep original border */ - background: rgba(0, 0, 0, 0.1); /* Keep original grayish overlay */ + border: 1px solid rgba(0, 0, 0, 0.2); + background: rgba(0, 0, 0, 0.1); cursor: pointer; overflow: hidden; - box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.2); /* Keep original shadow */ - transition: transform 0.2s ease; /* Allow scale animation */ + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.2); + transition: transform 0.2s ease; } .chip-cell::before { content: ''; position: absolute; - top: 1px; - left: 1px; - right: 1px; + inset: 1px 1px auto 1px; height: 4px; - background: rgba(255, 255, 255, 0.1); /* Keep original highlight */ - border-radius: 0; + background: rgba(255, 255, 255, 0.1); } -/* Scale animation when tree node is hovered OR selected */ +/* Active states */ .chip-cell.cell-hovered, .chip-cell.cell-selected { transform: scale(1.05) !important; - background: rgba(0, 0, 0, 0.1) !important; /* Keep original grayish color */ - border: 1px solid rgba(0, 0, 0, 0.2) !important; /* Keep original border */ - box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.2) !important; /* Keep original shadow */ } -/* Parent categories scale when children are hovered OR selected */ .chip-container.chip-hovered .chip-parent, .chip-container.chip-selected .chip-parent { transform: scale(1.02) !important; } -/* NO direct hover effects on cells */ +/* Prevent direct chip interactions */ .chip-cell:hover, .chip-cell:focus, .chip-cell:active { - background: rgba(0, 0, 0, 0.1) !important; /* Keep original grayish */ - border: 1px solid rgba(0, 0, 0, 0.2) !important; /* Keep original border */ - box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.2) !important; /* Keep original shadow */ + background: rgba(0, 0, 0, 0.1) !important; + border: 1px solid rgba(0, 0, 0, 0.2) !important; + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.2) !important; transform: none !important; - filter: none !important; - opacity: 1 !important; - animation: none !important; - transition: none !important; } .chip-cell:hover::before, @@ -195,60 +172,12 @@ .chip-cell:active::before, .chip-cell.cell-hovered::before, .chip-cell.cell-selected::before { - background: rgba(255, 255, 255, 0.1) !important; /* Keep original highlight */ + background: rgba(255, 255, 255, 0.1) !important; height: 4px !important; - box-shadow: none !important; } -/* NO CONTAINER EFFECTS */ -.chip-normal, -.chip-selected, -.chip-hovered { - /* No effects */ -} +/* ===================== RESPONSIVE DESIGN ===================== */ -/* Text glitch effect for tree nodes only */ -@keyframes nier-text-glitch { - 0%, 100% { - transform: translateX(0); - clip-path: inset(0 0 0 0); - } - 10% { - transform: translateX(-2px); - clip-path: inset(0 0 40% 0); - } - 15% { - transform: translateX(0); - } - 20% { - transform: translateX(1px); - clip-path: inset(40% 0 0 0); - } - 25% { - transform: translateX(0); - } - 30% { - transform: translateX(-1px); - clip-path: inset(20% 0 20% 0); - } - 35% { - transform: translateX(0); - } -} - -/* Scan line animation for tree nodes only */ -@keyframes nier-button-scan { - 0% { - opacity: 0.5; - left: -100%; - } - 100% { - opacity: 0; - left: 100%; - } -} - -/* Responsive Adjustments */ @media (max-width: 768px) { .chip-cell { height: 12px; @@ -263,14 +192,14 @@ } } -/* Touch device optimization for tree nodes only */ +/* Touch devices */ @media (hover: none) { .tree-node-item:active::before { width: 100%; } .tree-node-item:active .scan-line { - animation: nier-button-scan 0.3s ease forwards; + animation: scanLine 0.3s ease forwards; } .tree-node-item:active { diff --git a/src/app/pages/contact/contact-content/contact-content.component.css b/src/app/pages/contact/contact-content/contact-content.component.css new file mode 100644 index 0000000..86c5833 --- /dev/null +++ b/src/app/pages/contact/contact-content/contact-content.component.css @@ -0,0 +1,248 @@ +/* ===================== CONTACT PAGE ANIMATIONS ===================== */ + +/* Core keyframes - reused across components */ +@keyframes slideInLeft { + from { + opacity: 0; + transform: translate3d(-30px, 0, 0); + } + to { + opacity: 1; + transform: translate3d(0, 0, 0); + } +} + +@keyframes slideInUp { + from { + opacity: 0; + transform: translate3d(0, 30px, 0); + } + to { + opacity: 1; + transform: translate3d(0, 0, 0); + } +} + +@keyframes scaleIn { + from { + opacity: 0; + transform: scale(0.95); + } + to { + opacity: 1; + transform: scale(1); + } +} + +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +@keyframes scanLine { + from { transform: translate3d(-100%, 0, 0); opacity: 0; } + 50% { opacity: 1; } + to { transform: translate3d(100%, 0, 0); opacity: 0; } +} + +/* ===================== CONTACT ELEMENTS ===================== */ + +/* Content container */ +.contact-content-container { + opacity: 0; + animation: slideInUp 0.8s ease-out 0.4s forwards; +} + +/* Availability status */ +.availability-status { + opacity: 0; + animation: slideInLeft 0.6s ease-out 0.6s forwards; +} + +/* Status dot */ +.status-dot { + opacity: 0; + animation: scaleIn 0.4s ease-out 0.8s forwards; +} + +/* Contact grid */ +.contact-grid { + opacity: 0; + animation: slideInUp 0.8s ease-out 0.8s forwards; +} + +/* Contact items */ +.contact-item { + opacity: 0; + animation: scaleIn 0.8s ease-out forwards; + position: relative; +} + +.contact-item:nth-child(1) { animation-delay: 1.0s; } +.contact-item:nth-child(2) { animation-delay: 1.1s; } +.contact-item:nth-child(3) { animation-delay: 1.2s; } + +/* Scan line effect */ +.contact-item::before { + content: ''; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 2px; + background: linear-gradient(90deg, transparent, var(--color-nier-accent), transparent); + animation: scanLine 1.5s ease-out forwards; + z-index: 20; +} + +.contact-item:nth-child(1)::before { animation-delay: 1.2s; } +.contact-item:nth-child(2)::before { animation-delay: 1.3s; } +.contact-item:nth-child(3)::before { animation-delay: 1.4s; } + +/* Contact icons */ +.contact-icon { + opacity: 0; + animation: scaleIn 0.5s ease-out forwards; +} + +.contact-item:nth-child(1) .contact-icon { animation-delay: 1.3s; } +.contact-item:nth-child(2) .contact-icon { animation-delay: 1.4s; } +.contact-item:nth-child(3) .contact-icon { animation-delay: 1.5s; } + +/* Contact labels and values */ +.contact-label, +.contact-value { + opacity: 0; + animation: slideInLeft 0.4s ease-out forwards; +} + +.contact-item:nth-child(1) .contact-label { animation-delay: 1.4s; } +.contact-item:nth-child(2) .contact-label { animation-delay: 1.5s; } +.contact-item:nth-child(3) .contact-label { animation-delay: 1.6s; } + +.contact-item:nth-child(1) .contact-value { animation-delay: 1.5s; } +.contact-item:nth-child(2) .contact-value { animation-delay: 1.6s; } +.contact-item:nth-child(3) .contact-value { animation-delay: 1.7s; } + +/* Info panels container */ +.info-panels-container { + opacity: 0; + animation: slideInUp 0.8s ease-out 1.4s forwards; +} + +/* Info panels */ +.info-panel { + opacity: 0; + animation: scaleIn 0.6s ease-out forwards; +} + +.info-panel:nth-child(1) { animation-delay: 1.6s; } +.info-panel:nth-child(2) { animation-delay: 1.7s; } + +/* Info panel titles and content */ +.info-panel-title { + opacity: 0; + animation: slideInLeft 0.4s ease-out forwards; +} + +.info-panel-content { + opacity: 0; + animation: fadeIn 0.4s ease-out forwards; +} + +.info-panel:nth-child(1) .info-panel-title { animation-delay: 1.8s; } +.info-panel:nth-child(2) .info-panel-title { animation-delay: 1.9s; } + +.info-panel:nth-child(1) .info-panel-content { animation-delay: 1.9s; } +.info-panel:nth-child(2) .info-panel-content { animation-delay: 2.0s; } + +/* ===================== HOVER EFFECTS ===================== */ + +.contact-item:hover { + transform: translateY(-3px); + box-shadow: + 0 8px 25px rgba(0, 0, 0, 0.2), + 0 0 20px rgba(90, 90, 80, 0.1); + transition: all 0.3s ease; +} + +.contact-item:hover .contact-icon { + transform: scale(1.05); + box-shadow: 0 0 15px rgba(90, 90, 80, 0.3); + transition: all 0.3s ease; +} + +/* ===================== BACKGROUND PATTERN ===================== */ + +.contact-content-container::before { + content: ''; + position: absolute; + inset: 0; + opacity: 1; + background-image: + linear-gradient(45deg, rgba(90, 90, 80, 0.02) 25%, transparent 25%), + linear-gradient(-45deg, rgba(90, 90, 80, 0.02) 25%, transparent 25%), + linear-gradient(45deg, transparent 75%, rgba(90, 90, 80, 0.02) 75%), + linear-gradient(-45deg, transparent 75%, rgba(90, 90, 80, 0.02) 75%); + background-size: 20px 20px; + background-position: 0 0, 0 10px, 10px -10px, -10px 0px; + pointer-events: none; + z-index: -1; +} + +/* ===================== RESPONSIVE DESIGN ===================== */ + +@media (max-width: 768px) { + .contact-content-container { animation-delay: 0.3s; } + .availability-status { animation-delay: 0.4s; } + .contact-grid { animation-delay: 0.6s; } + + .contact-item:nth-child(1) { animation-delay: 0.8s; } + .contact-item:nth-child(2) { animation-delay: 0.9s; } + \contact-item:nth-child(3) { animation-delay: 1.0s; } + + .info-panels-container { animation-delay: 1.2s; } + .info-panel:nth-child(1) { animation-delay: 1.3s; } + .info-panel:nth-child(2) { animation-delay: 1.4s; } + + /* Faster animations on mobile */ + .contact-content-container, + .availability-status, + .contact-grid, + .contact-item, + .info-panels-container, + .info-panel { + animation-duration: 0.6s; + } + + .status-dot, + .contact-icon, + .contact-label, + .contact-value, + .info-panel-title, + .info-panel-content { + animation-duration: 0.3s; + } +} + +/* ===================== ACCESSIBILITY ===================== */ + +@media (prefers-reduced-motion: reduce) { + *[class*="contact-"], + *[class*="info-"], + .availability-status, + .status-dot { + animation: none !important; + opacity: 1 !important; + transform: none !important; + } + + .contact-item::before, + .contact-content-container::before { + display: none !important; + } + + .contact-item:hover { + transform: none; + } +} diff --git a/src/app/pages/contact/contact-content/contact-content.component.html b/src/app/pages/contact/contact-content/contact-content.component.html new file mode 100644 index 0000000..0f91e4a --- /dev/null +++ b/src/app/pages/contact/contact-content/contact-content.component.html @@ -0,0 +1,99 @@ + +
+
+ +
+
+ AVAILABLE FOR OPPORTUNITIES +
+ + + + + +
+ +
+
+ RESPONSE PROTOCOL +
+
+ Standard response time: 24-48 hour cycle
+ Emergency contact: Email protocol recommended
+ Priority inquiries: LinkedIn messaging for urgent requests +
+
+ + +
+
+ COLLABORATION STATUS +
+
+ Currently seeking: Full-time opportunities, internships, freelance projects
+ Specialization: Web development, Angular, modern tech stack
+ Location: Remote-ready, French hideout base of operations +
+
+
+
+
diff --git a/src/app/pages/contact/contact-content/contact-content.component.ts b/src/app/pages/contact/contact-content/contact-content.component.ts new file mode 100644 index 0000000..42c0299 --- /dev/null +++ b/src/app/pages/contact/contact-content/contact-content.component.ts @@ -0,0 +1,11 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-contact-content', + imports: [], + templateUrl: './contact-content.component.html', + styleUrl: './contact-content.component.css' +}) +export class ContactContentComponent { + +} diff --git a/src/app/pages/contact/contact.component.html b/src/app/pages/contact/contact.component.html index 06632fb..232e40a 100644 --- a/src/app/pages/contact/contact.component.html +++ b/src/app/pages/contact/contact.component.html @@ -1,3 +1,9 @@ -
- +
+ +
+ +
+ + +
diff --git a/src/app/pages/contact/contact.component.ts b/src/app/pages/contact/contact.component.ts index 32c4cad..0a7edba 100644 --- a/src/app/pages/contact/contact.component.ts +++ b/src/app/pages/contact/contact.component.ts @@ -1,9 +1,10 @@ import { Component } from '@angular/core'; import { SectionTitleComponent } from '../../components/section-title/section-title.component'; +import { ContactContentComponent } from './contact-content/contact-content.component'; @Component({ selector: 'app-contact', - imports: [SectionTitleComponent], + imports: [SectionTitleComponent, ContactContentComponent], templateUrl: './contact.component.html', styleUrl: './contact.component.css', }) diff --git a/src/app/pages/projects/projects.component.css b/src/app/pages/projects/projects.component.css index 29c8c11..e212ffb 100644 --- a/src/app/pages/projects/projects.component.css +++ b/src/app/pages/projects/projects.component.css @@ -1,24 +1,63 @@ -/* ===================== EXISTING ANIMATIONS ===================== */ +/* ===================== CORE ANIMATIONS ===================== */ -.animate-scan { - background: linear-gradient(90deg, transparent, rgba(0, 0, 0, 0.1), transparent); - animation: scan 3s infinite; +/* Universal slide animations */ +@keyframes slideInLeft { + from { + opacity: 0; + transform: translate3d(-30px, 0, 0); + } + to { + opacity: 1; + transform: translate3d(0, 0, 0); + } } -@keyframes scan { - 0% { transform: translateX(-100%); } - 100% { transform: translateX(100%); } +@keyframes slideInUp { + from { + opacity: 0; + transform: translate3d(0, 30px, 0); + } + to { + opacity: 1; + transform: translate3d(0, 0, 0); + } } -.animate-fade-in-line { - animation: fadeInLine 0.8s forwards; -} - -@keyframes fadeInLine { +@keyframes fadeIn { + from { opacity: 0; } to { opacity: 1; } } -/* Responsive grid for project cards */ +@keyframes scaleIn { + from { + opacity: 0; + transform: scale(0.9); + } + to { + opacity: 1; + transform: scale(1); + } +} + +@keyframes scanLine { + from { transform: translate3d(-100%, 0, 0); opacity: 0; } + 50% { opacity: 1; } + to { transform: translate3d(100%, 0, 0); opacity: 0; } +} + +/* ===================== UTILITY ANIMATIONS ===================== */ + +.animate-scan { + background: linear-gradient(90deg, transparent, rgba(0, 0, 0, 0.1), transparent); + animation: scanLine 3s infinite ease-in-out; +} + +.animate-fade-in-line { + animation: fadeIn 0.8s ease-out forwards; +} + +/* ===================== LAYOUT ===================== */ + .grid-project-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); @@ -31,249 +70,71 @@ } } -/* ===================== NEW PAGE-LEVEL ANIMATIONS ===================== */ +/* ===================== PAGE ELEMENTS ===================== */ -/* Section Title Animation */ +/* Section title - no animation */ .section-title-container { - opacity: 0; - transform: translateY(-30px); - animation: sectionTitleSlideDown 1s ease-out 0.2s forwards; + opacity: 1; } -@keyframes sectionTitleSlideDown { - 0% { - opacity: 0; - transform: translateY(-30px); - filter: blur(2px); - } - 60% { - opacity: 0.8; - transform: translateY(5px); - filter: blur(0.5px); - } - 100% { - opacity: 1; - transform: translateY(0); - filter: blur(0px); - } -} - -/* Terminal Header Animation */ +/* Terminal header */ .terminal-header { opacity: 0; - transform: scale(0.95) translateY(20px); - animation: terminalHeaderMaterialize 1.2s ease-out 0.4s forwards; + animation: slideInUp 0.8s ease-out 0.3s forwards; } -@keyframes terminalHeaderMaterialize { - 0% { - opacity: 0; - transform: scale(0.95) translateY(20px); - border-color: transparent; - background-color: rgba(41, 41, 37, 0); - } - 30% { - opacity: 0.4; - transform: scale(0.98) translateY(10px); - border-color: var(--color-nier-border); - } - 70% { - opacity: 0.8; - transform: scale(1.01) translateY(-2px); - background-color: var(--color-nier-checkered-bg); - } - 100% { - opacity: 1; - transform: scale(1) translateY(0); - border-color: var(--color-nier-border); - background-color: var(--color-nier-checkered-bg); - } -} - -/* Terminal Header Text Lines */ .terminal-line { opacity: 0; - transform: translateX(-20px); - animation: terminalLineType 0.6s ease-out forwards; + animation: slideInLeft 0.4s ease-out forwards; } -.terminal-line:nth-child(1) { animation-delay: 0.8s; } -.terminal-line:nth-child(2) { animation-delay: 1.2s; } -.terminal-line:nth-child(3) { animation-delay: 1.6s; } +.terminal-line:nth-child(1) { animation-delay: 0.6s; } +.terminal-line:nth-child(2) { animation-delay: 0.8s; } +.terminal-line:nth-child(3) { animation-delay: 1.0s; } -@keyframes terminalLineType { - 0% { - opacity: 0; - transform: translateX(-20px); - } - 50% { - opacity: 0.7; - transform: translateX(-5px); - } - 100% { - opacity: 1; - transform: translateX(0); - } -} - -/* Progress Bar Container Animation */ +/* Progress and directory */ .progress-container { opacity: 0; - transform: scaleX(0.8); - animation: progressContainerExpand 0.8s ease-out 1.0s forwards; + animation: scaleIn 0.6s ease-out 0.8s forwards; } -@keyframes progressContainerExpand { - 0% { - opacity: 0; - transform: scaleX(0.8); - border-color: transparent; - } - 100% { - opacity: 1; - transform: scaleX(1); - border-color: var(--color-nier-accent); - } -} - -/* Directory Tree Animation */ .directory-tree { opacity: 0; - transform: translateY(30px); - animation: directoryTreeRise 1s ease-out 0.6s forwards; + animation: slideInUp 0.8s ease-out 0.4s forwards; } -@keyframes directoryTreeRise { - 0% { - opacity: 0; - transform: translateY(30px); - border-color: transparent; - background-color: rgba(41, 41, 37, 0); - } - 40% { - opacity: 0.6; - transform: translateY(10px); - border-color: var(--color-nier-accent); - } - 100% { - opacity: 1; - transform: translateY(0); - border-color: var(--color-nier-accent); - background-color: var(--color-nier-dark); - } -} - -/* Directory Tree Header */ .directory-header { opacity: 0; - transform: translateX(-30px); - animation: directoryHeaderSlide 0.7s ease-out 1.2s forwards; + animation: slideInLeft 0.5s ease-out 1.0s forwards; } -@keyframes directoryHeaderSlide { - 0% { - opacity: 0; - transform: translateX(-30px); - border-bottom-color: transparent; - } - 100% { - opacity: 1; - transform: translateX(0); - border-bottom-color: var(--color-nier-mid); - } -} - -/* Enhanced Directory Lines Animation */ .animate-fade-in-line { opacity: 0; - transform: translateX(-15px); - animation: directoryLineFadeIn 0.4s ease-out forwards; + animation: slideInLeft 0.3s ease-out forwards; } -@keyframes directoryLineFadeIn { - 0% { - opacity: 0; - transform: translateX(-15px); - } - 100% { - opacity: 1; - transform: translateX(0); - } -} - -/* Projects Grid Container Animation */ .projects-grid { opacity: 0; - transform: translateY(40px); - animation: projectsGridReveal 1s ease-out 0.8s forwards; + animation: slideInUp 0.8s ease-out 0.6s forwards; } -@keyframes projectsGridReveal { - 0% { - opacity: 0; - transform: translateY(40px); - } - 100% { - opacity: 1; - transform: translateY(0); - } -} - -/* Footer Animation */ .directory-footer { opacity: 0; - transform: translateY(20px); - animation: footerSlideUp 0.8s ease-out 1.4s forwards; + animation: slideInUp 0.6s ease-out 1.2s forwards; } -@keyframes footerSlideUp { - 0% { - opacity: 0; - transform: translateY(20px); - border-top-color: transparent; - } - 100% { - opacity: 1; - transform: translateY(0); - border-top-color: var(--color-nier-border); - } -} - -/* Enhanced Scan Animation for Terminal Header */ +/* Enhanced scan for terminal */ .enhanced-scan { - background: linear-gradient(90deg, - transparent, - rgba(255, 201, 102, 0.3), - transparent - ); - animation: enhancedScanAnimation 3s ease-in-out infinite; -} - -@keyframes enhancedScanAnimation { - 0% { - transform: translateX(-100%); - } - 50% { - transform: translateX(100%); - } - 100% { - transform: translateX(-100%); - } -} - -/* Background Pattern Animation */ -.checkered-background { - position: relative; + background: linear-gradient(90deg, transparent, rgba(255, 201, 102, 0.3), transparent); + animation: scanLine 3s ease-in-out infinite; } +/* Background pattern - static */ .checkered-background::before { content: ''; position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - opacity: 0; + inset: 0; + opacity: 1; background-image: linear-gradient(45deg, rgba(255, 201, 102, 0.02) 25%, transparent 25%), linear-gradient(-45deg, rgba(255, 201, 102, 0.02) 25%, transparent 25%), @@ -281,56 +142,18 @@ linear-gradient(-45deg, transparent 75%, rgba(255, 201, 102, 0.02) 75%); background-size: 20px 20px; background-position: 0 0, 0 10px, 10px -10px, -10px 0px; - animation: backgroundPatternFadeIn 2s ease-out 0.1s forwards; pointer-events: none; } -@keyframes backgroundPatternFadeIn { - 0% { - opacity: 0; - } - 100% { - opacity: 1; - } -} +/* ===================== PROJECT CARDS ===================== */ -/* ===================== EXISTING PROJECT CARD ANIMATIONS ===================== */ - -/* Base styles with fade-in animations */ .project-card { min-height: 300px; cursor: pointer; - /* Animation - initially hidden */ opacity: 0; - transform: scale(0.95) translateY(20px); - animation: cardMaterialize 1.2s ease-out forwards; + animation: slideInUp 0.8s ease-out forwards; } -/* Main card materialization animation */ -@keyframes cardMaterialize { - 0% { - opacity: 0; - transform: scale(0.95) translateY(20px); - filter: blur(2px); - } - 20% { - opacity: 0.3; - transform: scale(0.98) translateY(10px); - filter: blur(1px); - } - 60% { - opacity: 0.8; - transform: scale(1.01) translateY(-2px); - filter: blur(0px); - } - 100% { - opacity: 1; - transform: scale(1) translateY(0); - filter: blur(0px); - } -} - -/* Scanning line effect during load */ .project-card::before { content: ''; position: absolute; @@ -338,142 +161,39 @@ left: -100%; width: 100%; height: 2px; - background: linear-gradient(90deg, - transparent, - var(--color-nier-accent), - transparent - ); - animation: scanLine 2s ease-out 0.2s forwards; + background: linear-gradient(90deg, transparent, var(--color-nier-accent), transparent); + animation: scanLine 1.5s ease-out 0.2s forwards; z-index: 20; } -@keyframes scanLine { - 0% { - left: -100%; - opacity: 0; - } - 50% { - opacity: 1; - } - 100% { - left: 100%; - opacity: 0; - } -} - -.project-card:not(.redacted):hover .click-indicator { - opacity: 1; - transition: opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1); -} - -/* Header section animation */ .project-header { opacity: 0; - transform: translateX(-30px); - animation: headerSlideIn 0.8s ease-out 0.3s forwards; + animation: slideInLeft 0.5s ease-out 0.2s forwards; } -@keyframes headerSlideIn { - 0% { - opacity: 0; - transform: translateX(-30px); - border-bottom-color: transparent; - } - 50% { - opacity: 0.6; - transform: translateX(-5px); - } - 100% { - opacity: 1; - transform: translateX(0); - border-bottom-color: var(--color-nier-border); - } -} - -/* Status text fade-in effect (reduced glitch) */ -.project-status { - opacity: 0; - animation: statusFadeIn 0.6s ease-out 0.5s forwards; -} - -@keyframes statusFadeIn { - 0% { - opacity: 0; - transform: translateX(-10px); - } - 100% { - opacity: 1; - transform: translateX(0); - } -} - -/* Title fade and scale */ -.project-title { - opacity: 0; - transform: scale(0.9); - animation: titleExpand 0.7s ease-out 0.7s forwards; -} - -@keyframes titleExpand { - 0% { - opacity: 0; - transform: scale(0.9); - letter-spacing: -0.1em; - } - 70% { - opacity: 0.8; - transform: scale(1.02); - letter-spacing: 0.05em; - } - 100% { - opacity: 1; - transform: scale(1); - letter-spacing: 0.1em; - } -} - -/* Body content staggered animation */ +.project-status, +.project-title, .project-body { opacity: 0; - animation: bodyFadeUp 0.8s ease-out 0.9s forwards; + animation: fadeIn 0.4s ease-out forwards; } -@keyframes bodyFadeUp { - 0% { - opacity: 0; - transform: translateY(15px); - } - 100% { - opacity: 1; - transform: translateY(0); - } -} +.project-status { animation-delay: 0.4s; } +.project-title { animation-delay: 0.5s; } +.project-body { animation-delay: 0.6s; } .project-detail { display: flex; align-items: flex-start; font-size: 0.875rem; line-height: 1.5; - /* Animation */ opacity: 0; - transform: translateX(-20px); - animation: detailSlideIn 0.5s ease-out forwards; + animation: slideInLeft 0.3s ease-out forwards; } -.project-detail:nth-child(1) { animation-delay: 1.1s; } -.project-detail:nth-child(2) { animation-delay: 1.2s; } -.project-detail:nth-child(3) { animation-delay: 1.3s; } - -@keyframes detailSlideIn { - 0% { - opacity: 0; - transform: translateX(-20px); - } - 100% { - opacity: 1; - transform: translateX(0); - } -} +.project-detail:nth-child(1) { animation-delay: 0.8s; } +.project-detail:nth-child(2) { animation-delay: 0.9s; } +.project-detail:nth-child(3) { animation-delay: 1.0s; } .detail-label { font-weight: 500; @@ -503,47 +223,14 @@ font-weight: 500; letter-spacing: 0.05em; border-radius: 2px; - /* Animation */ opacity: 0; - transform: scale(0.8); - animation: techItemPop 0.3s ease-out forwards; - animation-delay: 1.6s; /* Default delay, overridden by Angular */ + animation: scaleIn 0.2s ease-out forwards; + animation-delay: 1.2s; } -@keyframes techItemPop { - 0% { - opacity: 0; - transform: scale(0.8); - border-color: transparent; - } - 50% { - opacity: 0.8; - transform: scale(1.1); - } - 100% { - opacity: 1; - transform: scale(1); - border-color: var(--color-nier-border); - } -} - -/* Tech stack animation */ .tech-stack { opacity: 0; - animation: techStackReveal 0.6s ease-out 1.4s forwards; -} - -@keyframes techStackReveal { - 0% { - opacity: 0; - transform: translateY(10px); - border-top-color: transparent; - } - 100% { - opacity: 1; - transform: translateY(0); - border-top-color: var(--color-nier-border); - } + animation: slideInUp 0.4s ease-out 1.1s forwards; } .access-section { @@ -551,32 +238,13 @@ background-color: var(--color-nier-checkered-bg); background-size: 0.2rem 0.2rem; background-image: - linear-gradient(to right, var(--color-nier-checkered-grid) 1px, rgba(0, 0, 0, 0) 1px), - linear-gradient(to bottom, var(--color-nier-checkered-grid) 1px, rgba(0, 0, 0, 0) 1px); + linear-gradient(to right, var(--color-nier-checkered-grid) 1px, transparent 1px), + linear-gradient(to bottom, var(--color-nier-checkered-grid) 1px, transparent 1px); border-top: 1px solid var(--color-nier-border); display: flex; gap: 1rem; - /* Animation */ opacity: 0; - transform: translateY(20px); - animation: accessSectionRise 0.7s ease-out 1.8s forwards; -} - -@keyframes accessSectionRise { - 0% { - opacity: 0; - transform: translateY(20px); - border-top-color: transparent; - } - 50% { - opacity: 0.7; - transform: translateY(-2px); - } - 100% { - opacity: 1; - transform: translateY(0); - border-top-color: var(--color-nier-border); - } + animation: slideInUp 0.5s ease-out 1.4s forwards; } .access-btn { @@ -588,38 +256,15 @@ font-size: 0.875rem; font-weight: 500; cursor: pointer; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + transition: all 0.3s ease; letter-spacing: 0.05em; flex: 1; - /* Animation */ opacity: 0; - transform: scale(0.9); - animation: buttonMaterialize 0.4s ease-out forwards; + animation: scaleIn 0.3s ease-out forwards; } -.access-btn:first-child { animation-delay: 2.0s; } -.access-btn:last-child { animation-delay: 2.1s; } - -@keyframes buttonMaterialize { - 0% { - opacity: 0; - transform: scale(0.9); - border-color: transparent; - box-shadow: none; - } - 30% { - opacity: 0.6; - transform: scale(1.05); - border-color: var(--color-nier-accent); - box-shadow: 0 0 10px rgba(255, 201, 102, 0.3); - } - 100% { - opacity: 1; - transform: scale(1); - border-color: var(--color-nier-accent); - box-shadow: none; - } -} +.access-btn:first-child { animation-delay: 1.6s; } +.access-btn:last-child { animation-delay: 1.7s; } .access-btn:hover { background-color: var(--color-nier-accent); @@ -638,55 +283,22 @@ opacity: 0; transition: opacity 0.3s ease; pointer-events: none; - /* Animation */ - animation: indicatorFade 0.5s ease-out 2.3s forwards; } -@keyframes indicatorFade { - 0% { - opacity: 0; - transform: translateY(5px); - } - 100% { - opacity: 0; /* Still hidden until hover */ - transform: translateY(0); - } +.project-card:not(.redacted):hover .click-indicator { + opacity: 1; } -/* Status Colors */ +/* Status colors */ .status-completed { color: var(--color-nier-accent); } .status-active { color: #4a7c59; } .status-experimental { color: #7c5a4a; } .status-archived { color: var(--color-nier-mid); } -/* Redacted Styles */ +/* Redacted cards */ .project-card.redacted { cursor: not-allowed; - /* Special animation for redacted cards */ - animation: redactedMaterialize 1.5s ease-out forwards; -} - -@keyframes redactedMaterialize { - 0% { - opacity: 0; - transform: scale(0.95); - filter: blur(3px); - } - 30% { - opacity: 0.4; - transform: scale(0.98); - filter: blur(2px); - } - 60% { - opacity: 0.7; - transform: scale(1.01); - filter: blur(1px); - } - 100% { - opacity: 1; - transform: scale(1); - filter: blur(0px); - } + animation: slideInUp 1s ease-out forwards; } .project-card.redacted:hover { @@ -723,224 +335,63 @@ border: 2px solid var(--color-nier-text-light); text-align: center; z-index: 10; - /* Animation */ opacity: 0; - animation: redactedOverlaySlam 0.6s ease-out 1.2s forwards; + animation: scaleIn 0.4s ease-out 0.8s forwards; } -@keyframes redactedOverlaySlam { - 0% { - opacity: 0; - transform: translate(-50%, -50%) scale(0.8) rotate(-2deg); - } - 50% { - opacity: 0.9; - transform: translate(-50%, -50%) scale(1.1) rotate(1deg); - } - 100% { - opacity: 1; - transform: translate(-50%, -50%) scale(1) rotate(0deg); - } -} - -/* Static overlay animation for redacted cards */ -.static-overlay { - opacity: 0; - animation: staticFadeIn 0.8s ease-out 1.0s forwards; -} - -@keyframes staticFadeIn { - 0% { - opacity: 0; - } - 100% { - opacity: 1; - } -} - -/* Corner accent animation */ -.corner-accent { - opacity: 0; - animation: cornerAccentGlow 0.8s ease-out 2.5s forwards; -} - -@keyframes cornerAccentGlow { - 0% { - opacity: 0; - transform: scale(0.5); - } - 50% { - opacity: 0.8; - transform: scale(1.2); - } - 100% { - opacity: 1; - transform: scale(1); - } -} - -/* Glitch layers animation for added cyberpunk effect */ +.static-overlay, +.corner-accent, .glitch-layer { opacity: 0; - animation: glitchLayerFlicker 0.1s ease-in-out forwards; + animation: fadeIn 0.5s ease-out 0.6s forwards; } -.glitch-layer-1 { animation-delay: 0.8s; } -.glitch-layer-2 { animation-delay: 1.0s; } -.glitch-layer-3 { animation-delay: 1.2s; } +/* ===================== MODALS ===================== */ -@keyframes glitchLayerFlicker { - 0%, 100% { opacity: 0; } - 50% { opacity: 0.1; } -} - -/* ===================== MODAL ANIMATIONS ===================== */ - -/* Modal Animations */ .modal-overlay { opacity: 0; - animation: modalOverlayFadeIn 0.4s ease-out forwards; -} - -@keyframes modalOverlayFadeIn { - 0% { - opacity: 0; - } - 100% { - opacity: 1; - } + animation: fadeIn 0.3s ease-out forwards; } .modal-container { opacity: 0; - transform: scale(0.9) translateY(-20px); - animation: modalContainerSlideIn 0.5s ease-out 0.1s forwards; -} - -@keyframes modalContainerSlideIn { - 0% { - opacity: 0; - transform: scale(0.9) translateY(-20px); - filter: blur(1px); - } - 60% { - opacity: 0.9; - transform: scale(1.02) translateY(-5px); - filter: blur(0px); - } - 100% { - opacity: 1; - transform: scale(1) translateY(0); - filter: blur(0px); - } + transform: scale(0.95) translateY(-20px); + animation: scaleIn 0.4s ease-out 0.1s forwards; } .modal-header { opacity: 0; - transform: translateY(-10px); - animation: modalHeaderFadeIn 0.4s ease-out 0.3s forwards; -} - -@keyframes modalHeaderFadeIn { - 0% { - opacity: 0; - transform: translateY(-10px); - } - 100% { - opacity: 1; - transform: translateY(0); - } + animation: slideInUp 0.3s ease-out 0.2s forwards; } .modal-content { opacity: 0; - transform: translateY(15px); - animation: modalContentFadeUp 0.5s ease-out 0.4s forwards; -} - -@keyframes modalContentFadeUp { - 0% { - opacity: 0; - transform: translateY(15px); - } - 100% { - opacity: 1; - transform: translateY(0); - } + animation: slideInUp 0.4s ease-out 0.3s forwards; } .modal-section { opacity: 0; - transform: translateX(-10px); - animation: modalSectionSlideIn 0.3s ease-out forwards; + animation: slideInLeft 0.2s ease-out forwards; } -.modal-section:nth-child(1) { animation-delay: 0.6s; } -.modal-section:nth-child(2) { animation-delay: 0.7s; } -.modal-section:nth-child(3) { animation-delay: 0.8s; } -.modal-section:nth-child(4) { animation-delay: 0.9s; } -.modal-section:nth-child(5) { animation-delay: 1.0s; } - -@keyframes modalSectionSlideIn { - 0% { - opacity: 0; - transform: translateX(-10px); - } - 100% { - opacity: 1; - transform: translateX(0); - } -} +.modal-section:nth-child(1) { animation-delay: 0.4s; } +.modal-section:nth-child(2) { animation-delay: 0.5s; } +.modal-section:nth-child(3) { animation-delay: 0.6s; } +.modal-section:nth-child(4) { animation-delay: 0.7s; } +.modal-section:nth-child(5) { animation-delay: 0.8s; } .modal-close-btn { opacity: 0; - transform: scale(0.8); - animation: modalCloseBtnPop 0.3s ease-out 0.5s forwards; -} - -@keyframes modalCloseBtnPop { - 0% { - opacity: 0; - transform: scale(0.8); - } - 50% { - opacity: 0.8; - transform: scale(1.1); - } - 100% { - opacity: 1; - transform: scale(1); - } + animation: scaleIn 0.2s ease-out 0.4s forwards; } /* Modal exit animations */ .modal-overlay.closing { - animation: modalOverlayFadeOut 0.3s ease-in forwards; -} - -@keyframes modalOverlayFadeOut { - 0% { - opacity: 1; - } - 100% { - opacity: 0; - } + animation: fadeIn 0.2s ease-in reverse forwards; } .modal-container.closing { - animation: modalContainerSlideOut 0.3s ease-in forwards; -} - -@keyframes modalContainerSlideOut { - 0% { - opacity: 1; - transform: scale(1) translateY(0); - } - 100% { - opacity: 0; - transform: scale(0.95) translateY(-10px); - filter: blur(1px); - } + animation: scaleIn 0.2s ease-in reverse forwards; } /* ===================== RESPONSIVE DESIGN ===================== */ @@ -958,91 +409,34 @@ flex: none; } - .grid-project-cards { - grid-template-columns: 1fr; - } - /* Faster animations on mobile */ - .project-card { - animation-duration: 1s; - } - - .project-header { - animation-duration: 0.6s; - animation-delay: 0.2s; - } - - .project-status { - animation-delay: 0.4s; - } - - .project-title { - animation-delay: 0.5s; - } - - .project-body { - animation-delay: 0.6s; - } - - /* Faster page-level animations on mobile */ - .section-title-container { - animation-duration: 0.8s; - animation-delay: 0.1s; - } - - .terminal-header { - animation-duration: 1s; - animation-delay: 0.3s; - } - - .directory-tree { - animation-duration: 0.8s; - animation-delay: 0.5s; - } - + .project-card, + .terminal-header, + .directory-tree, .projects-grid { - animation-duration: 0.8s; - animation-delay: 0.7s; + animation-duration: 0.6s; + } + + .terminal-line, + .project-detail { + animation-duration: 0.3s; } } /* ===================== ACCESSIBILITY ===================== */ -/* Accessibility - Respect reduced motion preferences */ @media (prefers-reduced-motion: reduce) { - .project-card, - .project-header, - .project-status, - .project-title, - .project-body, - .project-detail, - .tech-stack, - .tech-item, - .access-section, - .access-btn, - .click-indicator, - .corner-accent, - .glitch-layer, - .static-overlay, - .redacted-overlay, - .section-title-container, - .terminal-header, - .terminal-line, - .progress-container, - .directory-tree, - .directory-header, - .animate-fade-in-line, - .projects-grid, - .directory-footer { - animation: none !important; - opacity: 1 !important; - transform: none !important; + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; } - .project-card::before, .animate-scan, .enhanced-scan, - .checkered-background::before { + .project-card::before { display: none !important; } } diff --git a/src/styles.css b/src/styles.css index d13f61c..40900f4 100644 --- a/src/styles.css +++ b/src/styles.css @@ -1,3 +1,5 @@ +/* ===================== OPTIMIZED GLOBAL THEME ===================== */ + @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500&family=Major+Mono+Display&family=Noto+Sans+JP:wght@300;400;500&family=VT323&display=swap'); @layer theme, tailwind-base, tailwind-utilities; @@ -14,146 +16,108 @@ --font-terminal-nier: 'Major Mono Display', monospace; --font-noto-jp: 'Noto Sans JP', sans-serif; - /* Authentic NieR Automata UI colors from screenshot */ - --color-nier-bg: #dcd8c0; /* Main parchment background */ - --color-nier-dark: #5a5a50; /* Dark UI elements */ - --color-nier-mid: #adaba0; /* Medium gray UI elements */ - --color-nier-text-dark: #292925; /* Dark text */ - --color-nier-text-light: #dcd8c0; /* Light text */ - --color-nier-border: #a39e8c; /* Border color */ - --color-nier-grid: #bfbcad; /* Grid lines */ - --color-nier-accent: #292925; /* Dark accent */ - --color-nier-highlight: #5a5a50; /* Highlight for selection */ + /* NieR Automata UI colors */ + --color-nier-bg: #dcd8c0; + --color-nier-dark: #5a5a50; + --color-nier-mid: #adaba0; + --color-nier-text-dark: #292925; + --color-nier-text-light: #dcd8c0; + --color-nier-border: #a39e8c; + --color-nier-grid: #bfbcad; + --color-nier-accent: #292925; + --color-nier-highlight: #5a5a50; + --color-nier-checkered-bg: #d1cdb7; + --color-nier-checkered-grid: #ccc8b1; + } - /* Checkered background colors */ - --color-nier-checkered-bg: #d1cdb7; /* Slightly darker than main bg */ - --color-nier-checkered-grid: #ccc8b1; /* Grid lines for checkered pattern */ + /* Dark mode variables */ + @media (prefers-color-scheme: dark) { + :root { + --color-nier-bg: #292925; + --color-nier-dark: #adaba0; + --color-nier-mid: #5a5a50; + --color-nier-text-dark: #dcd8c0; + --color-nier-text-light: #292925; + --color-nier-border: #5a5a50; + --color-nier-grid: #3f3e39; + --color-nier-accent: #dcd8c0; + --color-nier-highlight: #adaba0; + --color-nier-checkered-bg: #121210; + --color-nier-checkered-grid: #323232; + } + } + + /* Manual theme classes */ + .dark { + --color-nier-bg: #292925; + --color-nier-dark: #adaba0; + --color-nier-mid: #5a5a50; + --color-nier-text-dark: #dcd8c0; + --color-nier-text-light: #292925; + --color-nier-border: #5a5a50; + --color-nier-grid: #3f3e39; + --color-nier-accent: #dcd8c0; + --color-nier-highlight: #adaba0; + --color-nier-checkered-bg: #121210; + --color-nier-checkered-grid: #323232; + } + + .light { + --color-nier-bg: #dcd8c0; + --color-nier-dark: #5a5a50; + --color-nier-mid: #adaba0; + --color-nier-text-dark: #292925; + --color-nier-text-light: #dcd8c0; + --color-nier-border: #a39e8c; + --color-nier-grid: #bfbcad; + --color-nier-accent: #292925; + --color-nier-highlight: #5a5a50; + --color-nier-checkered-bg: #d1cdb7; + --color-nier-checkered-grid: #ccc8b1; } } -@media (prefers-color-scheme: dark) { - :root { - /* Inverted colors for dark mode */ - --color-nier-bg: #292925; /* Inverted from text-dark */ - --color-nier-dark: #adaba0; /* Inverted from mid */ - --color-nier-mid: #5a5a50; /* Inverted from dark */ - --color-nier-text-dark: #dcd8c0; /* Inverted from bg */ - --color-nier-text-light: #292925; /* Inverted from text-dark */ - --color-nier-border: #5a5a50; /* Darkened border */ - --color-nier-grid: #3f3e39; /* Darkened grid */ - --color-nier-accent: #dcd8c0; /* Inverted from accent */ - --color-nier-highlight: #adaba0; /* Inverted from highlight */ - - /* Inverted checkered background colors */ - --color-nier-checkered-bg: #121210; /* Dark background for checkered pattern */ - --color-nier-checkered-grid: #323232; /* Grid lines for dark checkered pattern */ - } -} - -/* Manual dark mode class for toggle functionality */ -.dark { - /* Same inverted colors as media query */ - --color-nier-bg: #292925; /* Inverted from text-dark */ - --color-nier-dark: #adaba0; /* Inverted from mid */ - --color-nier-mid: #5a5a50; /* Inverted from dark */ - --color-nier-text-dark: #dcd8c0; /* Inverted from bg */ - --color-nier-text-light: #292925; /* Inverted from text-dark */ - --color-nier-border: #5a5a50; /* Darkened border */ - --color-nier-grid: #3f3e39; /* Darkened grid */ - --color-nier-accent: #dcd8c0; /* Inverted from accent */ - --color-nier-highlight: #adaba0; /* Inverted from highlight */ - - /* Inverted checkered background colors */ - --color-nier-checkered-bg: #121210; /* Dark background for checkered pattern */ - --color-nier-checkered-grid: #323232; /* Grid lines for dark checkered pattern */ -} - -/* Light mode class to override system preference */ -.light { - /* Original colors */ - --color-nier-bg: #dcd8c0; /* Main parchment background */ - --color-nier-dark: #5a5a50; /* Dark UI elements */ - --color-nier-mid: #adaba0; /* Medium gray UI elements */ - --color-nier-text-dark: #292925; /* Dark text */ - --color-nier-text-light: #dcd8c0; /* Light text */ - --color-nier-border: #a39e8c; /* Border color */ - --color-nier-grid: #bfbcad; /* Grid lines */ - --color-nier-accent: #292925; /* Dark accent */ - --color-nier-highlight: #5a5a50; /* Highlight for selection */ - - /* Checkered background colors */ - --color-nier-checkered-bg: #d1cdb7; /* Slightly darker than main bg */ - --color-nier-checkered-grid: #ccc8b1; /* Grid lines for checkered pattern */ -} - - @layer tailwind-utilities { /* Font utilities */ - .font-terminal { - font-family: var(--font-terminal); - } - - .font-terminal-retro { - font-family: var(--font-terminal-retro); - } - - .font-terminal-nier { - font-family: var(--font-terminal-nier); - } - + .font-terminal { font-family: var(--font-terminal); } + .font-terminal-retro { font-family: var(--font-terminal-retro); } + .font-terminal-nier { font-family: var(--font-terminal-nier); } .font-noto-jp { font-family: var(--font-noto-jp); - letter-spacing: -0.02em; /* Slight adjustment to mimic NieR style */ + letter-spacing: -0.02em; } /* Background utilities */ - .bg-nier-bg { - background-color: var(--color-nier-bg); - } - - .bg-nier-dark { - background-color: var(--color-nier-dark); - } - - .bg-nier-mid { - background-color: var(--color-nier-mid); - } + .bg-nier-bg { background-color: var(--color-nier-bg); } + .bg-nier-dark { background-color: var(--color-nier-dark); } + .bg-nier-mid { background-color: var(--color-nier-mid); } /* Text utilities */ - .text-nier-dark { - color: var(--color-nier-text-dark); - } - - .text-nier-light { - color: var(--color-nier-text-light); - } + .text-nier-dark { color: var(--color-nier-text-dark); } + .text-nier-light { color: var(--color-nier-text-light); } /* Border utilities */ - .border-nier-border { - border-color: var(--color-nier-border); - } + .border-nier-border { border-color: var(--color-nier-border); } + .border-nier-accent { border-color: var(--color-nier-accent); } - .border-nier-accent { - border-color: var(--color-nier-accent); - } - - /* Checkered background using theme variables */ + /* Pattern utilities */ .checkered-background { background-color: var(--color-nier-checkered-bg); background-size: 0.3rem 0.3rem; - background-image: linear-gradient(to right, var(--color-nier-checkered-grid) 1px, rgba(0, 0, 0, 0) 1px), - linear-gradient(to bottom, var(--color-nier-checkered-grid) 1px, rgba(0, 0, 0, 0) 1px); + background-image: + linear-gradient(to right, var(--color-nier-checkered-grid) 1px, transparent 1px), + linear-gradient(to bottom, var(--color-nier-checkered-grid) 1px, transparent 1px); color: var(--color-nier-text-dark); - transition: background-color 0.3s ease, background-image 0.3s ease, color 0.3s ease; + transition: all 0.3s ease; } - /* Additional utility for grid-based layouts */ .nier-grid { display: grid; background-color: var(--color-nier-bg); background-size: 2rem 2rem; - background-image: linear-gradient(to right, var(--color-nier-grid) 1px, rgba(0, 0, 0, 0) 1px), - linear-gradient(to bottom, var(--color-nier-grid) 1px, rgba(0, 0, 0, 0) 1px); + background-image: + linear-gradient(to right, var(--color-nier-grid) 1px, transparent 1px), + linear-gradient(to bottom, var(--color-nier-grid) 1px, transparent 1px); border: 1px solid var(--color-nier-border); transition: all 0.3s ease; }