Added animation to tree node

This commit is contained in:
AdamBtech
2025-05-24 00:26:13 +02:00
parent ea11cab734
commit 06bd75fb39
5 changed files with 133 additions and 32 deletions

View File

@@ -2,7 +2,7 @@
<app-section-title title="NEURAL PROFILE" /> <app-section-title title="NEURAL PROFILE" />
<div class="flex-1 flex items-start justify-between gap-6 mt-6"> <div class="flex-1 flex items-start justify-between gap-6 mt-6">
<!-- Left side - Neural Profile Tree (always shown) --> <!-- Left side - Neural Profile Tree with NieR animations -->
<div class="flex-shrink-0"> <div class="flex-shrink-0">
<app-neural-profile-tree /> <app-neural-profile-tree />
</div> </div>

View File

@@ -0,0 +1,119 @@
/* NieR-style hover effects for tree nodes */
.tree-node-item {
position: relative;
overflow: hidden;
transition: color 0.3s ease;
border-bottom: 1px solid transparent;
}
/* Underline effect */
.tree-node-item::before {
content: "";
position: absolute;
left: 0;
bottom: 0;
width: 0;
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%;
width: 100%;
height: 1px;
background-color: var(--color-nier-text-dark);
opacity: 0;
z-index: 3;
pointer-events: none;
}
.tree-node-item:hover .scan-line {
animation: nier-button-scan 0.3s ease forwards;
}
/* Text glitch animation for tree nodes */
@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);
}
}
/* The scan line animation */
@keyframes nier-button-scan {
0% {
opacity: 0.5;
left: -100%;
}
100% {
opacity: 0;
left: 100%;
}
}
/* Active/click state for tree nodes */
.tree-node-item:active {
transform: scale(0.98);
}
/* Touch device optimization */
@media (hover: none) {
.tree-node-item:active::before {
width: 100%;
}
.tree-node-item:active .scan-line {
animation: nier-button-scan 0.3s ease forwards;
}
.tree-node-item:active {
color: var(--color-nier-text-dark);
}
}

View File

@@ -1,4 +1,4 @@
<div class="bg-nier-bg border-2 border-nier-border text-nier-dark w-96 font-terminal text-sm leading-relaxed p-4"> <div class="bg-nier-bg border-2 border-nier-border text-nier-dark w-96 md:w-[28rem] lg:w-[32rem] font-terminal text-sm md:text-base lg:text-lg leading-relaxed p-4 md:p-6">
@for (node of treeData(); track node.id) { @for (node of treeData(); track node.id) {
<div <div
class="border-l-2 border-transparent" class="border-l-2 border-transparent"
@@ -6,8 +6,10 @@
[class.hidden]="!node.visible" [class.hidden]="!node.visible"
> >
<div <div
class="flex items-center py-1 px-2 cursor-pointer transition-all duration-150 ease-in-out hover:bg-nier-mid/20 hover:translate-x-0.5 relative" class="tree-node-item flex items-center py-1 md:py-2 px-2 md:px-3 cursor-pointer transition-all duration-150 ease-in-out hover:bg-nier-mid/20 hover:translate-x-0.5 relative overflow-hidden"
[class.font-bold]="node.isSelected"
[style.padding-left]="getIndentPadding(node.level)" [style.padding-left]="getIndentPadding(node.level)"
[attr.data-label]="node.title"
(click)="selectNode(node)" (click)="selectNode(node)"
> >
<!-- Selection indicator --> <!-- Selection indicator -->
@@ -17,7 +19,7 @@
<!-- Expand/Collapse Icon --> <!-- Expand/Collapse Icon -->
<span <span
class="w-4 h-4 flex items-center justify-center text-nier-accent mr-2 cursor-pointer select-none" class="w-4 h-4 md:w-5 md:h-5 lg:w-6 lg:h-6 flex items-center justify-center text-nier-accent mr-2 md:mr-3 cursor-pointer select-none text-sm md:text-base lg:text-lg"
[class.text-nier-mid]="!node.hasChildren" [class.text-nier-mid]="!node.hasChildren"
(click)="$event.stopPropagation(); toggleExpand(node)" (click)="$event.stopPropagation(); toggleExpand(node)"
> >
@@ -34,13 +36,16 @@
} }
</span> </span>
<!-- Node Title --> <!-- Node Title with NieR effects -->
<span <span
class="flex-1 text-nier-dark select-none" class="flex-1 text-nier-dark select-none relative z-10"
[class.font-bold]="node.isSelected" [class.font-bold]="node.isSelected"
> >
{{ node.title }} {{ node.title }}
</span> </span>
<!-- Scan line effect -->
<span class="scan-line"></span>
</div> </div>
</div> </div>
} }

View File

@@ -17,7 +17,7 @@ export interface NeuralProfileNode {
standalone: true, standalone: true,
imports: [CommonModule], imports: [CommonModule],
templateUrl: './neural-profile-tree.component.html', templateUrl: './neural-profile-tree.component.html',
styles: [], styleUrl: './neural-profile-tree.component.css',
}) })
export class NeuralProfileTreeComponent { export class NeuralProfileTreeComponent {
// Input for external data (optional) // Input for external data (optional)
@@ -32,8 +32,8 @@ export class NeuralProfileTreeComponent {
private getInitialData(): NeuralProfileNode[] { private getInitialData(): NeuralProfileNode[] {
return [ return [
{ {
id: 'neuralProfile', id: 'workstation',
title: ':TECH_CORE ', title: 'Workstation/',
isExpanded: true, isExpanded: true,
isSelected: false, isSelected: false,
level: 0, level: 0,

View File

@@ -1,23 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HeaderContactLinksComponent } from './header-contact-links.component';
describe('HeaderContactLinksComponent', () => {
let component: HeaderContactLinksComponent;
let fixture: ComponentFixture<HeaderContactLinksComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [HeaderContactLinksComponent]
})
.compileComponents();
fixture = TestBed.createComponent(HeaderContactLinksComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});