mirror of
https://github.com/adam-benyekkou/my_portfolio.git
synced 2026-01-15 20:20:09 +00:00
Refactoring tech stack into a reusable component
This commit is contained in:
@@ -125,65 +125,6 @@
|
|||||||
text-shadow: 0 1px 2px rgba(0,0,0,0.1);
|
text-shadow: 0 1px 2px rgba(0,0,0,0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Tech Stack Section */
|
|
||||||
.tech-section {
|
|
||||||
margin-top: var(--card-gap);
|
|
||||||
padding-top: var(--card-gap);
|
|
||||||
border-top: 1px solid var(--color-nier-border);
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tech-section::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 0;
|
|
||||||
height: 1px;
|
|
||||||
background: var(--color-nier-accent);
|
|
||||||
animation: expandLine 0.6s ease-out calc(var(--animation-delay-base) * 12) forwards;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tech-grid {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 0.25rem;
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Tech Items */
|
|
||||||
.tech-item {
|
|
||||||
background: linear-gradient(135deg, var(--color-nier-accent) 0%, var(--color-nier-highlight) 100%);
|
|
||||||
color: var(--color-nier-text-light);
|
|
||||||
padding: 0.25rem 0.5rem;
|
|
||||||
font-size: 0.7rem;
|
|
||||||
font-family: var(--font-terminal);
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.05em;
|
|
||||||
border-radius: 3px;
|
|
||||||
white-space: nowrap;
|
|
||||||
transition: var(--transition-smooth);
|
|
||||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
||||||
|
|
||||||
/* Initial state for staggered animation */
|
|
||||||
opacity: 0;
|
|
||||||
transform: scale(0.8) translateY(10px);
|
|
||||||
animation: techItemFadeIn 0.4s ease-out forwards;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Staggered animation delays for tech items */
|
|
||||||
.tech-item:nth-child(1) { animation-delay: calc(var(--animation-delay-base) * 13); }
|
|
||||||
.tech-item:nth-child(2) { animation-delay: calc(var(--animation-delay-base) * 14); }
|
|
||||||
.tech-item:nth-child(3) { animation-delay: calc(var(--animation-delay-base) * 15); }
|
|
||||||
.tech-item:nth-child(4) { animation-delay: calc(var(--animation-delay-base) * 16); }
|
|
||||||
.tech-item:nth-child(5) { animation-delay: calc(var(--animation-delay-base) * 17); }
|
|
||||||
.tech-item:nth-child(n+6) { animation-delay: calc(var(--animation-delay-base) * 18); }
|
|
||||||
|
|
||||||
.tech-item:hover {
|
|
||||||
transform: translateY(-2px) scale(1.05);
|
|
||||||
box-shadow: 0 6px 12px rgba(0,0,0,0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Status Badges */
|
/* Status Badges */
|
||||||
.current-status {
|
.current-status {
|
||||||
padding: 0.375rem 0.75rem;
|
padding: 0.375rem 0.75rem;
|
||||||
@@ -392,38 +333,14 @@
|
|||||||
margin-bottom: 0.25rem;
|
margin-bottom: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-block {
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
padding: 0.5rem 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.detail-label {
|
|
||||||
font-size: 0.7rem;
|
|
||||||
margin-bottom: 0.375rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.detail-value {
|
.detail-value {
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tech-section {
|
|
||||||
margin-top: 1.25rem;
|
|
||||||
padding-top: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tech-item {
|
|
||||||
padding: 0.25rem 0.5rem;
|
|
||||||
font-size: 0.7rem;
|
|
||||||
margin-bottom: 0.25rem;
|
|
||||||
|
|
||||||
/* Simplified tech item animation */
|
|
||||||
animation-name: techItemFadeIn;
|
|
||||||
animation-duration: 0.2s;
|
|
||||||
animation-timing-function: ease-out;
|
|
||||||
animation-delay: 0.6s;
|
|
||||||
animation-fill-mode: forwards;
|
|
||||||
}
|
|
||||||
|
|
||||||
.current-status {
|
.current-status {
|
||||||
padding: 0.375rem 0.75rem;
|
padding: 0.375rem 0.75rem;
|
||||||
|
|||||||
@@ -15,15 +15,7 @@
|
|||||||
<div class="experience-body">
|
<div class="experience-body">
|
||||||
<app-detail-block label="PROGRAM:" [value]="training.classification" />
|
<app-detail-block label="PROGRAM:" [value]="training.classification" />
|
||||||
<app-detail-block label="INSTITUTION:" [value]="training.timeline" />
|
<app-detail-block label="INSTITUTION:" [value]="training.timeline" />
|
||||||
|
<app-tech-stack label="CURRICULUM:" [techStack]="training.techStack"/>
|
||||||
<div class="tech-section">
|
|
||||||
<div class="detail-label">CURRICULUM:</div>
|
|
||||||
<div class="tech-grid">
|
|
||||||
@for (tech of training.techStack; track tech) {
|
|
||||||
<span class="tech-item">{{ tech }}</span>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,15 +2,16 @@ import { Component, signal } from '@angular/core';
|
|||||||
import { type Experience } from '../../../../shared/models/experience.model';
|
import { type Experience } from '../../../../shared/models/experience.model';
|
||||||
import { DetailBlockComponent } from '../shared/detail-block/detail-block.component';
|
import { DetailBlockComponent } from '../shared/detail-block/detail-block.component';
|
||||||
import { InfoCardComponent } from '../shared/info-card/info-card.component';
|
import { InfoCardComponent } from '../shared/info-card/info-card.component';
|
||||||
|
import { TechStackComponent } from '../tech-stack/tech-stack.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-column-education-info',
|
selector: 'app-column-education-info',
|
||||||
imports: [DetailBlockComponent, InfoCardComponent],
|
imports: [DetailBlockComponent, InfoCardComponent, TechStackComponent],
|
||||||
templateUrl: './column-education-info.component.html',
|
templateUrl: './column-education-info.component.html',
|
||||||
styleUrl: './column-education-info.component.css',
|
styleUrl: './column-education-info.component.css',
|
||||||
})
|
})
|
||||||
export class ColumnEducationInfoComponent {
|
export class ColumnEducationInfoComponent {
|
||||||
private readonly _currentTraining = signal<Experience>({
|
private _currentTraining = signal<Experience>({
|
||||||
id: 'developer-training',
|
id: 'developer-training',
|
||||||
title: 'DEVELOPER_TRAINING.EXE',
|
title: 'DEVELOPER_TRAINING.EXE',
|
||||||
classification: "Concepteur Développeur d'Applications (RNCP Level BAC+4)",
|
classification: "Concepteur Développeur d'Applications (RNCP Level BAC+4)",
|
||||||
@@ -28,5 +29,5 @@ export class ColumnEducationInfoComponent {
|
|||||||
isCurrent: true,
|
isCurrent: true,
|
||||||
} as const);
|
} as const);
|
||||||
|
|
||||||
readonly currentTraining = this._currentTraining.asReadonly();
|
currentTraining = this._currentTraining.asReadonly();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -150,64 +150,6 @@
|
|||||||
color: var(--color-nier-text-light);
|
color: var(--color-nier-text-light);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Tech Stack Section */
|
|
||||||
.tech-section {
|
|
||||||
margin-top: var(--card-gap);
|
|
||||||
padding-top: var(--card-gap);
|
|
||||||
border-top: 1px solid var(--color-nier-border);
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tech-section::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 0;
|
|
||||||
height: 1px;
|
|
||||||
background: var(--color-nier-accent);
|
|
||||||
animation: expandLine 0.6s ease-out calc(var(--animation-delay-base) * 12) forwards;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tech-grid {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 0.25rem;
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Tech Items */
|
|
||||||
.tech-item {
|
|
||||||
background: linear-gradient(135deg, var(--color-nier-accent) 0%, var(--color-nier-highlight) 100%);
|
|
||||||
color: var(--color-nier-text-light);
|
|
||||||
padding: 0.25rem 0.5rem;
|
|
||||||
font-size: 0.7rem;
|
|
||||||
font-family: var(--font-terminal);
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.05em;
|
|
||||||
border-radius: 3px;
|
|
||||||
white-space: nowrap;
|
|
||||||
transition: var(--transition-smooth);
|
|
||||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
||||||
|
|
||||||
/* Initial state for staggered animation */
|
|
||||||
opacity: 0;
|
|
||||||
transform: scale(0.8) translateY(10px);
|
|
||||||
animation: techItemFadeIn 0.4s ease-out forwards;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Staggered animation delays for tech items */
|
|
||||||
.tech-item:nth-child(1) { animation-delay: calc(var(--animation-delay-base) * 13); }
|
|
||||||
.tech-item:nth-child(2) { animation-delay: calc(var(--animation-delay-base) * 14); }
|
|
||||||
.tech-item:nth-child(3) { animation-delay: calc(var(--animation-delay-base) * 15); }
|
|
||||||
.tech-item:nth-child(4) { animation-delay: calc(var(--animation-delay-base) * 16); }
|
|
||||||
.tech-item:nth-child(5) { animation-delay: calc(var(--animation-delay-base) * 17); }
|
|
||||||
.tech-item:nth-child(n+6) { animation-delay: calc(var(--animation-delay-base) * 18); }
|
|
||||||
|
|
||||||
.tech-item:hover {
|
|
||||||
transform: translateY(-2px) scale(1.05);
|
|
||||||
box-shadow: 0 6px 12px rgba(0,0,0,0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Visual Effects */
|
/* Visual Effects */
|
||||||
.corner-accent {
|
.corner-accent {
|
||||||
|
|||||||
@@ -24,15 +24,7 @@
|
|||||||
<div class="experience-body">
|
<div class="experience-body">
|
||||||
<app-detail-block label="CLASSIFICATION:" [value]="experience.classification"/>
|
<app-detail-block label="CLASSIFICATION:" [value]="experience.classification"/>
|
||||||
<app-detail-block label="TIMELINE:" [value]="experience.timeline"/>
|
<app-detail-block label="TIMELINE:" [value]="experience.timeline"/>
|
||||||
|
<app-tech-stack label="TECH_STACK:" [techStack]="experience.techStack"/>
|
||||||
<div class="tech-section">
|
|
||||||
<div class="detail-label">TECH_STACK:</div>
|
|
||||||
<div class="tech-grid">
|
|
||||||
@for (tech of experience.techStack; track tech) {
|
|
||||||
<span class="tech-item">{{ tech }}</span>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
import { Component, signal } from '@angular/core';
|
import { Component, signal } from '@angular/core';
|
||||||
import { Experience } from '../../../../shared/models/experience.model';
|
import { Experience } from '../../../../shared/models/experience.model';
|
||||||
import { DetailBlockComponent } from '../shared/detail-block/detail-block.component';
|
import { DetailBlockComponent } from '../shared/detail-block/detail-block.component';
|
||||||
|
import { TechStackComponent } from '../tech-stack/tech-stack.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-column-professional-info',
|
selector: 'app-column-professional-info',
|
||||||
imports: [DetailBlockComponent],
|
imports: [DetailBlockComponent, TechStackComponent],
|
||||||
templateUrl: './column-professional-info.component.html',
|
templateUrl: './column-professional-info.component.html',
|
||||||
styleUrl: './column-professional-info.component.css',
|
styleUrl: './column-professional-info.component.css',
|
||||||
})
|
})
|
||||||
export class ColumnProfessionalInfoComponent {
|
export class ColumnProfessionalInfoComponent {
|
||||||
private readonly _experiences = signal<readonly Experience[]>([
|
private _experiences = signal<Experience[]>([
|
||||||
{
|
{
|
||||||
id: 'senior-specialist',
|
id: 'senior-specialist',
|
||||||
title: 'PYTHON_API_SPECIALIST.EXE',
|
title: 'PYTHON_API_SPECIALIST.EXE',
|
||||||
@@ -17,7 +18,7 @@ export class ColumnProfessionalInfoComponent {
|
|||||||
objective:
|
objective:
|
||||||
'Specialized in Python/Django development for API automation and RPA processes. Led technical interventions for complex enterprise HR system integrations, developing robust solutions that bridge business requirements with technical implementation.',
|
'Specialized in Python/Django development for API automation and RPA processes. Led technical interventions for complex enterprise HR system integrations, developing robust solutions that bridge business requirements with technical implementation.',
|
||||||
timeline: 'UKG | January 2024 - January 2025',
|
timeline: 'UKG | January 2024 - January 2025',
|
||||||
techStack: ['PYTHON', 'DJANGO', 'API', 'RPA'] as const,
|
techStack: ['PYTHON', 'DJANGO', 'API', 'RPA'],
|
||||||
status: 'completed',
|
status: 'completed',
|
||||||
statusLabel: 'ARCHIVED | SME_SPECIALIST',
|
statusLabel: 'ARCHIVED | SME_SPECIALIST',
|
||||||
},
|
},
|
||||||
@@ -28,7 +29,7 @@ export class ColumnProfessionalInfoComponent {
|
|||||||
objective:
|
objective:
|
||||||
'Subject Matter Expert (SME) for enterprise HRSD connectors and system integrations. Specialized in complex technical challenges involving API development, RPA automation, and SFTP protocols. Provided BI support and data visualization solutions.',
|
'Subject Matter Expert (SME) for enterprise HRSD connectors and system integrations. Specialized in complex technical challenges involving API development, RPA automation, and SFTP protocols. Provided BI support and data visualization solutions.',
|
||||||
timeline: 'UKG | January 2023 - January 2024',
|
timeline: 'UKG | January 2023 - January 2024',
|
||||||
techStack: ['PYTHON', 'DJANGO', 'API', 'RPA'] as const,
|
techStack: ['PYTHON', 'DJANGO', 'API', 'RPA'],
|
||||||
status: 'completed',
|
status: 'completed',
|
||||||
statusLabel: 'ARCHIVED | SME_SPECIALIST',
|
statusLabel: 'ARCHIVED | SME_SPECIALIST',
|
||||||
},
|
},
|
||||||
@@ -39,7 +40,7 @@ export class ColumnProfessionalInfoComponent {
|
|||||||
objective:
|
objective:
|
||||||
'Delivered high-level technical support for VIP business clients using HRSD solutions. Developed expertise in SaaS platforms and enterprise decision-making processes while managing critical client relationships.',
|
'Delivered high-level technical support for VIP business clients using HRSD solutions. Developed expertise in SaaS platforms and enterprise decision-making processes while managing critical client relationships.',
|
||||||
timeline: 'UKG | October 2021 - January 2023',
|
timeline: 'UKG | October 2021 - January 2023',
|
||||||
techStack: ['SAAS', 'HRSD', 'CLIENT_MGMT'] as const,
|
techStack: ['SAAS', 'HRSD', 'CLIENT_MGMT'],
|
||||||
status: 'completed',
|
status: 'completed',
|
||||||
statusLabel: 'ARCHIVED | VIP_TIER',
|
statusLabel: 'ARCHIVED | VIP_TIER',
|
||||||
},
|
},
|
||||||
@@ -50,11 +51,11 @@ export class ColumnProfessionalInfoComponent {
|
|||||||
objective:
|
objective:
|
||||||
'Provided VIP end-user support, managed hardware/software deployments, and implemented networking and information security practices in a mission-critical environment.',
|
'Provided VIP end-user support, managed hardware/software deployments, and implemented networking and information security practices in a mission-critical environment.',
|
||||||
timeline: 'Armée de Terre | July 2018 - December 2018',
|
timeline: 'Armée de Terre | July 2018 - December 2018',
|
||||||
techStack: ['NETWORKING', 'SECURITY', 'DEPLOYMENT'] as const,
|
techStack: ['NETWORKING', 'SECURITY', 'DEPLOYMENT'],
|
||||||
status: 'completed',
|
status: 'completed',
|
||||||
statusLabel: 'ARCHIVED | MILITARY',
|
statusLabel: 'ARCHIVED | MILITARY',
|
||||||
},
|
},
|
||||||
] as const);
|
]);
|
||||||
|
|
||||||
readonly experiences = this._experiences.asReadonly();
|
experiences = this._experiences;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
css/* tech-stack.component.css */
|
||||||
|
.tech-section {
|
||||||
|
margin-top: 1.25rem;
|
||||||
|
padding-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tech-grid {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.25rem;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tech Items with fallbacks */
|
||||||
|
.tech-item {
|
||||||
|
background: linear-gradient(135deg, var(--color-nier-accent, #d4af37) 0%, var(--color-nier-highlight, #f4d03f) 100%);
|
||||||
|
color: var(--color-nier-text-light, #ffffff);
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-family: var(--font-terminal, 'Courier New', monospace);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
border-radius: 3px;
|
||||||
|
white-space: nowrap;
|
||||||
|
transition: var(--transition-smooth, all 0.3s ease);
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
|
||||||
|
/* Initial state for staggered animation */
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.8) translateY(10px);
|
||||||
|
animation: techItemFadeIn 0.4s ease-out forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Staggered animation delays for tech items */
|
||||||
|
.tech-item:nth-child(1) { animation-delay: calc(var(--animation-delay-base, 0.1s) * 13); }
|
||||||
|
.tech-item:nth-child(2) { animation-delay: calc(var(--animation-delay-base, 0.1s) * 14); }
|
||||||
|
.tech-item:nth-child(3) { animation-delay: calc(var(--animation-delay-base, 0.1s) * 15); }
|
||||||
|
.tech-item:nth-child(4) { animation-delay: calc(var(--animation-delay-base, 0.1s) * 16); }
|
||||||
|
.tech-item:nth-child(5) { animation-delay: calc(var(--animation-delay-base, 0.1s) * 17); }
|
||||||
|
.tech-item:nth-child(n+6) { animation-delay: calc(var(--animation-delay-base, 0.1s) * 18); }
|
||||||
|
|
||||||
|
.tech-item:hover {
|
||||||
|
transform: translateY(-2px) scale(1.05);
|
||||||
|
box-shadow: 0 6px 12px rgba(0,0,0,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-label {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
margin-bottom: 0.375rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes techItemFadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.8) translateY(10px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1) translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<div class="tech-section">
|
||||||
|
<div class="detail-label">{{label()}}
|
||||||
|
</div>
|
||||||
|
<div class="tech-grid">
|
||||||
|
@for (tech of techStack(); track tech) {
|
||||||
|
<span class="tech-item">{{ tech }}</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { Component, input } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-tech-stack',
|
||||||
|
imports: [],
|
||||||
|
templateUrl: './tech-stack.component.html',
|
||||||
|
styleUrl: './tech-stack.component.css',
|
||||||
|
})
|
||||||
|
export class TechStackComponent {
|
||||||
|
label = input.required<string>();
|
||||||
|
techStack = input.required<string[]>();
|
||||||
|
}
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
type Experience = {
|
type Experience = {
|
||||||
readonly id: string;
|
id: string;
|
||||||
readonly title: string;
|
title: string;
|
||||||
readonly classification: string;
|
classification: string;
|
||||||
readonly objective: string;
|
objective: string;
|
||||||
readonly timeline: string;
|
timeline: string;
|
||||||
readonly techStack: readonly string[];
|
techStack: string[];
|
||||||
readonly status: 'current' | 'completed';
|
status: 'current' | 'completed';
|
||||||
readonly statusLabel: string;
|
statusLabel: string;
|
||||||
readonly isCurrent?: boolean;
|
isCurrent?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export { type Experience };
|
export { type Experience };
|
||||||
|
|||||||
Reference in New Issue
Block a user