From 55f1af512dc39860875ddddaf99dd1fe2cb0d7ba Mon Sep 17 00:00:00 2001 From: AdamBtech <60339324+AdamBtech@users.noreply.github.com> Date: Mon, 2 Jun 2025 16:18:09 +0200 Subject: [PATCH] Refactored project page in smaller components --- .../chip-container.component.css | 0 .../chip-container.component.html | 1 - .../chip-container.component.ts | 9 - .../neural-profile-tree.component.html | 2 +- .../neural-profile-tree.component.ts | 16 +- .../operative-history.component.css | 1 + .../operative-history.component.html | 12 +- .../project-card/project-card.component.css | 468 ++++-------------- .../project-card/project-card.component.html | 80 +-- .../project-card/project-card.component.ts | 8 +- .../project-footer.component.css | 69 +++ .../project-footer.component.html | 4 + .../project-footer.component.ts | 28 ++ .../project-loader-decorator.component.css | 119 +++++ .../project-loader-decorator.component.html | 19 + .../project-loader-decorator.component.ts | 13 + .../project-modal/project-modal.component.css | 88 ++++ .../project-modal.component.html | 26 + .../project-modal/project-modal.component.ts | 36 ++ .../project-tree/project-tree.component.css | 46 ++ .../project-tree/project-tree.component.html | 11 + .../project-tree/project-tree.component.ts | 17 + .../projects-list/projects-list.component.css | 51 ++ .../projects-list.component.html | 8 + .../projects-list/projects-list.component.ts | 19 + src/app/pages/projects/projects.component.css | 367 +------------- .../pages/projects/projects.component.html | 81 +-- src/app/pages/projects/projects.component.ts | 52 +- 28 files changed, 764 insertions(+), 887 deletions(-) delete mode 100644 src/app/pages/about/chip-container/chip-container.component.css delete mode 100644 src/app/pages/about/chip-container/chip-container.component.html delete mode 100644 src/app/pages/about/chip-container/chip-container.component.ts create mode 100644 src/app/pages/projects/project-footer/project-footer.component.css create mode 100644 src/app/pages/projects/project-footer/project-footer.component.html create mode 100644 src/app/pages/projects/project-footer/project-footer.component.ts create mode 100644 src/app/pages/projects/project-loader-decorator/project-loader-decorator.component.css create mode 100644 src/app/pages/projects/project-loader-decorator/project-loader-decorator.component.html create mode 100644 src/app/pages/projects/project-loader-decorator/project-loader-decorator.component.ts create mode 100644 src/app/pages/projects/project-modal/project-modal.component.css create mode 100644 src/app/pages/projects/project-modal/project-modal.component.html create mode 100644 src/app/pages/projects/project-modal/project-modal.component.ts create mode 100644 src/app/pages/projects/project-tree/project-tree.component.css create mode 100644 src/app/pages/projects/project-tree/project-tree.component.html create mode 100644 src/app/pages/projects/project-tree/project-tree.component.ts create mode 100644 src/app/pages/projects/projects-list/projects-list.component.css create mode 100644 src/app/pages/projects/projects-list/projects-list.component.html create mode 100644 src/app/pages/projects/projects-list/projects-list.component.ts diff --git a/src/app/pages/about/chip-container/chip-container.component.css b/src/app/pages/about/chip-container/chip-container.component.css deleted file mode 100644 index e69de29..0000000 diff --git a/src/app/pages/about/chip-container/chip-container.component.html b/src/app/pages/about/chip-container/chip-container.component.html deleted file mode 100644 index 6322363..0000000 --- a/src/app/pages/about/chip-container/chip-container.component.html +++ /dev/null @@ -1 +0,0 @@ -

chip-container works!

diff --git a/src/app/pages/about/chip-container/chip-container.component.ts b/src/app/pages/about/chip-container/chip-container.component.ts deleted file mode 100644 index 434448e..0000000 --- a/src/app/pages/about/chip-container/chip-container.component.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Component } from '@angular/core'; - -@Component({ - selector: 'app-chip-container', - imports: [], - templateUrl: './chip-container.component.html', - styleUrl: './chip-container.component.css', -}) -export class ChipContainerComponent {} diff --git a/src/app/pages/about/neural-profile-tree/neural-profile-tree.component.html b/src/app/pages/about/neural-profile-tree/neural-profile-tree.component.html index 06f8369..8511c6a 100644 --- a/src/app/pages/about/neural-profile-tree/neural-profile-tree.component.html +++ b/src/app/pages/about/neural-profile-tree/neural-profile-tree.component.html @@ -71,7 +71,7 @@
- @for (tool of ['webstorm', 'git', 'docker']; track tool) { + @for (tool of ['webstorm', 'linux', 'git', 'docker']; track tool) {
INTERESTS
- GENRE: - Science Fiction + READINGS: + Science Fiction, Philosophy, Fantasy
- STUDY: - Philosophy + INTERESTS: + Digital & Tabletop Games, Hiking, Drawing
PETS: - 6 Guinea Pigs + Proud Dad of 6 Guinea Pigs (You read right!)
@@ -124,6 +124,8 @@ PERSISTENT ANALYTICAL CREATIVE + ADAPTIVE + SELF-LEARNER
diff --git a/src/app/pages/projects/project-card/project-card.component.css b/src/app/pages/projects/project-card/project-card.component.css index 8ff6aab..e607510 100644 --- a/src/app/pages/projects/project-card/project-card.component.css +++ b/src/app/pages/projects/project-card/project-card.component.css @@ -1,38 +1,35 @@ -/* Base styles with fade-in animations */ +/* Shared keyframes */ +@keyframes fadeSlideIn { + 0% { opacity: 0; transform: translateY(20px); } + 100% { opacity: 1; transform: translateY(0); } +} + +@keyframes fadeSlideLeft { + 0% { opacity: 0; transform: translateX(-20px); } + 100% { opacity: 1; transform: translateX(0); } +} + +@keyframes scaleIn { + 0% { opacity: 0; transform: scale(0.9); } + 100% { opacity: 1; transform: scale(1); } +} + +/* Base card animation */ .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; } -/* 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); - } + 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 */ +/* Scanning line effect */ .project-card::before { content: ''; position: absolute; @@ -40,35 +37,18 @@ left: -100%; width: 100%; height: 2px; - background: linear-gradient(90deg, - transparent, - var(--color-nier-accent), - transparent - ); + background: linear-gradient(90deg, transparent, var(--color-nier-accent), transparent); animation: scanLine 2s ease-out 0.2s forwards; z-index: 20; } @keyframes scanLine { - 0% { - left: -100%; - opacity: 0; - } - 50% { - opacity: 1; - } - 100% { - left: 100%; - opacity: 0; - } + 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 */ +/* Header animations */ .project-header { opacity: 0; transform: translateX(-30px); @@ -92,24 +72,17 @@ } } -/* Status text fade-in effect (reduced glitch) */ +/* Content animations */ .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); - } + 0% { opacity: 0; transform: translateX(-10px); } + 100% { opacity: 1; transform: translateX(0); } } -/* Title fade and scale */ .project-title { opacity: 0; transform: scale(0.9); @@ -134,29 +107,18 @@ } } -/* Body content staggered animation */ .project-body { opacity: 0; animation: bodyFadeUp 0.8s ease-out 0.9s forwards; } @keyframes bodyFadeUp { - 0% { - opacity: 0; - transform: translateY(15px); - } - 100% { - opacity: 1; - transform: translateY(0); - } + 0% { opacity: 0; transform: translateY(15px); } + 100% { opacity: 1; transform: translateY(0); } } +/* Detail animations */ .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; @@ -167,16 +129,11 @@ .project-detail:nth-child(3) { animation-delay: 1.3s; } @keyframes detailSlideIn { - 0% { - opacity: 0; - transform: translateX(-20px); - } - 100% { - opacity: 1; - transform: translateX(0); - } + 0% { opacity: 0; transform: translateX(-20px); } + 100% { opacity: 1; transform: translateX(0); } } +/* Detail styling */ .detail-label { font-weight: 500; color: var(--color-nier-accent); @@ -190,6 +147,25 @@ word-break: break-word; } +/* Tech stack animations */ +.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); + } +} + .tech-grid { display: flex; flex-wrap: wrap; @@ -205,11 +181,10 @@ 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-delay: 1.6s; } @keyframes techItemPop { @@ -229,36 +204,15 @@ } } -/* 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); - } -} - +/* Access section animations */ .access-section { - padding: 1rem 1.5rem; 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); border-top: 1px solid var(--color-nier-border); - display: flex; - gap: 1rem; - /* Animation */ + padding: 1rem 1.25rem; opacity: 0; transform: translateY(20px); animation: accessSectionRise 0.7s ease-out 1.8s forwards; @@ -281,19 +235,22 @@ } } +/* Button styling and animations */ .access-btn { background-color: transparent; border: 2px solid var(--color-nier-accent); color: var(--color-nier-accent); - padding: 0.5rem 1rem; + padding: 0.625rem 1.25rem; font-family: var(--font-terminal); font-size: 0.875rem; font-weight: 500; cursor: pointer; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); letter-spacing: 0.05em; - flex: 1; - /* Animation */ + min-height: 2.5rem; + display: flex; + align-items: center; + justify-content: center; opacity: 0; transform: scale(0.9); animation: buttonMaterialize 0.4s ease-out forwards; @@ -323,13 +280,26 @@ } } -.access-btn:hover { - background-color: var(--color-nier-accent); - color: var(--color-nier-text-light); - transform: translateY(-1px); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); +/* Desktop hover effects only */ +@media (hover: hover) and (pointer: fine) { + .access-btn:hover { + background-color: var(--color-nier-accent); + color: var(--color-nier-text-light); + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + } } +/* Mobile touch effects */ +@media (max-width: 640px) { + .access-btn:active { + background-color: var(--color-nier-accent); + color: var(--color-nier-text-light); + transform: scale(0.98); + } +} + +/* Click indicator */ .click-indicator { position: absolute; bottom: 0.5rem; @@ -340,55 +310,36 @@ 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); - } + 0% { opacity: 0; transform: translateY(5px); } + 100% { opacity: 0; transform: translateY(0); } } -/* Status Colors */ +.project-card:not(.redacted):hover .click-indicator { + opacity: 1; + transition: opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +/* 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 card styles */ .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); - } + 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); } } .project-card.redacted:hover { @@ -398,7 +349,7 @@ .redacted-background { position: relative; - min-height: 300px; + min-height: 240px; background-color: var(--color-nier-checkered-bg); background-size: 0.5rem 0.5rem; background-image: @@ -425,63 +376,38 @@ 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; } @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); - } + 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 */ +/* Glitch effects */ .static-overlay { opacity: 0; animation: staticFadeIn 0.8s ease-out 1.0s forwards; } @keyframes staticFadeIn { - 0% { - opacity: 0; - } - 100% { - opacity: 1; - } + 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); - } + 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 */ .glitch-layer { opacity: 0; animation: glitchLayerFlicker 0.1s ease-in-out forwards; @@ -496,189 +422,7 @@ 50% { opacity: 0.1; } } -/* Responsive Design */ -@media (max-width: 768px) { - .detail-label { - min-width: 100px; - } - - .access-section { - flex-direction: column; - } - - .access-btn { - flex: none; - } - - /* 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; - } -} - -/* Modal Animations */ -.modal-overlay { - opacity: 0; - animation: modalOverlayFadeIn 0.4s ease-out forwards; -} - -@keyframes modalOverlayFadeIn { - 0% { - opacity: 0; - } - 100% { - opacity: 1; - } -} - -.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); - } -} - -.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); - } -} - -.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); - } -} - -.modal-section { - opacity: 0; - transform: translateX(-10px); - animation: modalSectionSlideIn 0.3s 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-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); - } -} - -/* Modal exit animations */ -.modal-overlay.closing { - animation: modalOverlayFadeOut 0.3s ease-in forwards; -} - -@keyframes modalOverlayFadeOut { - 0% { - opacity: 1; - } - 100% { - opacity: 0; - } -} - -.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); - } -} +/* Accessibility */ @media (prefers-reduced-motion: reduce) { .project-card, .project-header, diff --git a/src/app/pages/projects/project-card/project-card.component.html b/src/app/pages/projects/project-card/project-card.component.html index a42f277..31f1095 100644 --- a/src/app/pages/projects/project-card/project-card.component.html +++ b/src/app/pages/projects/project-card/project-card.component.html @@ -1,4 +1,4 @@ -
@if (!project().isRedacted) { -
+
-
{{ project().status }}
-
+
{{ project().title }}
-
+
- CLASSIFICATION: - {{ project().classification }} + CLASSIFICATION: + {{ project().classification }}
- OBJECTIVE: - {{ project().objective }} + OBJECTIVE: + {{ project().objective }}
- STATUS: - {{ project().statusDescription }} + STATUS: + {{ project().statusDescription }}
- @if (project().techStack && project().techStack.length > 0) { -
-
TECH_STACK:
-
- @for (tech of project().techStack; track tech; let i = $index) { - {{ tech }} - } + @if (project().techStack && project().techStack.length > 0) { +
+
TECH_STACK:
+
+ @for (tech of project().techStack; track tech; let i = $index) { + {{ tech }} + } +
-
- } + }
@if (project().demoUrl || project().codeUrl) { -
+
@if (project().demoUrl) { - } @if (project().codeUrl) { - +
+ + + +
+
+} diff --git a/src/app/pages/projects/project-modal/project-modal.component.ts b/src/app/pages/projects/project-modal/project-modal.component.ts new file mode 100644 index 0000000..17d7745 --- /dev/null +++ b/src/app/pages/projects/project-modal/project-modal.component.ts @@ -0,0 +1,36 @@ +import { Component, effect, input, output } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { type Project } from '../../../shared/models/project.model'; + +@Component({ + selector: 'app-project-modal', + imports: [CommonModule], + templateUrl: './project-modal.component.html', + styleUrl: './project-modal.component.css', +}) +export class ProjectModalComponent { + project = input(null); + closeModal = output(); + constructor() { + effect(() => { + if (this.project()) { + // Modal is open - disable scroll + document.body.style.overflow = 'hidden'; + } else { + // Modal is closed - restore scroll + document.body.style.overflow = 'auto'; + } + }); + } + + onClose(): void { + this.closeModal.emit(); + } + + onOverlayClick(event: MouseEvent): void { + // Close modal only if clicking on the overlay, not the modal content + if (event.target === event.currentTarget) { + this.onClose(); + } + } +} diff --git a/src/app/pages/projects/project-tree/project-tree.component.css b/src/app/pages/projects/project-tree/project-tree.component.css new file mode 100644 index 0000000..0d18628 --- /dev/null +++ b/src/app/pages/projects/project-tree/project-tree.component.css @@ -0,0 +1,46 @@ +.directory-tree { + opacity: 0; + animation: slideInUp 0.8s ease-out 0.4s forwards; +} + +.directory-header { + opacity: 0; + animation: slideInLeft 0.5s ease-out 1.0s forwards; +} + +.animate-fade-in-line { + opacity: 0; + animation: slideInLeft 0.3s ease-out forwards; +} + +/* Define the keyframe animations */ +@keyframes slideInUp { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes slideInLeft { + from { + opacity: 0; + transform: translateX(-20px); + } + to { + opacity: 1; + transform: translateX(0); + } +} + +@keyframes scanLine { + 0%, 100% { + transform: translateX(-100%); + } + 50% { + transform: translateX(100%); + } +} diff --git a/src/app/pages/projects/project-tree/project-tree.component.html b/src/app/pages/projects/project-tree/project-tree.component.html new file mode 100644 index 0000000..6a5f68a --- /dev/null +++ b/src/app/pages/projects/project-tree/project-tree.component.html @@ -0,0 +1,11 @@ +
+
SYSTEM DIRECTORY
+
+ @for (line of directoryLines(); track $index) { +
+ {{ line }} +
+ } +
+
diff --git a/src/app/pages/projects/project-tree/project-tree.component.ts b/src/app/pages/projects/project-tree/project-tree.component.ts new file mode 100644 index 0000000..f4a8fae --- /dev/null +++ b/src/app/pages/projects/project-tree/project-tree.component.ts @@ -0,0 +1,17 @@ +import { Component, signal } from '@angular/core'; + +@Component({ + selector: 'app-project-tree', + imports: [], + templateUrl: './project-tree.component.html', + styleUrl: './project-tree.component.css', +}) +export class ProjectTreeComponent { + directoryLines = signal([ + 'EXECUTE//DIRECTORY/', + '├── WEB_APPLICATIONS/', + '│ ├── portfolio_system.exe', + '│ └── [CLASSIFIED].exe', + '└── EXPERIMENTAL_PROJECTS/', + ]); +} diff --git a/src/app/pages/projects/projects-list/projects-list.component.css b/src/app/pages/projects/projects-list/projects-list.component.css new file mode 100644 index 0000000..a8f01de --- /dev/null +++ b/src/app/pages/projects/projects-list/projects-list.component.css @@ -0,0 +1,51 @@ +/* projects-list.component.css */ + +/* ===================== GRID LAYOUT ===================== */ + +.grid-project-cards { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); + gap: 1.5rem; +} + +/* Projects grid animation */ +.projects-grid { + opacity: 0; + animation: slideInUp 0.8s ease-out 0.6s forwards; +} + +/* ===================== ANIMATIONS ===================== */ + +@keyframes slideInUp { + from { + opacity: 0; + transform: translate3d(0, 30px, 0); + } + to { + opacity: 1; + transform: translate3d(0, 0, 0); + } +} + +/* ===================== RESPONSIVE DESIGN ===================== */ + +@media (max-width: 768px) { + .grid-project-cards { + grid-template-columns: 1fr; + gap: 1rem; + } + + /* Faster animations on mobile */ + .projects-grid { + animation-duration: 0.6s; + } +} + +/* ===================== ACCESSIBILITY ===================== */ + +@media (prefers-reduced-motion: reduce) { + .projects-grid { + animation: none !important; + opacity: 1 !important; + } +} diff --git a/src/app/pages/projects/projects-list/projects-list.component.html b/src/app/pages/projects/projects-list/projects-list.component.html new file mode 100644 index 0000000..db54894 --- /dev/null +++ b/src/app/pages/projects/projects-list/projects-list.component.html @@ -0,0 +1,8 @@ + +
+ @for (project of projects(); track project.id) { + + } +
diff --git a/src/app/pages/projects/projects-list/projects-list.component.ts b/src/app/pages/projects/projects-list/projects-list.component.ts new file mode 100644 index 0000000..f4e6da6 --- /dev/null +++ b/src/app/pages/projects/projects-list/projects-list.component.ts @@ -0,0 +1,19 @@ +// projects-list.component.ts +import { Component, input, output } from '@angular/core'; +import { ProjectCardComponent } from '../project-card/project-card.component'; +import { type Project } from '../../../shared/models/project.model'; + +@Component({ + selector: 'app-project-list', + imports: [ProjectCardComponent], + templateUrl: './projects-list.component.html', + styleUrl: './projects-list.component.css', +}) +export class ProjectListComponent { + projects = input.required(); + caseStudyOpen = output(); + + onCaseStudyOpen(project: Project): void { + this.caseStudyOpen.emit(project); + } +} diff --git a/src/app/pages/projects/projects.component.css b/src/app/pages/projects/projects.component.css index e212ffb..57e39c9 100644 --- a/src/app/pages/projects/projects.component.css +++ b/src/app/pages/projects/projects.component.css @@ -12,17 +12,6 @@ } } -@keyframes slideInUp { - from { - opacity: 0; - transform: translate3d(0, 30px, 0); - } - to { - opacity: 1; - transform: translate3d(0, 0, 0); - } -} - @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } @@ -56,80 +45,18 @@ animation: fadeIn 0.8s ease-out forwards; } -/* ===================== LAYOUT ===================== */ - -.grid-project-cards { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); - gap: 1.5rem; -} - -@media (max-width: 768px) { - .grid-project-cards { - grid-template-columns: 1fr; - } -} - /* ===================== PAGE ELEMENTS ===================== */ -/* Section title - no animation */ +/* Section title - static */ .section-title-container { opacity: 1; } -/* Terminal header */ -.terminal-header { - opacity: 0; - animation: slideInUp 0.8s ease-out 0.3s forwards; -} - -.terminal-line { - opacity: 0; - animation: slideInLeft 0.4s ease-out forwards; -} - -.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; } - -/* Progress and directory */ -.progress-container { - opacity: 0; - animation: scaleIn 0.6s ease-out 0.8s forwards; -} - -.directory-tree { - opacity: 0; - animation: slideInUp 0.8s ease-out 0.4s forwards; -} - -.directory-header { - opacity: 0; - animation: slideInLeft 0.5s ease-out 1.0s forwards; -} - -.animate-fade-in-line { - opacity: 0; - animation: slideInLeft 0.3s ease-out forwards; -} - -.projects-grid { - opacity: 0; - animation: slideInUp 0.8s ease-out 0.6s forwards; -} - -.directory-footer { - opacity: 0; - animation: slideInUp 0.6s ease-out 1.2s forwards; -} - -/* Enhanced scan for terminal */ -.enhanced-scan { - background: linear-gradient(90deg, transparent, rgba(255, 201, 102, 0.3), transparent); - animation: scanLine 3s ease-in-out infinite; -} - /* Background pattern - static */ +.checkered-background { + position: relative; +} + .checkered-background::before { content: ''; position: absolute; @@ -143,284 +70,7 @@ background-size: 20px 20px; background-position: 0 0, 0 10px, 10px -10px, -10px 0px; pointer-events: none; -} - -/* ===================== PROJECT CARDS ===================== */ - -.project-card { - min-height: 300px; - cursor: pointer; - opacity: 0; - animation: slideInUp 0.8s ease-out forwards; -} - -.project-card::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 0.2s forwards; - z-index: 20; -} - -.project-header { - opacity: 0; - animation: slideInLeft 0.5s ease-out 0.2s forwards; -} - -.project-status, -.project-title, -.project-body { - opacity: 0; - animation: fadeIn 0.4s ease-out forwards; -} - -.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; - opacity: 0; - animation: slideInLeft 0.3s ease-out forwards; -} - -.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; - color: var(--color-nier-accent); - min-width: 120px; - margin-right: 1rem; - flex-shrink: 0; -} - -.detail-value { - flex: 1; - word-break: break-word; -} - -.tech-grid { - display: flex; - flex-wrap: wrap; - gap: 0.5rem; -} - -.tech-item { - background-color: var(--color-nier-mid); - color: var(--color-nier-text-dark); - border: 1px solid var(--color-nier-border); - padding: 0.375rem 0.75rem; - font-size: 0.75rem; - font-weight: 500; - letter-spacing: 0.05em; - border-radius: 2px; - opacity: 0; - animation: scaleIn 0.2s ease-out forwards; - animation-delay: 1.2s; -} - -.tech-stack { - opacity: 0; - animation: slideInUp 0.4s ease-out 1.1s forwards; -} - -.access-section { - padding: 1rem 1.5rem; - 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, 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; - opacity: 0; - animation: slideInUp 0.5s ease-out 1.4s forwards; -} - -.access-btn { - background-color: transparent; - border: 2px solid var(--color-nier-accent); - color: var(--color-nier-accent); - padding: 0.5rem 1rem; - font-family: var(--font-terminal); - font-size: 0.875rem; - font-weight: 500; - cursor: pointer; - transition: all 0.3s ease; - letter-spacing: 0.05em; - flex: 1; - opacity: 0; - animation: scaleIn 0.3s ease-out forwards; -} - -.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); - color: var(--color-nier-text-light); - transform: translateY(-1px); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); -} - -.click-indicator { - position: absolute; - bottom: 0.5rem; - right: 1rem; - font-size: 0.7rem; - color: var(--color-nier-mid); - font-family: var(--font-terminal-retro); - opacity: 0; - transition: opacity 0.3s ease; - pointer-events: none; -} - -.project-card:not(.redacted):hover .click-indicator { - opacity: 1; -} - -/* 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 cards */ -.project-card.redacted { - cursor: not-allowed; - animation: slideInUp 1s ease-out forwards; -} - -.project-card.redacted:hover { - transform: none; - border-color: var(--color-nier-border); -} - -.redacted-background { - position: relative; - min-height: 300px; - background-color: var(--color-nier-checkered-bg); - background-size: 0.5rem 0.5rem; - background-image: - linear-gradient(45deg, var(--color-nier-mid) 25%, transparent 25%), - linear-gradient(-45deg, var(--color-nier-mid) 25%, transparent 25%), - linear-gradient(45deg, transparent 75%, var(--color-nier-mid) 75%), - linear-gradient(-45deg, transparent 75%, var(--color-nier-mid) 75%); -} - -.redacted-content { - filter: blur(1px); - user-select: none; - opacity: 0.6; -} - -.redacted-overlay { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - background-color: var(--color-nier-accent); - color: var(--color-nier-text-light); - padding: 1.5rem 2rem; - border: 2px solid var(--color-nier-text-light); - text-align: center; - z-index: 10; - opacity: 0; - animation: scaleIn 0.4s ease-out 0.8s forwards; -} - -.static-overlay, -.corner-accent, -.glitch-layer { - opacity: 0; - animation: fadeIn 0.5s ease-out 0.6s forwards; -} - -/* ===================== MODALS ===================== */ - -.modal-overlay { - opacity: 0; - animation: fadeIn 0.3s ease-out forwards; -} - -.modal-container { - opacity: 0; - transform: scale(0.95) translateY(-20px); - animation: scaleIn 0.4s ease-out 0.1s forwards; -} - -.modal-header { - opacity: 0; - animation: slideInUp 0.3s ease-out 0.2s forwards; -} - -.modal-content { - opacity: 0; - animation: slideInUp 0.4s ease-out 0.3s forwards; -} - -.modal-section { - opacity: 0; - animation: slideInLeft 0.2s ease-out forwards; -} - -.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; - animation: scaleIn 0.2s ease-out 0.4s forwards; -} - -/* Modal exit animations */ -.modal-overlay.closing { - animation: fadeIn 0.2s ease-in reverse forwards; -} - -.modal-container.closing { - animation: scaleIn 0.2s ease-in reverse forwards; -} - -/* ===================== RESPONSIVE DESIGN ===================== */ - -@media (max-width: 768px) { - .detail-label { - min-width: 100px; - } - - .access-section { - flex-direction: column; - } - - .access-btn { - flex: none; - } - - /* Faster animations on mobile */ - .project-card, - .terminal-header, - .directory-tree, - .projects-grid { - animation-duration: 0.6s; - } - - .terminal-line, - .project-detail { - animation-duration: 0.3s; - } + z-index: -1; } /* ===================== ACCESSIBILITY ===================== */ @@ -435,8 +85,7 @@ } .animate-scan, - .enhanced-scan, - .project-card::before { - display: none !important; + .checkered-background::before { + animation: none !important; } } diff --git a/src/app/pages/projects/projects.component.html b/src/app/pages/projects/projects.component.html index 1defc54..e827eeb 100644 --- a/src/app/pages/projects/projects.component.html +++ b/src/app/pages/projects/projects.component.html @@ -1,4 +1,4 @@ - +
@@ -6,81 +6,22 @@
- -
- -
- -
>> ACCESSING PROJECT DATABASE...
- - -
-
-
-
-
- -
>> CONNECTION ESTABLISHED
-
>> DISPLAYING ARCHIVED MISSIONS
-
+ -
-
SYSTEM DIRECTORY
-
- @for (line of directoryLines(); track $index) { -
- {{ line }} -
- } -
-
+ - -
- @for (project of projects(); track project.id) { - - } -
+ + - - +
- - @if (selectedProject()) { - - }
diff --git a/src/app/pages/projects/projects.component.ts b/src/app/pages/projects/projects.component.ts index b84a5d8..d793a49 100644 --- a/src/app/pages/projects/projects.component.ts +++ b/src/app/pages/projects/projects.component.ts @@ -1,13 +1,23 @@ -// project-list.component.ts -import { Component, signal, computed, effect, OnInit } from '@angular/core'; -import { ProjectCardComponent } from './project-card/project-card.component'; +// projects.component.ts +import { Component, signal, OnInit } from '@angular/core'; import { SectionTitleComponent } from '../../components/section-title/section-title.component'; import { type Project } from '../../shared/models/project.model'; +import { ProjectTreeComponent } from './project-tree/project-tree.component'; +import { ProjectModalComponent } from './project-modal/project-modal.component'; +import { ProjectFooterComponent } from './project-footer/project-footer.component'; +import { ProjectLoaderDecoratorComponent } from './project-loader-decorator/project-loader-decorator.component'; +import { ProjectListComponent } from './projects-list/projects-list.component'; @Component({ selector: 'app-projects', - standalone: true, - imports: [ProjectCardComponent, SectionTitleComponent], + imports: [ + SectionTitleComponent, + ProjectTreeComponent, + ProjectModalComponent, + ProjectFooterComponent, + ProjectLoaderDecoratorComponent, + ProjectListComponent, + ], templateUrl: './projects.component.html', styleUrl: './projects.component.css', }) @@ -17,33 +27,6 @@ export class ProjectsComponent implements OnInit { selectedProject = signal(null); isLoading = signal(false); - directoryLines = signal([ - 'EXECUTE//DIRECTORY/', - '├── WEB_APPLICATIONS/', - '│ ├── portfolio_system.exe', - '│ └── [CLASSIFIED].exe', - '└── EXPERIMENTAL_PROJECTS/', - ]); - - // Computed signals - totalProjects = computed(() => this.projects().length); - activeProjects = computed( - () => - this.projects().filter( - (p) => !p.isRedacted && p.status === '[MISSION_ACTIVE]', - ).length, - ); - redactedProjects = computed( - () => this.projects().filter((p) => p.isRedacted).length, - ); - - // Effect to handle body scroll when modal opens/closes - constructor() { - effect(() => { - document.body.style.overflow = this.selectedProject() ? 'hidden' : 'auto'; - }); - } - ngOnInit(): void { this.loadProjects(); this.startLoadingAnimation(); @@ -160,10 +143,7 @@ export class ProjectsComponent implements OnInit { this.selectedProject.set(project); } - closeCaseStudy(event?: MouseEvent): void { - if (event && event.target !== event.currentTarget) { - return; - } + closeCaseStudy(): void { this.selectedProject.set(null); } }