About page responsive and cells animation done

This commit is contained in:
AdamBtech
2025-05-26 11:06:08 +02:00
parent 06bd75fb39
commit 67dc4736c3
4 changed files with 455 additions and 69 deletions

View File

@@ -1,18 +1,57 @@
<section class="border-b-1 h-screen checkered-bg p-6 flex flex-col"> <section class="border-b border-nier-border min-h-screen checkered-background p-6 lg:p-8 flex flex-col relative">
<app-section-title title="NEURAL PROFILE" />
<div class="flex-1 flex items-start justify-between gap-6 mt-6"> <!-- Header section with NieR styling -->
<!-- Left side - Neural Profile Tree with NieR animations --> <div class="mb-6 lg:mb-8">
<div class="flex-shrink-0"> <app-section-title title="NEURAL PROFILE" />
<app-neural-profile-tree /> <!-- Subtle divider line -->
<div class="mt-3 h-px bg-nier-border opacity-50"></div>
</div>
<!-- Main content grid -->
<div class="flex-1 grid grid-cols-1 lg:grid-cols-12 gap-6 lg:gap-8">
<!-- Left panel - Neural Profile Tree -->
<div class="lg:col-span-7 xl:col-span-6">
<div class="h-full bg-nier-bg border-2 border-nier-border font-terminal text-nier-dark">
<!-- Inner content container with proper padding -->
<div class="h-full p-4 lg:p-6">
<app-neural-profile-tree />
</div>
</div>
</div> </div>
<!-- Right side - Video container (hidden on mobile) --> <!-- Right panel - Video and status (responsive) -->
<div class="hidden md:flex flex-1 items-center justify-end"> <div class="lg:col-span-5 xl:col-span-6 flex flex-col gap-4 lg:gap-6">
<div class="w-4/5 h-96 lg:h-[28rem] overflow-hidden">
<app-holo-video-container <!-- Main video display -->
containerClasses="w-full h-full" <div class="flex-1 bg-nier-dark border-2 border-nier-accent">
videoSrc="cyber_skull.mp4" /> <div class="h-full min-h-[250px] sm:min-h-[300px] lg:min-h-[400px] xl:min-h-[500px] p-2">
<div class="w-full h-full bg-nier-bg/10 border border-nier-border/50">
<app-holo-video-container
containerClasses="w-full h-full"
videoSrc="cyber_skull.mp4" />
</div>
</div>
</div>
<!-- Status panel -->
<div class="bg-nier-mid border-2 border-nier-border font-terminal">
<div class="p-3 sm:p-4 lg:p-6">
<!-- Status grid - responsive layout -->
<div class="nier-grid grid-cols-1 sm:grid-cols-2 gap-3 sm:gap-4 p-3">
<div class="text-nier-dark">
<div class="text-xs font-terminal-retro tracking-wider opacity-60 mb-1">STATUS</div>
<div class="flex items-center gap-2 font-terminal text-sm">
<div class="w-2 h-2 bg-nier-accent animate-pulse"></div>
<span>NEURAL SCAN ACTIVE</span>
</div>
</div>
<div class="text-nier-dark">
<div class="text-xs font-terminal-retro tracking-wider opacity-60 mb-1">MODE</div>
<div class="font-terminal text-sm">REAL-TIME ANALYSIS</div>
</div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -57,7 +57,157 @@
animation: nier-button-scan 0.3s ease forwards; animation: nier-button-scan 0.3s ease forwards;
} }
/* Text glitch animation for tree nodes */ /* Active/click state for tree nodes */
.tree-node-item:active {
transform: scale(0.98);
}
/* NieR-style Chip Container System - NO INTERACTIONS */
.chip-container {
position: relative;
}
.chip-parent {
position: relative;
padding: 16px;
border-radius: 0;
border: 1px solid rgba(0, 0, 0, 0.15); /* Very thin border */
box-shadow: none;
overflow: hidden;
transition: transform 0.2s ease; /* Allow parent scaling */
}
/* Parent Chip Colors - Complete NieR Earth Palette */
.tools-parent {
background: linear-gradient(135deg, #a67c63 0%, #8b6b52 100%);
}
.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%);
border: none;
cursor: not-allowed;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.chip-locked-category {
opacity: 0.7;
flex: 1;
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;
justify-content: center;
height: 100%;
width: 100%;
}
.lock-icon {
width: 40px;
height: 40px;
color: #b0b0b0;
stroke-width: 1.5;
opacity: 0.8;
filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.3));
}
/* Individual Chip Cells - Keep original grayish overlay */
.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 */
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 */
}
.chip-cell::before {
content: '';
position: absolute;
top: 1px;
left: 1px;
right: 1px;
height: 4px;
background: rgba(255, 255, 255, 0.1); /* Keep original highlight */
border-radius: 0;
}
/* Scale animation when tree node is hovered OR selected */
.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 */
.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 */
transform: none !important;
filter: none !important;
opacity: 1 !important;
animation: none !important;
transition: none !important;
}
.chip-cell:hover::before,
.chip-cell:focus::before,
.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 */
height: 4px !important;
box-shadow: none !important;
}
/* NO CONTAINER EFFECTS */
.chip-normal,
.chip-selected,
.chip-hovered {
/* No effects */
}
/* Text glitch effect for tree nodes only */
@keyframes nier-text-glitch { @keyframes nier-text-glitch {
0%, 100% { 0%, 100% {
transform: translateX(0); transform: translateX(0);
@@ -86,7 +236,7 @@
} }
} }
/* The scan line animation */ /* Scan line animation for tree nodes only */
@keyframes nier-button-scan { @keyframes nier-button-scan {
0% { 0% {
opacity: 0.5; opacity: 0.5;
@@ -98,12 +248,22 @@
} }
} }
/* Active/click state for tree nodes */ /* Responsive Adjustments */
.tree-node-item:active { @media (max-width: 768px) {
transform: scale(0.98); .chip-cell {
height: 12px;
}
.chip-cells-column {
gap: 2px;
}
.chip-parent {
padding: 12px;
}
} }
/* Touch device optimization */ /* Touch device optimization for tree nodes only */
@media (hover: none) { @media (hover: none) {
.tree-node-item:active::before { .tree-node-item:active::before {
width: 100%; width: 100%;

View File

@@ -1,52 +1,155 @@
<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"> <div class="flex gap-6 h-auto">
@for (node of treeData(); track node.id) { <!-- Tree Component -->
<div <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 h-auto">
class="border-l-2 border-transparent" @for (node of treeData(); track node.id) {
[class.border-nier-accent]="node.level >= 2"
[class.hidden]="!node.visible"
>
<div <div
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="border-l-2 border-transparent"
[class.font-bold]="node.isSelected" [class.border-nier-accent]="node.level >= 2"
[style.padding-left]="getIndentPadding(node.level)" [class.hidden]="!node.visible"
[attr.data-label]="node.title"
(click)="selectNode(node)"
> >
<!-- Selection indicator --> <div
@if (node.isSelected) { 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"
<div class="absolute left-0 top-0 bottom-0 w-1 bg-nier-accent"></div>
}
<!-- Expand/Collapse Icon -->
<span
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"
(click)="$event.stopPropagation(); toggleExpand(node)"
>
@if (node.hasChildren) {
{{ node.isExpanded ? '▼' : '▶' }}
} @else {
@switch (node.level) {
@case (2) { ├ }
@case (3) {
@if (isLastChild(node)) { └ } @else { ├ }
}
@default { + }
}
}
</span>
<!-- Node Title with NieR effects -->
<span
class="flex-1 text-nier-dark select-none relative z-10"
[class.font-bold]="node.isSelected" [class.font-bold]="node.isSelected"
[style.padding-left]="getIndentPadding(node.level)"
[attr.data-label]="node.title"
(click)="selectNode(node)"
(mouseenter)="hoverNode(node)"
(mouseleave)="clearHover()"
> >
{{ node.title }} <!-- Selection indicator -->
</span> @if (node.isSelected) {
<div class="absolute left-0 top-0 bottom-0 w-1 bg-nier-accent"></div>
}
<!-- Scan line effect --> <!-- Expand/Collapse Icon -->
<span class="scan-line"></span> <span
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"
(click)="$event.stopPropagation(); toggleExpand(node)"
>
@if (node.hasChildren) {
{{ node.isExpanded ? '▼' : '▶' }}
} @else {
@switch (node.level) {
@case (2) { ├ }
@case (3) {
@if (isLastChild(node)) { └ } @else { ├ }
}
@default { + }
}
}
</span>
<!-- Node Title with NieR effects -->
<span
class="flex-1 text-nier-dark select-none relative z-10"
[class.font-bold]="node.isSelected"
>
{{ node.title }}
</span>
<!-- Scan line effect -->
<span class="scan-line"></span>
</div>
</div>
}
</div>
<!-- Chip Slots Container -->
<article class="hidden md:flex flex-col bg-nier-bg border-2 border-nier-border w-80 h-auto p-6 gap-4 self-stretch">
<!-- Tools Chip -->
<div class="chip-container" [ngClass]="'chip-' + getVisualState('tools')">
<div class="chip-parent tools-parent">
<div class="chip-cells-column">
@for (tool of ['webstorm', 'git', 'docker']; track tool) {
<div
class="chip-cell"
[class.cell-hovered]="hoveredNodeId() === tool"
[class.cell-selected]="selectedNodeId() === tool"
(click)="selectToolChild(tool)"
(mouseenter)="hoverChipChild(tool)"
(mouseleave)="clearHover()"
>
<span class="scan-line"></span>
</div>
}
</div>
</div> </div>
</div> </div>
}
<!-- Frontend Chip -->
<div class="chip-container" [ngClass]="'chip-' + getVisualState('front')">
<div class="chip-parent frontend-parent">
<div class="chip-cells-column">
@for (tech of ['angular', 'tailwind', 'html-css']; track tech) {
<div
class="chip-cell"
[class.cell-hovered]="hoveredNodeId() === tech"
[class.cell-selected]="selectedNodeId() === tech"
(click)="selectToolChild(tech)"
(mouseenter)="hoverChipChild(tech)"
(mouseleave)="clearHover()"
>
<span class="scan-line"></span>
</div>
}
</div>
</div>
</div>
<!-- Backend Chip -->
<div class="chip-container" [ngClass]="'chip-' + getVisualState('back')">
<div class="chip-parent backend-parent">
<div class="chip-cells-column">
@for (lang of ['nodejs', 'typescript', 'php', 'python']; track lang) {
<div
class="chip-cell"
[class.cell-hovered]="hoveredNodeId() === lang"
[class.cell-selected]="selectedNodeId() === lang"
(click)="selectToolChild(lang)"
(mouseenter)="hoverChipChild(lang)"
(mouseleave)="clearHover()"
>
<span class="scan-line"></span>
</div>
}
</div>
</div>
</div>
<!-- Database Chip -->
<div class="chip-container" [ngClass]="'chip-' + getVisualState('data')">
<div class="chip-parent database-parent">
<div class="chip-cells-column">
@for (db of ['postgresql', 'mongodb']; track db) {
<div
class="chip-cell"
[class.cell-hovered]="hoveredNodeId() === db"
[class.cell-selected]="selectedNodeId() === db"
(click)="selectToolChild(db)"
(mouseenter)="hoverChipChild(db)"
(mouseleave)="clearHover()"
>
<span class="scan-line"></span>
</div>
}
</div>
</div>
</div>
<!-- Locked Category -->
<div class="chip-container chip-locked-category">
<div class="chip-parent locked-parent">
<div class="locked-content">
<svg class="lock-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"/>
<circle cx="12" cy="12" r="3"/>
<path d="M7 11V7a5 5 0 0 1 10 0v4"/>
</svg>
</div>
</div>
</div>
</article>
</div> </div>

View File

@@ -23,11 +23,13 @@ export class NeuralProfileTreeComponent {
// Input for external data (optional) // Input for external data (optional)
externalData = input<NeuralProfileNode[]>([]); externalData = input<NeuralProfileNode[]>([]);
// Internal tree state // Internal tree state and interaction signals
private treeState = signal<NeuralProfileNode[]>(this.getInitialData()); private treeState = signal<NeuralProfileNode[]>(this.getInitialData());
public hoveredNodeId = signal<string | null>(null);
public selectedNodeId = signal<string | null>(null);
// Computed flattened tree for rendering // Computed flattened tree for rendering
treeData = computed(() => this.flattenTree(this.treeState())); treeData = computed(() => this.flattenTreeWithVisibility(this.treeState()));
private getInitialData(): NeuralProfileNode[] { private getInitialData(): NeuralProfileNode[] {
return [ return [
@@ -208,19 +210,29 @@ export class NeuralProfileTreeComponent {
]; ];
} }
private flattenTree(nodes: NeuralProfileNode[]): NeuralProfileNode[] { private flattenTreeWithVisibility(
nodes: NeuralProfileNode[],
): NeuralProfileNode[] {
const result: NeuralProfileNode[] = []; const result: NeuralProfileNode[] = [];
const traverse = (nodes: NeuralProfileNode[]) => { const traverse = (
nodes: NeuralProfileNode[],
parentVisible: boolean = true,
) => {
for (const node of nodes) { for (const node of nodes) {
// Node is visible if parent is visible
node.visible = parentVisible;
result.push(node); result.push(node);
if (node.isExpanded && node.children) {
traverse(node.children); // Only traverse children if this node is expanded AND visible
if (node.children) {
const childrenVisible = parentVisible && node.isExpanded;
traverse(node.children, childrenVisible);
} }
} }
}; };
traverse(nodes); traverse(nodes, true); // Root is always visible
return result; return result;
} }
@@ -251,10 +263,82 @@ export class NeuralProfileTreeComponent {
// Set current node as selected // Set current node as selected
this.updateNodeInTree(newState, node.id, { isSelected: true }); this.updateNodeInTree(newState, node.id, { isSelected: true });
// Update selected node ID for visual representation
this.selectedNodeId.set(node.id);
return newState; return newState;
}); });
} }
// NEW METHOD: Handle chip cell hover
hoverChipChild(childId: string): void {
this.hoveredNodeId.set(childId);
}
selectToolChild(childId: string): void {
// Find the node in the tree and select it
this.treeState.update((state) => {
const newState = JSON.parse(JSON.stringify(state));
// Clear all selections first
this.clearAllSelections(newState);
// Find and select the specific child node
this.updateNodeInTree(newState, childId, { isSelected: true });
// Update the signal for visual state
this.selectedNodeId.set(childId);
return newState;
});
}
hoverNode(node: NeuralProfileNode): void {
this.hoveredNodeId.set(node.id);
}
clearHover(): void {
this.hoveredNodeId.set(null);
}
getVisualState(sectionId: string): 'normal' | 'selected' | 'hovered' {
const hoveredId = this.hoveredNodeId();
const selectedId = this.selectedNodeId();
// Map node IDs to their corresponding visual sections
const nodeToSection: { [key: string]: string } = {
workstation: 'workstation',
tools: 'tools',
webstorm: 'tools',
git: 'tools',
docker: 'tools',
dev: 'dev',
front: 'front',
angular: 'front',
tailwind: 'front',
'html-css': 'front',
back: 'back',
nodejs: 'back',
typescript: 'back',
php: 'back',
python: 'back',
data: 'data',
postgresql: 'data',
mongodb: 'data',
};
// Check if any hovered node belongs to this section
if (hoveredId && nodeToSection[hoveredId] === sectionId) {
return 'hovered';
}
// Check if any selected node belongs to this section
if (selectedId && nodeToSection[selectedId] === sectionId) {
return 'selected';
}
return 'normal';
}
private updateNodeInTree( private updateNodeInTree(
nodes: NeuralProfileNode[], nodes: NeuralProfileNode[],
id: string, id: string,