diff --git a/src/frontend/src/app/application/i18n/translations/en.ts b/src/frontend/src/app/application/i18n/translations/en.ts index eabe73f..1d57850 100644 --- a/src/frontend/src/app/application/i18n/translations/en.ts +++ b/src/frontend/src/app/application/i18n/translations/en.ts @@ -19,33 +19,33 @@ export const EN_TRANSLATIONS: TranslationDictionary = { p1LeadTag: '01 // SOFTWARE ENGINEERING', p1LeadName: 'Bryan Baño', p1LeadRole: 'Fullstack Software Engineer', - p1LeadDesc: 'Specialist in .NET Clean Architecture and reactive Angular Signals. I engineer high-throughput systems, strict decoupling, and high-performance interactive web experiences.', + p1LeadDesc: 'Specialist in .NET Clean Architecture and Angular Signals. I build high-throughput systems with strict decoupling and fluid web experiences.', p1LeadBadge1: '.NET 9 Clean Arch', p1LeadBadge2: 'Angular Signals', p1LeadBadge3: 'MySQL Performance', p1LeadMetric: 'HEXAGONAL ARCHITECTURE • ZERO BLOAT', p1RightTag: 'PHILOSOPHY // CORE', p1RightTitle: 'Simplicity & Performance', - p1RightDesc: 'Ponytail approach (YAGNI / KISS): native platform tools before heavy packages and direct 60 FPS code.', + p1RightDesc: 'Ponytail: less dependency, more performance, and direct 60 FPS code.', p1RightBadge: 'Native Web Platform', p2LeftTag: 'BACKEND CORE', p2LeftTitle: '.NET 9 & MediatR CQRS', - p2LeftDesc: 'Command Query Responsibility Segregation, FluentValidation rules, and high-throughput transactional queries via EF Core.', + p2LeftDesc: 'CQRS, FluentValidation, and optimized transactional queries in EF Core.', p2LeftBadge1: 'CQRS & MediatR', p2LeftBadge2: 'Optimized EF Core', p2RightTag: 'FRONTEND ENGINE', p2RightTitle: 'Angular 19 & Signals', - p2RightDesc: 'Native reactive state primitives without RxJS overhead, OnPush rendering, and GPU-accelerated 2D Canvas.', + p2RightDesc: 'Native Signals, OnPush rendering, and hardware-accelerated 2D animation.', p2RightBadge1: 'Reactive Signals', p2RightBadge2: 'OnPush 60FPS', p3LeftTag: '04 // STANDARDS', p3LeftTitle: 'Patterns & Resilience', - p3LeftDesc: 'Dependency Inversion (DIP), strictly typed DTOs, and uniform ProblemDetails RFC 7807 error handling.', + p3LeftDesc: 'DIP, typed DTOs, and consistent ProblemDetails handling.', p3LeftBadge1: 'Dependency Inversion', p3LeftBadge2: 'RFC 7807', p3RightTag: '05 // LIVE LAB', p3RightTitle: 'Interactive Sandbox', - p3RightDesc: 'Embedded interactive Linux terminal with live backend HTTP calls and Retro OS with classic arcade games.', + p3RightDesc: 'Interactive Linux terminal and retro lab with classic games.', p3RightBadge1: 'Linux Terminal', p3RightBadge2: 'Live .NET API', @@ -75,17 +75,36 @@ export const EN_TRANSLATIONS: TranslationDictionary = { fpAriaLabel: "Bryan Baño's featured projects", fpPlaceholderImage: 'Image', fpAltPrefix: 'Screenshot of ', + projectDetailChallenge: 'Challenges', + projectDetailSolution: 'Solution', + projectDetailResults: 'Results', + projectDetailCloseAria: 'Close project details', prj1Subtitle: 'CACES Institutional Accreditation', prj1Desc: 'Led platform development centralizing evidence upload and validation with advanced security via JWT and SharePoint.', prj1Impact: 'Automated indicator tracking, eliminating physical paper management with complete traceability for government audits.', + prj1Challenge: 'The accreditation process was fragmented across spreadsheets, folders, and multiple validation stages, which made evidence control slow and vulnerable to omissions during audits.', + prj1Solution: 'I designed a central platform with role-based access, structured evidence workflows, document validation, and secure integrations with SharePoint and JWT-based authentication to protect records and streamline approval processes.', + prj1Result1: 'Reduced manual evidence review time and eliminated duplicate records across departments.', + prj1Result2: 'Improved audit readiness with full traceability, timestamps, and version control for every document.', + prj1Result3: 'Strengthened institutional governance by standardizing the approval flow for each accreditation requirement.', prj2Subtitle: 'Comprehensive Workshop & Work Order Management', prj2Institution: 'Automotive Sector', prj2Desc: 'Comprehensive platform for mechanical supply inventory control, vehicle registration, and real-time work order tracking.', prj2Impact: 'Optimized workshop operational flow, eliminating shortages of critical supplies and improving turnaround times with digital records.', + prj2Challenge: 'The workshop lacked a single source of truth for inventories, vehicle history, and pending work orders; that led to delays, stockouts, and repetitive manual coordination.', + prj2Solution: 'I built a modular system for inventory control, vehicle master data, and maintenance order lifecycle tracking, connecting operational records with real-time reporting to support faster decisions on the shop floor.', + prj2Result1: 'Minimized inventory shortages by improving stock visibility and replenishment planning.', + prj2Result2: 'Reduced operational friction with digital work orders and complete vehicle histories.', + prj2Result3: 'Improved delivery time and service planning through cleaner operational traceability.', prj3Subtitle: 'Accelerated Virtual Education', prj3Institution: 'Academic Platform', prj3Desc: 'Led academic management platform development based on RESTful API architecture for high availability in remote learning.', prj3Impact: 'Centralizes academic administration and automates student tracking, eliminating manual enrollment and attendance processes.', + prj3Challenge: 'Academic operations depended on disconnected manual procedures for enrollment, attendance, and course records, which slowed administrative work and limited visibility for management.', + prj3Solution: 'I implemented a scalable academic platform with REST APIs, structured student management, attendance tracking, and administrative dashboards to support remote learning under a reliable architecture.', + prj3Result1: 'Automated academic records and reduced the time spent on repetitive administrative tasks.', + prj3Result2: 'Improved data consistency for access, course enrollment, and attendance follow-up.', + prj3Result3: 'Created a more resilient and scalable foundation for the digital education experience.', // Terminal & Workstation terminalWorkstationTag: '02 // INTERACTIVE WORKSTATION', diff --git a/src/frontend/src/app/application/i18n/translations/es.ts b/src/frontend/src/app/application/i18n/translations/es.ts index e69df70..8f975fa 100644 --- a/src/frontend/src/app/application/i18n/translations/es.ts +++ b/src/frontend/src/app/application/i18n/translations/es.ts @@ -15,37 +15,37 @@ export const ES_TRANSLATIONS: TranslationDictionary = { heroAriaFrontend: 'Frontend Reactivo', heroAriaStandards: 'Estándares Técnicos', heroAriaInteractive: 'Entorno Interactivo', - p1LeadStatus: '● DISPONIBLE // FULLSTACK', - p1LeadTag: '01 // INGENIERÍA DE SOFTWARE', + p1LeadStatus: 'DISPONIBLE // FULLSTACK', + p1LeadTag: 'INGENIERÍA DE SOFTWARE', p1LeadName: 'Bryan Baño', p1LeadRole: 'Fullstack Software Engineer', - p1LeadDesc: 'Especialista en .NET Clean Architecture y Angular Signals reactivo. Diseño sistemas empresariales de alta concurrencia, desacoplamiento estricto y experiencias web de máxima fluidez.', + p1LeadDesc: 'Especialista en .NET Clean Architecture y Angular Signals. Diseño sistemas de alta concurrencia con desacoplamiento estricto y experiencias web fluidas.', p1LeadBadge1: '.NET 9 Clean Arch', p1LeadBadge2: 'Angular Signals', p1LeadBadge3: 'MySQL Performance', - p1LeadMetric: 'ARQUITECTURA HEXAGONAL • CERO SOBREINGENIERÍA', + p1LeadMetric: 'ARQUITECTURA HEXAGONAL • CERO BLOAT', p1RightTag: 'FILOSOFÍA // CORE', p1RightTitle: 'Simplicidad & Rendimiento', - p1RightDesc: 'Enfoque Ponytail (YAGNI / KISS): herramientas nativas antes de dependencias pesadas y código directo a 60 FPS.', + p1RightDesc: 'Ponytail: menos dependencia, más rendimiento y código directo a 60 FPS.', p1RightBadge: 'Web Platform Nativa', p2LeftTag: 'BACKEND CORE', p2LeftTitle: '.NET 9 & MediatR CQRS', - p2LeftDesc: 'Segregación de comandos y consultas, validación con FluentValidation y mapeo transaccional con EF Core.', + p2LeftDesc: 'CQRS, FluentValidation y consultas transaccionales optimizadas con EF Core.', p2LeftBadge1: 'CQRS & MediatR', p2LeftBadge2: 'EF Core Optimizado', p2RightTag: 'FRONTEND ENGINE', p2RightTitle: 'Angular 19 & Signals', - p2RightDesc: 'Gestión de estado nativa con Signals, renderizado OnPush y animaciones por hardware en Canvas 2D.', + p2RightDesc: 'Signals nativos, OnPush y animaciones 2D aceleradas por hardware.', p2RightBadge1: 'Signals Reactivos', p2RightBadge2: 'OnPush 60FPS', p3LeftTag: 'ESTÁNDARES', p3LeftTitle: 'Patrones & Resiliencia', - p3LeftDesc: 'Inversión de dependencias (DIP), DTOs estrictamente tipados y manejo uniforme de errores ProblemDetails RFC 7807.', + p3LeftDesc: 'DIP, DTOs tipados y errores uniformes con ProblemDetails.', p3LeftBadge1: 'Inversión Dependencias', p3LeftBadge2: 'RFC 7807', p3RightTag: 'LABORATORIO EN VIVO', p3RightTitle: 'Entorno Interactivo', - p3RightDesc: 'Consola Linux interactiva con llamadas HTTP reales al backend y mini-sistema operativo retro con videojuegos clásicos.', + p3RightDesc: 'Terminal Linux interactiva y laboratorio retro con juegos clásicos.', p3RightBadge1: 'Terminal Linux', p3RightBadge2: 'Live .NET API', @@ -75,17 +75,36 @@ export const ES_TRANSLATIONS: TranslationDictionary = { fpAriaLabel: 'Proyectos reales de Bryan Baño', fpPlaceholderImage: 'Imagen', fpAltPrefix: 'Captura de pantalla de ', + projectDetailChallenge: 'Complicaciones', + projectDetailSolution: 'Solución', + projectDetailResults: 'Resultados', + projectDetailCloseAria: 'Cerrar detalle del proyecto', prj1Subtitle: 'Acreditación Institucional CACES', prj1Desc: 'Lideré el desarrollo de la plataforma centralizando la carga y validación de evidencias con seguridad avanzada mediante JWT y SharePoint.', prj1Impact: 'Automatizó el seguimiento de indicadores, eliminando la gestión física de documentos con trazabilidad total para auditorías gubernamentales.', + prj1Challenge: 'El proceso de acreditación estaba fragmentado entre hojas de cálculo, carpetas físicas y varias etapas de validación, lo que hacía lento el control de evidencias y dejaba margen para omisiones durante auditorías.', + prj1Solution: 'Diseñé una plataforma central con accesos por roles, flujos estructurados de evidencias, validación documental e integración segura con SharePoint y autenticación JWT para proteger los registros y agilizar las aprobaciones.', + prj1Result1: 'Reduje el tiempo de revisión manual de evidencias y eliminé registros duplicados entre departamentos.', + prj1Result2: 'Mejoré la preparación para auditorías con trazabilidad completa, marcas de tiempo y control de versiones para cada documento.', + prj1Result3: 'Fortalecí la gobernanza institucional al estandarizar el flujo de aprobación de cada requisito de acreditación.', prj2Subtitle: 'Gestión Integral de Talleres & Órdenes', prj2Institution: 'Sector Automotriz', prj2Desc: 'Plataforma integral para control de inventarios de insumos mecánicos, registro detallado de vehículos y seguimiento de órdenes de trabajo en tiempo real.', prj2Impact: 'Optimizó el flujo operativo del taller, eliminando el desabastecimiento de suministros críticos y mejorando los tiempos de entrega con historial digital.', + prj2Challenge: 'El taller no contaba con una fuente única de verdad para inventarios, historial de vehículos y órdenes pendientes; esto generaba retrasos, faltantes y coordinación manual repetitiva.', + prj2Solution: 'Desarrollé un sistema modular para control de inventario, datos maestros de vehículos y ciclo de vida de órdenes de mantenimiento, conectando registros operativos con reportes en tiempo real para decisiones más rápidas en taller.', + prj2Result1: 'Disminuí los desabastecimientos mediante mejor visibilidad del stock y planificación de reposición.', + prj2Result2: 'Reducí la fricción operativa con órdenes digitales y un historial completo de cada vehículo.', + prj2Result3: 'Mejoré tiempos de entrega y planificación del servicio gracias a una trazabilidad más clara del trabajo.', prj3Subtitle: 'Educación Virtual Acelerada', prj3Institution: 'Plataforma Académica', prj3Desc: 'Lideré el desarrollo de la plataforma de gestión académica basada en arquitectura de APIs RESTful para alta disponibilidad en entornos remotos.', prj3Impact: 'Centraliza la administración académica y automatiza el seguimiento estudiantil, eliminando procesos manuales de inscripción y control de asistencia.', + prj3Challenge: 'Las operaciones académicas dependían de procedimientos manuales no conectados para matrícula, asistencia y registros de cursos, lo que reducía velocidad administrativa y visibilidad para la gestión.', + prj3Solution: 'Implementé una plataforma académica escalable con APIs REST, gestión estructurada de estudiantes, control de asistencia y dashboards administrativos para sostener la educación remota con una arquitectura confiable.', + prj3Result1: 'Automatizé registros académicos y reduje el tiempo invertido en tareas administrativas repetitivas.', + prj3Result2: 'Mejoré la consistencia de datos para acceso, matriculación y seguimiento de asistencia.', + prj3Result3: 'Construí una base más resiliente y escalable para la experiencia de educación digital.', // Terminal & Workstation terminalWorkstationTag: '02 // INTERACTIVE WORKSTATION', diff --git a/src/frontend/src/app/domain/models/i18n.model.ts b/src/frontend/src/app/domain/models/i18n.model.ts index f9cdedf..de99a3b 100644 --- a/src/frontend/src/app/domain/models/i18n.model.ts +++ b/src/frontend/src/app/domain/models/i18n.model.ts @@ -75,17 +75,36 @@ export interface TranslationDictionary { readonly fpAriaLabel: string; readonly fpPlaceholderImage: string; readonly fpAltPrefix: string; + readonly projectDetailChallenge: string; + readonly projectDetailSolution: string; + readonly projectDetailResults: string; + readonly projectDetailCloseAria: string; readonly prj1Subtitle: string; readonly prj1Desc: string; readonly prj1Impact: string; + readonly prj1Challenge: string; + readonly prj1Solution: string; + readonly prj1Result1: string; + readonly prj1Result2: string; + readonly prj1Result3: string; readonly prj2Subtitle: string; readonly prj2Institution: string; readonly prj2Desc: string; readonly prj2Impact: string; + readonly prj2Challenge: string; + readonly prj2Solution: string; + readonly prj2Result1: string; + readonly prj2Result2: string; + readonly prj2Result3: string; readonly prj3Subtitle: string; readonly prj3Institution: string; readonly prj3Desc: string; readonly prj3Impact: string; + readonly prj3Challenge: string; + readonly prj3Solution: string; + readonly prj3Result1: string; + readonly prj3Result2: string; + readonly prj3Result3: string; // Terminal & Workstation readonly terminalWorkstationTag: string; diff --git a/src/frontend/src/app/presentation/features/featured-projects/featured-projects.component.html b/src/frontend/src/app/presentation/features/featured-projects/featured-projects.component.html deleted file mode 100644 index 8a140bc..0000000 --- a/src/frontend/src/app/presentation/features/featured-projects/featured-projects.component.html +++ /dev/null @@ -1,55 +0,0 @@ - \ No newline at end of file diff --git a/src/frontend/src/app/presentation/features/featured-projects/featured-projects.component.css b/src/frontend/src/app/presentation/features/featured-projects/featured-projects/featured-projects.component.css similarity index 60% rename from src/frontend/src/app/presentation/features/featured-projects/featured-projects.component.css rename to src/frontend/src/app/presentation/features/featured-projects/featured-projects/featured-projects.component.css index 7586115..af2f303 100644 --- a/src/frontend/src/app/presentation/features/featured-projects/featured-projects.component.css +++ b/src/frontend/src/app/presentation/features/featured-projects/featured-projects/featured-projects.component.css @@ -12,7 +12,6 @@ user-select: text; } -/* Micro-Cabecera de la sección */ .section-micro-header { display: flex; align-items: center; @@ -45,13 +44,6 @@ letter-spacing: 0.08em; } -.header-count { - font-family: var(--font-sans); - font-size: 0.75rem; - color: var(--zen-text-muted); -} - -/* Grid de 3 columnas para los proyectos */ .projects-stack { display: grid; grid-template-columns: 1fr; @@ -60,40 +52,54 @@ @media (min-width: 960px) { .projects-stack { - grid-template-columns: repeat(3, 1fr); + grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-5); + align-items: start; } } -/* Tarjeta individual de proyecto */ .project-card { display: flex; flex-direction: column; align-items: stretch; gap: var(--space-3); padding: var(--space-4); - background-color: rgba(14, 18, 24, 0.75); - border: 1px solid var(--zen-border-subtle); + min-height: 100%; + background: + linear-gradient(180deg, rgba(16, 20, 25, 0.92), rgba(12, 16, 20, 0.88)), + rgba(11, 15, 18, 0.85); + border: 1px solid rgba(255, 255, 255, 0.08); border-top: 3px solid var(--prj-accent, var(--zen-accent-kintsugi)); - border-radius: var(--zen-radius-xs); - transition: transform var(--transition-fast), border-color var(--transition-fast), box-shadow var(--transition-fast), background-color var(--transition-fast); - cursor: default; + border-radius: 1.15rem; + box-shadow: 0 12px 24px rgba(0, 0, 0, 0.18); + cursor: pointer; + text-align: left; + transition: + transform var(--transition-fast), + border-color var(--transition-fast), + box-shadow var(--transition-fast), + background var(--transition-fast); } -.project-card:hover { - background-color: rgba(18, 24, 32, 0.95); +.project-card:hover, +.project-card:focus-visible { + transform: translateY(-5px); border-color: var(--prj-accent, var(--zen-accent-kintsugi)); - box-shadow: 0 12px 30px rgba(0, 0, 0, 0.6), 0 0 16px rgba(125, 207, 255, 0.05); - transform: translateY(-4px); + box-shadow: 0 16px 28px rgba(0, 0, 0, 0.24), 0 0 18px rgba(125, 207, 255, 0.1); + outline: none; +} + +.project-card.is-selected { + border-color: var(--prj-accent, var(--zen-accent-kintsugi)); + box-shadow: 0 18px 36px rgba(0, 0, 0, 0.28), 0 0 22px rgba(125, 207, 255, 0.12); } -/* Espacio reservado para la imagen del proyecto (Banner superior de la card) */ .project-image-slot { width: 100%; - height: 150px; + height: 180px; background-color: #090b0e; - border: 1px solid var(--zen-border-subtle); - border-radius: var(--zen-radius-xs); + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 0.9rem; overflow: hidden; position: relative; display: flex; @@ -106,9 +112,9 @@ height: 100%; object-fit: cover; display: block; + filter: saturate(1.08) contrast(1.04); } -/* Marcador de posición técnico cuando aún no hay archivo subido */ .image-placeholder { display: flex; flex-direction: column; @@ -124,9 +130,9 @@ } .placeholder-icon { - font-size: 1.25rem; + font-size: 1.5rem; color: var(--prj-accent, var(--zen-accent-kintsugi)); - opacity: 0.8; + opacity: 0.9; } .placeholder-code { @@ -143,14 +149,12 @@ letter-spacing: 0.05em; } -/* Contenido informativo del proyecto */ .project-content { flex: 1; min-width: 0; display: flex; flex-direction: column; - justify-content: space-between; - gap: 3px; + gap: 0.8rem; } .project-meta-row { @@ -164,6 +168,7 @@ display: flex; align-items: center; gap: 5px; + min-width: 0; } .meta-bullet { @@ -190,47 +195,90 @@ .project-title { font-family: var(--font-sans); - font-size: 1.25rem; + font-size: 1.45rem; font-weight: 800; color: var(--zen-text-primary); margin: 0; - line-height: 1.25; + line-height: 1.2; letter-spacing: -0.01em; } .project-impact { font-family: var(--font-sans); - font-size: 0.8125rem; + font-size: 0.82rem; color: var(--zen-text-secondary); - line-height: 1.5; + line-height: 1.6; margin: 0; display: -webkit-box; - -webkit-line-clamp: 2; + -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; } -/* Fila de chips tecnológicos */ .project-tags-row { display: flex; flex-wrap: wrap; - gap: 4px; - margin-top: 2px; + gap: 0.45rem; + margin-top: auto; } .tech-chip { + display: inline-flex; + align-items: center; + padding: 0.25rem 0.55rem; font-family: var(--font-sans); - font-size: 0.6875rem; - font-weight: 600; - padding: 2px 7px; - background-color: rgba(255, 255, 255, 0.04); + font-size: 0.67rem; + font-weight: 700; + letter-spacing: 0.04em; + background: rgba(255, 255, 255, 0.04); border: 1px solid rgba(255, 255, 255, 0.08); - border-radius: 2px; + border-radius: 999px; color: var(--zen-text-muted); - transition: color var(--transition-fast), border-color var(--transition-fast); } -.project-card:hover .tech-chip { - color: var(--zen-text-primary); - border-color: rgba(255, 255, 255, 0.18); +.project-action { + display: inline-flex; + align-items: center; + justify-content: flex-start; + margin-top: 0.25rem; + font-size: 0.72rem; + font-weight: 700; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--prj-accent, var(--zen-accent-kintsugi)); } + +.project-detail-overlay { + position: fixed; + inset: 0; + z-index: 100; + display: flex; + align-items: center; + justify-content: center; + padding: clamp(1rem, 2vw, 2.5rem); + background: rgba(4, 7, 10, 0.82); + backdrop-filter: blur(10px); +} + +.project-detail-shell { + width: min(1200px, 100%); + max-height: 92vh; + overflow: auto; + border-radius: 1.4rem; +} + +@media (max-width: 540px) { + .project-card { + padding: var(--space-3); + } + + .project-image-slot { + height: 150px; + } + + .project-meta-row { + flex-direction: column; + align-items: flex-start; + } +} + diff --git a/src/frontend/src/app/presentation/features/featured-projects/featured-projects/featured-projects.component.html b/src/frontend/src/app/presentation/features/featured-projects/featured-projects/featured-projects.component.html new file mode 100644 index 0000000..292c027 --- /dev/null +++ b/src/frontend/src/app/presentation/features/featured-projects/featured-projects/featured-projects.component.html @@ -0,0 +1,68 @@ + diff --git a/src/frontend/src/app/presentation/features/featured-projects/featured-projects.component.ts b/src/frontend/src/app/presentation/features/featured-projects/featured-projects/featured-projects.component.ts similarity index 66% rename from src/frontend/src/app/presentation/features/featured-projects/featured-projects.component.ts rename to src/frontend/src/app/presentation/features/featured-projects/featured-projects/featured-projects.component.ts index 47f192a..1ee9dd5 100644 --- a/src/frontend/src/app/presentation/features/featured-projects/featured-projects.component.ts +++ b/src/frontend/src/app/presentation/features/featured-projects/featured-projects/featured-projects.component.ts @@ -1,5 +1,6 @@ import { Component, ChangeDetectionStrategy, signal, computed, inject } from '@angular/core'; -import { I18nService } from '../../../application/services/i18n.service'; +import { I18nService } from '../../../../application/services/i18n.service'; +import { ProjectDetailComponent } from '../project-detail/project-detail.component'; export interface ProjectCardData { readonly id: string; @@ -9,6 +10,9 @@ export interface ProjectCardData { readonly institution: string; readonly description: string; readonly impact: string; + readonly challenge: string; + readonly solution: string; + readonly results: readonly string[]; readonly tags: readonly string[]; readonly imageUrl?: string; readonly accentColor: string; @@ -17,6 +21,7 @@ export interface ProjectCardData { @Component({ selector: 'app-featured-projects', standalone: true, + imports: [ProjectDetailComponent], templateUrl: './featured-projects.component.html', styleUrl: './featured-projects.component.css', changeDetection: ChangeDetectionStrategy.OnPush @@ -36,6 +41,9 @@ export class FeaturedProjectsComponent { institution: 'IST Mayor Pedro Traversari', description: t.prj1Desc, impact: t.prj1Impact, + challenge: t.prj1Challenge, + solution: t.prj1Solution, + results: [t.prj1Result1, t.prj1Result2, t.prj1Result3], tags: ['Angular', '.NET 8', 'MySQL', 'JWT', 'SharePoint'], imageUrl: 'assets/images/projects/siplece.webp', accentColor: '#7dcfff' @@ -48,6 +56,9 @@ export class FeaturedProjectsComponent { institution: t.prj2Institution, description: t.prj2Desc, impact: t.prj2Impact, + challenge: t.prj2Challenge, + solution: t.prj2Solution, + results: [t.prj2Result1, t.prj2Result2, t.prj2Result3], tags: ['Angular', '.NET 8', 'MySQL', 'REST API'], imageUrl: 'assets/images/projects/vita.webp', accentColor: '#9ece6a' @@ -60,6 +71,9 @@ export class FeaturedProjectsComponent { institution: t.prj3Institution, description: t.prj3Desc, impact: t.prj3Impact, + challenge: t.prj3Challenge, + solution: t.prj3Solution, + results: [t.prj3Result1, t.prj3Result2, t.prj3Result3], tags: ['Angular', '.NET 8', 'MySQL', 'Clean Arch'], imageUrl: 'assets/images/projects/ammi.webp', accentColor: '#bb9af7' @@ -67,9 +81,28 @@ export class FeaturedProjectsComponent { ]; }); + protected readonly selectedProjectId = signal(null); + + protected readonly selectedProject = computed(() => { + const selectedId = this.selectedProjectId(); + if (!selectedId) { + return null; + } + + return this.projects().find(project => project.id === selectedId) ?? null; + }); + // Manejo de fallback elegante si el usuario aún no ha colocado los archivos de imagen protected readonly imageErrors = signal>({}); + protected selectProject(projectId: string): void { + this.selectedProjectId.set(projectId); + } + + protected clearSelection(): void { + this.selectedProjectId.set(null); + } + onImageError(projectId: string): void { this.imageErrors.update(curr => ({ ...curr, [projectId]: true })); } diff --git a/src/frontend/src/app/presentation/features/featured-projects/project-detail/project-detail.component.css b/src/frontend/src/app/presentation/features/featured-projects/project-detail/project-detail.component.css new file mode 100644 index 0000000..350518a --- /dev/null +++ b/src/frontend/src/app/presentation/features/featured-projects/project-detail/project-detail.component.css @@ -0,0 +1,196 @@ +:host { + display: block; + width: 100%; +} + +.project-detail-panel { + display: flex; + flex-direction: column; + width: 100%; + gap: var(--space-4); + padding: var(--space-5); + border: 1px solid rgba(255, 255, 255, 0.1); + border-top: 3px solid var(--prj-accent, var(--zen-accent-kintsugi)); + border-radius: 1.25rem; + background: + linear-gradient(135deg, rgba(12, 16, 22, 0.96), rgba(17, 22, 28, 0.9)), + rgba(11, 14, 18, 0.9); + box-shadow: 0 24px 50px rgba(0, 0, 0, 0.32); +} + +.project-detail-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: var(--space-3); +} + +.detail-kicker { + display: inline-block; + margin-bottom: 0.35rem; + font-size: 0.7rem; + font-weight: 700; + letter-spacing: 0.14em; + color: var(--prj-accent, var(--zen-accent-kintsugi)); + text-transform: uppercase; +} + +.project-detail-header h3 { + margin: 0; + font-size: clamp(1.5rem, 2.2vw, 2.2rem); + line-height: 1.15; + color: var(--zen-text-primary); +} + +.close-button { + display: inline-flex; + align-items: center; + justify-content: center; + width: 2.25rem; + height: 2.25rem; + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 999px; + background: rgba(255, 255, 255, 0.03); + color: var(--zen-text-primary); + cursor: pointer; + transition: transform var(--transition-fast), border-color var(--transition-fast); +} + +.close-button:hover { + transform: rotate(90deg); + border-color: rgba(255, 255, 255, 0.2); +} + +.project-detail-layout { + display: grid; + grid-template-columns: minmax(220px, 0.9fr) minmax(0, 1.4fr); + gap: var(--space-5); + align-items: stretch; +} + +.project-detail-visual { + min-height: 260px; + border-radius: 1rem; + overflow: hidden; + border: 1px solid rgba(255, 255, 255, 0.08); + background: #0a0d12; +} + +.project-detail-visual img { + display: block; + width: 100%; + height: 100%; + object-fit: cover; +} + +.detail-placeholder { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 100%; + min-height: 260px; + font-size: 1.25rem; + font-weight: 800; + color: var(--zen-text-secondary); + background: radial-gradient(circle at center, rgba(255, 255, 255, 0.03), transparent 70%); + border: 1px dashed rgba(255, 255, 255, 0.12); +} + +.project-detail-body { + display: flex; + flex-direction: column; + gap: var(--space-3); +} + +.project-detail-meta { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; +} + +.meta-pill { + display: inline-flex; + align-items: center; + padding: 0.35rem 0.75rem; + border-radius: 999px; + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.08); + font-size: 0.72rem; + font-weight: 700; + color: var(--zen-text-primary); +} + +.meta-pill.subtle { + color: var(--zen-text-secondary); +} + +.project-detail-summary { + margin: 0; + font-size: 0.96rem; + line-height: 1.7; + color: var(--zen-text-secondary); +} + +.detail-block { + padding: var(--space-3); + border: 1px solid rgba(255, 255, 255, 0.06); + border-radius: 0.9rem; + background: rgba(255, 255, 255, 0.02); +} + +.detail-block h4 { + margin: 0 0 0.5rem; + font-size: 0.72rem; + letter-spacing: 0.12em; + text-transform: uppercase; + color: var(--prj-accent, var(--zen-accent-kintsugi)); +} + +.detail-block p, +.detail-block li { + margin: 0; + color: var(--zen-text-secondary); + line-height: 1.7; + font-size: 0.92rem; +} + +.results-block ul { + margin: 0; + padding-left: 1.15rem; + display: grid; + gap: 0.45rem; +} + +.detail-tags { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; +} + +.detail-tags span { + display: inline-flex; + align-items: center; + padding: 0.4rem 0.7rem; + border-radius: 999px; + background: rgba(125, 207, 255, 0.08); + border: 1px solid rgba(125, 207, 255, 0.18); + color: var(--zen-text-primary); + font-size: 0.7rem; + font-weight: 700; + letter-spacing: 0.04em; +} + +@media (max-width: 720px) { + .project-detail-panel { + padding: var(--space-4); + } + + .project-detail-layout { + grid-template-columns: 1fr; + } + + .project-detail-header { + align-items: center; + } +} diff --git a/src/frontend/src/app/presentation/features/featured-projects/project-detail/project-detail.component.html b/src/frontend/src/app/presentation/features/featured-projects/project-detail/project-detail.component.html new file mode 100644 index 0000000..5938256 --- /dev/null +++ b/src/frontend/src/app/presentation/features/featured-projects/project-detail/project-detail.component.html @@ -0,0 +1,59 @@ +@if (project) { +
+
+
+ {{ project.code }} +

{{ project.title }}

+
+ +
+ +
+
+ @if (project.imageUrl) { + + } @else { +
+ {{ project.code }} +
+ } +
+ +
+
+ {{ project.institution }} + {{ project.subtitle }} +
+ +

{{ project.description }}

+ +
+

{{ i18n.t().projectDetailChallenge }}

+

{{ project.challenge }}

+
+ +
+

{{ i18n.t().projectDetailSolution }}

+

{{ project.solution }}

+
+ +
+

{{ i18n.t().projectDetailResults }}

+
    + @for (result of project.results; track result) { +
  • {{ result }}
  • + } +
+
+ +
+ @for (tag of project.tags; track tag) { + {{ tag }} + } +
+
+
+
+} diff --git a/src/frontend/src/app/presentation/features/featured-projects/project-detail/project-detail.component.ts b/src/frontend/src/app/presentation/features/featured-projects/project-detail/project-detail.component.ts new file mode 100644 index 0000000..97426c7 --- /dev/null +++ b/src/frontend/src/app/presentation/features/featured-projects/project-detail/project-detail.component.ts @@ -0,0 +1,22 @@ +import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, inject } from '@angular/core'; +import { I18nService } from '../../../../application/services/i18n.service'; +import { ProjectCardData } from '../featured-projects/featured-projects.component'; + +@Component({ + selector: 'app-project-detail', + standalone: true, + templateUrl: './project-detail.component.html', + styleUrl: './project-detail.component.css', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ProjectDetailComponent { + protected readonly i18n = inject(I18nService); + + @Input() project: ProjectCardData | null = null; + + @Output() close = new EventEmitter(); + + protected onClose(): void { + this.close.emit(); + } +} diff --git a/src/frontend/src/app/presentation/features/hero-3d/hero-3d.component.css b/src/frontend/src/app/presentation/features/hero-3d/hero-3d.component.css index 03c1cfe..47d839b 100644 --- a/src/frontend/src/app/presentation/features/hero-3d/hero-3d.component.css +++ b/src/frontend/src/app/presentation/features/hero-3d/hero-3d.component.css @@ -24,19 +24,23 @@ } .lateral-panel { - position: absolute; z-index: 10; width: 100%; opacity: 0; visibility: hidden; pointer-events: none; + position: absolute; z-index: 10; width: min(100%, 25rem); opacity: 0; visibility: hidden; pointer-events: none; transition: opacity 0.4s ease, transform 0.4s ease, visibility 0.4s; } -.side-left { left: clamp(1.5rem, 4vw, 4rem); transform: translateY(calc(-50% + 16px)); } -.side-right { right: clamp(1.5rem, 4vw, 4rem); transform: translateY(calc(-50% + 16px)); } +.side-left { left: clamp(0.75rem, 1.8vw, 2.25rem); transform: translateY(calc(-50% + 18px)); } +.side-right { right: clamp(0.75rem, 1.8vw, 2.25rem); transform: translateY(calc(-50% + 18px)); } .lateral-panel.active { opacity: 1; visibility: visible; pointer-events: auto; transform: translateY(-50%); } -.pos-f1-left { top: 48%; max-width: 460px; } -.pos-f1-right { top: 56%; max-width: 320px; } -.pos-f2-left { top: 52%; max-width: 390px; } -.pos-f2-right { top: 42%; max-width: 350px; } -.pos-f3-left { top: 45%; max-width: 340px; } -.pos-f3-right { top: 52%; max-width: 400px; } +.pos-f1-left { + top: 46%; + left: clamp(2rem, 4vw, 4rem); + max-width: 340px; +} +.pos-f1-right { top: 58%; max-width: 220px; } +.pos-f2-left { top: 50%; max-width: 300px; } +.pos-f2-right { top: 42%; max-width: 240px; } +.pos-f3-left { top: 46%; max-width: 280px; } +.pos-f3-right { top: 54%; max-width: 250px; } /* Contenedores Flotantes (Sin Cards) */ .hero-floating-lead, .hero-floating-item { @@ -48,20 +52,22 @@ .lead-header { display: flex; - align-items: center; - gap: 12px; - margin-bottom: 8px; + flex-direction: column; + align-items: flex-start; + gap: 16px; + margin-bottom: 24px; } .lead-status { font-family: var(--font-sans); - font-size: 0.75rem; + font-size: 0.66rem; color: var(--zen-accent-matcha); font-weight: 700; - letter-spacing: 0.04em; + letter-spacing: 0.12em; display: inline-flex; align-items: center; gap: 6px; text-shadow: 0 1px 6px rgba(0, 0, 0, 0.8); + line-height: 1.65; } .lead-status::before { content: ''; @@ -74,85 +80,128 @@ } .lead-tag { font-family: var(--font-sans); - font-size: 0.75rem; + font-size: 0.6rem; color: var(--zen-text-muted); font-weight: 600; - letter-spacing: 0.04em; + letter-spacing: 0.14em; text-shadow: 0 1px 6px rgba(0, 0, 0, 0.8); + line-height: 1.8; } .lead-name { font-family: var(--font-sans); - font-size: clamp(2.25rem, 3.5vw, 3rem); + font-size: clamp(2.1rem, 3vw, 3rem); font-weight: 800; color: var(--zen-text-primary); - line-height: 1.05; + line-height: 1.16; letter-spacing: -0.03em; - margin: 0 0 4px 0; + margin: 0 0 16px 0; text-shadow: 0 2px 14px rgba(0, 0, 0, 0.95), 0 4px 24px rgba(0, 0, 0, 0.9); } .lead-role, .panel-eyebrow { display: block; font-family: var(--font-sans); - font-size: 0.8125rem; + font-size: 0.66rem; font-weight: 700; - letter-spacing: 0.08em; + letter-spacing: 0.12em; text-transform: uppercase; color: var(--zen-accent-kintsugi); - margin-bottom: 8px; + margin-bottom: 18px; text-shadow: 0 2px 8px rgba(0, 0, 0, 0.85); + line-height: 1.7; } .lead-desc { font-family: var(--font-sans); - font-size: 0.9375rem; + font-size: 0.78rem; color: var(--zen-text-secondary); - line-height: 1.6; - margin: 0 0 16px 0; + line-height: 1.75; + margin: 0 0 24px 0; text-shadow: 0 2px 10px rgba(0, 0, 0, 0.9); + max-width: 25ch; } .lead-badges, .panel-tags { display: flex; flex-wrap: wrap; - gap: 8px; + gap: 12px; } .lead-footer { border-top: 1px solid rgba(255, 255, 255, 0.1); - padding-top: 10px; - margin-top: 14px; + padding-top: 16px; + margin-top: 28px; + display: flex; + flex-direction: column; + gap: 20px; } .lead-metric { font-family: var(--font-sans); - font-size: 0.75rem; + font-size: 0.66rem; + line-height: 1.6; font-weight: 600; - letter-spacing: 0.04em; + letter-spacing: 0.08em; color: var(--zen-text-muted); text-shadow: 0 1px 6px rgba(0, 0, 0, 0.8); } +.lead-social-links { + display: flex; + align-items: center; + flex-direction: column; + align-items: flex-start; + gap: 12px; +} +.social-link { + display: inline-grid; + width: 40px; + height: 40px; + place-items: center; + color: var(--zen-text-secondary); + border: 1px solid var(--zen-border-subtle); + transition: color 150ms cubic-bezier(0.4, 0, 0.2, 1), border-color 150ms cubic-bezier(0.4, 0, 0.2, 1); +} +.social-link:hover, +.social-link:focus-visible { + color: var(--zen-accent-kintsugi); + border-color: var(--zen-accent-kintsugi); +} +.social-link svg { + width: 20px; + height: 20px; + fill: currentColor; +} +.social-link:last-child svg { + fill: none; + stroke: currentColor; + stroke-width: 1.5; +} +.social-link-disabled { + color: var(--zen-text-muted); + opacity: 0.55; + cursor: not-allowed; +} .panel-title { font-family: var(--font-sans); - font-size: clamp(1.35rem, 2vw, 1.65rem); + font-size: clamp(1.1rem, 1.5vw, 1.35rem); font-weight: 800; - line-height: 1.2; + line-height: 1.18; letter-spacing: -0.02em; color: var(--zen-text-primary); - margin: 0 0 8px 0; + margin: 0 0 5px 0; text-shadow: 0 2px 14px rgba(0, 0, 0, 0.95), 0 4px 24px rgba(0, 0, 0, 0.9); } .panel-desc { font-family: var(--font-sans); - font-size: 0.875rem; + font-size: 0.74rem; color: var(--zen-text-secondary); - line-height: 1.55; - margin: 0 0 12px 0; + line-height: 1.38; + margin: 0 0 8px 0; text-shadow: 0 2px 10px rgba(0, 0, 0, 0.9); } /* Chips de Etiquetas Flotantes */ .zen-chip { font-family: var(--font-sans); - font-size: 0.75rem; + font-size: 0.64rem; font-weight: 600; - padding: 3px 10px; + padding: 3px 8px; border-radius: 2px; background: rgba(16, 20, 26, 0.7); backdrop-filter: blur(8px); @@ -160,6 +209,23 @@ color: var(--zen-text-primary); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.6); } + +@media (max-width: 900px) { + .lateral-panel { + max-width: min(90%, 300px) !important; left: 50% !important; right: auto !important; + transform: translateX(-50%) translateY(16px); + } + .side-left { top: 12% !important; } + .side-right { top: auto !important; bottom: 8% !important; } + .lateral-panel.active { transform: translateX(-50%) translateY(0); } + + .lead-name { + font-size: clamp(1.8rem, 7vw, 2.6rem); + } + .lead-desc { + max-width: 28ch; + } +} .chip-gold { border-color: rgba(197, 155, 39, 0.45); color: var(--zen-accent-kintsugi); @@ -173,10 +239,6 @@ @media (max-width: 900px) { .lateral-panel { - max-width: min(90%, 380px) !important; left: 50% !important; right: auto !important; - transform: translateX(-50%) translateY(16px); + max-width: min(90%, 300px) !important; } - .side-left { top: 12% !important; } - .side-right { top: auto !important; bottom: 8% !important; } - .lateral-panel.active { transform: translateX(-50%) translateY(0); } } diff --git a/src/frontend/src/app/presentation/features/hero-3d/hero-3d.component.html b/src/frontend/src/app/presentation/features/hero-3d/hero-3d.component.html index 9b6944b..72628d8 100644 --- a/src/frontend/src/app/presentation/features/hero-3d/hero-3d.component.html +++ b/src/frontend/src/app/presentation/features/hero-3d/hero-3d.component.html @@ -28,6 +28,43 @@ diff --git a/src/frontend/src/app/presentation/features/landing/landing.component.ts b/src/frontend/src/app/presentation/features/landing/landing.component.ts index a96579d..92a2ca0 100644 --- a/src/frontend/src/app/presentation/features/landing/landing.component.ts +++ b/src/frontend/src/app/presentation/features/landing/landing.component.ts @@ -2,7 +2,7 @@ import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, signal, inject } import { Hero3dComponent } from '../hero-3d/hero-3d.component'; import { RetroOsComponent } from '../retro-os/retro-os.component'; import { TerminalComponent } from '../terminal/terminal.component'; -import { FeaturedProjectsComponent } from '../featured-projects/featured-projects.component'; +import { FeaturedProjectsComponent } from '../featured-projects/featured-projects/featured-projects.component'; import { ContactSectionComponent } from '../contact-section/contact-section.component'; import { TechnicalProfileComponent } from '../technical-profile/technical-profile.component'; import { I18nService } from '../../../application/services/i18n.service';