Refactor Featured Projects Component
- Removed the old HTML template for the Featured Projects component and replaced it with a new structure that includes a button for each project card, enhancing accessibility with ARIA attributes. - Introduced a new CSS file for the Featured Projects component to encapsulate styles, improving maintainability. - Created a new TypeScript file for the Featured Projects component, implementing a reactive approach using signals for state management. - Added a Project Detail component with its own HTML and CSS files to display detailed information about selected projects. - Updated the Hero 3D component to include social media links with appropriate SVG icons. - Adjusted styles in the Hero 3D component for better responsiveness and visual hierarchy. - Fixed import paths in the Landing component to reflect the new structure of the Featured Projects component.
This commit is contained in:
parent
74632bf2b4
commit
2192734b2f
|
|
@ -19,33 +19,33 @@ export const EN_TRANSLATIONS: TranslationDictionary = {
|
||||||
p1LeadTag: '01 // SOFTWARE ENGINEERING',
|
p1LeadTag: '01 // SOFTWARE ENGINEERING',
|
||||||
p1LeadName: 'Bryan Baño',
|
p1LeadName: 'Bryan Baño',
|
||||||
p1LeadRole: 'Fullstack Software Engineer',
|
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',
|
p1LeadBadge1: '.NET 9 Clean Arch',
|
||||||
p1LeadBadge2: 'Angular Signals',
|
p1LeadBadge2: 'Angular Signals',
|
||||||
p1LeadBadge3: 'MySQL Performance',
|
p1LeadBadge3: 'MySQL Performance',
|
||||||
p1LeadMetric: 'HEXAGONAL ARCHITECTURE • ZERO BLOAT',
|
p1LeadMetric: 'HEXAGONAL ARCHITECTURE • ZERO BLOAT',
|
||||||
p1RightTag: 'PHILOSOPHY // CORE',
|
p1RightTag: 'PHILOSOPHY // CORE',
|
||||||
p1RightTitle: 'Simplicity & Performance',
|
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',
|
p1RightBadge: 'Native Web Platform',
|
||||||
p2LeftTag: 'BACKEND CORE',
|
p2LeftTag: 'BACKEND CORE',
|
||||||
p2LeftTitle: '.NET 9 & MediatR CQRS',
|
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',
|
p2LeftBadge1: 'CQRS & MediatR',
|
||||||
p2LeftBadge2: 'Optimized EF Core',
|
p2LeftBadge2: 'Optimized EF Core',
|
||||||
p2RightTag: 'FRONTEND ENGINE',
|
p2RightTag: 'FRONTEND ENGINE',
|
||||||
p2RightTitle: 'Angular 19 & Signals',
|
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',
|
p2RightBadge1: 'Reactive Signals',
|
||||||
p2RightBadge2: 'OnPush 60FPS',
|
p2RightBadge2: 'OnPush 60FPS',
|
||||||
p3LeftTag: '04 // STANDARDS',
|
p3LeftTag: '04 // STANDARDS',
|
||||||
p3LeftTitle: 'Patterns & Resilience',
|
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',
|
p3LeftBadge1: 'Dependency Inversion',
|
||||||
p3LeftBadge2: 'RFC 7807',
|
p3LeftBadge2: 'RFC 7807',
|
||||||
p3RightTag: '05 // LIVE LAB',
|
p3RightTag: '05 // LIVE LAB',
|
||||||
p3RightTitle: 'Interactive Sandbox',
|
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',
|
p3RightBadge1: 'Linux Terminal',
|
||||||
p3RightBadge2: 'Live .NET API',
|
p3RightBadge2: 'Live .NET API',
|
||||||
|
|
||||||
|
|
@ -75,17 +75,36 @@ export const EN_TRANSLATIONS: TranslationDictionary = {
|
||||||
fpAriaLabel: "Bryan Baño's featured projects",
|
fpAriaLabel: "Bryan Baño's featured projects",
|
||||||
fpPlaceholderImage: 'Image',
|
fpPlaceholderImage: 'Image',
|
||||||
fpAltPrefix: 'Screenshot of ',
|
fpAltPrefix: 'Screenshot of ',
|
||||||
|
projectDetailChallenge: 'Challenges',
|
||||||
|
projectDetailSolution: 'Solution',
|
||||||
|
projectDetailResults: 'Results',
|
||||||
|
projectDetailCloseAria: 'Close project details',
|
||||||
prj1Subtitle: 'CACES Institutional Accreditation',
|
prj1Subtitle: 'CACES Institutional Accreditation',
|
||||||
prj1Desc: 'Led platform development centralizing evidence upload and validation with advanced security via JWT and SharePoint.',
|
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.',
|
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',
|
prj2Subtitle: 'Comprehensive Workshop & Work Order Management',
|
||||||
prj2Institution: 'Automotive Sector',
|
prj2Institution: 'Automotive Sector',
|
||||||
prj2Desc: 'Comprehensive platform for mechanical supply inventory control, vehicle registration, and real-time work order tracking.',
|
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.',
|
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',
|
prj3Subtitle: 'Accelerated Virtual Education',
|
||||||
prj3Institution: 'Academic Platform',
|
prj3Institution: 'Academic Platform',
|
||||||
prj3Desc: 'Led academic management platform development based on RESTful API architecture for high availability in remote learning.',
|
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.',
|
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
|
// Terminal & Workstation
|
||||||
terminalWorkstationTag: '02 // INTERACTIVE WORKSTATION',
|
terminalWorkstationTag: '02 // INTERACTIVE WORKSTATION',
|
||||||
|
|
|
||||||
|
|
@ -15,37 +15,37 @@ export const ES_TRANSLATIONS: TranslationDictionary = {
|
||||||
heroAriaFrontend: 'Frontend Reactivo',
|
heroAriaFrontend: 'Frontend Reactivo',
|
||||||
heroAriaStandards: 'Estándares Técnicos',
|
heroAriaStandards: 'Estándares Técnicos',
|
||||||
heroAriaInteractive: 'Entorno Interactivo',
|
heroAriaInteractive: 'Entorno Interactivo',
|
||||||
p1LeadStatus: '● DISPONIBLE // FULLSTACK',
|
p1LeadStatus: 'DISPONIBLE // FULLSTACK',
|
||||||
p1LeadTag: '01 // INGENIERÍA DE SOFTWARE',
|
p1LeadTag: 'INGENIERÍA DE SOFTWARE',
|
||||||
p1LeadName: 'Bryan Baño',
|
p1LeadName: 'Bryan Baño',
|
||||||
p1LeadRole: 'Fullstack Software Engineer',
|
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',
|
p1LeadBadge1: '.NET 9 Clean Arch',
|
||||||
p1LeadBadge2: 'Angular Signals',
|
p1LeadBadge2: 'Angular Signals',
|
||||||
p1LeadBadge3: 'MySQL Performance',
|
p1LeadBadge3: 'MySQL Performance',
|
||||||
p1LeadMetric: 'ARQUITECTURA HEXAGONAL • CERO SOBREINGENIERÍA',
|
p1LeadMetric: 'ARQUITECTURA HEXAGONAL • CERO BLOAT',
|
||||||
p1RightTag: 'FILOSOFÍA // CORE',
|
p1RightTag: 'FILOSOFÍA // CORE',
|
||||||
p1RightTitle: 'Simplicidad & Rendimiento',
|
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',
|
p1RightBadge: 'Web Platform Nativa',
|
||||||
p2LeftTag: 'BACKEND CORE',
|
p2LeftTag: 'BACKEND CORE',
|
||||||
p2LeftTitle: '.NET 9 & MediatR CQRS',
|
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',
|
p2LeftBadge1: 'CQRS & MediatR',
|
||||||
p2LeftBadge2: 'EF Core Optimizado',
|
p2LeftBadge2: 'EF Core Optimizado',
|
||||||
p2RightTag: 'FRONTEND ENGINE',
|
p2RightTag: 'FRONTEND ENGINE',
|
||||||
p2RightTitle: 'Angular 19 & Signals',
|
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',
|
p2RightBadge1: 'Signals Reactivos',
|
||||||
p2RightBadge2: 'OnPush 60FPS',
|
p2RightBadge2: 'OnPush 60FPS',
|
||||||
p3LeftTag: 'ESTÁNDARES',
|
p3LeftTag: 'ESTÁNDARES',
|
||||||
p3LeftTitle: 'Patrones & Resiliencia',
|
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',
|
p3LeftBadge1: 'Inversión Dependencias',
|
||||||
p3LeftBadge2: 'RFC 7807',
|
p3LeftBadge2: 'RFC 7807',
|
||||||
p3RightTag: 'LABORATORIO EN VIVO',
|
p3RightTag: 'LABORATORIO EN VIVO',
|
||||||
p3RightTitle: 'Entorno Interactivo',
|
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',
|
p3RightBadge1: 'Terminal Linux',
|
||||||
p3RightBadge2: 'Live .NET API',
|
p3RightBadge2: 'Live .NET API',
|
||||||
|
|
||||||
|
|
@ -75,17 +75,36 @@ export const ES_TRANSLATIONS: TranslationDictionary = {
|
||||||
fpAriaLabel: 'Proyectos reales de Bryan Baño',
|
fpAriaLabel: 'Proyectos reales de Bryan Baño',
|
||||||
fpPlaceholderImage: 'Imagen',
|
fpPlaceholderImage: 'Imagen',
|
||||||
fpAltPrefix: 'Captura de pantalla de ',
|
fpAltPrefix: 'Captura de pantalla de ',
|
||||||
|
projectDetailChallenge: 'Complicaciones',
|
||||||
|
projectDetailSolution: 'Solución',
|
||||||
|
projectDetailResults: 'Resultados',
|
||||||
|
projectDetailCloseAria: 'Cerrar detalle del proyecto',
|
||||||
prj1Subtitle: 'Acreditación Institucional CACES',
|
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.',
|
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.',
|
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',
|
prj2Subtitle: 'Gestión Integral de Talleres & Órdenes',
|
||||||
prj2Institution: 'Sector Automotriz',
|
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.',
|
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.',
|
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',
|
prj3Subtitle: 'Educación Virtual Acelerada',
|
||||||
prj3Institution: 'Plataforma Académica',
|
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.',
|
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.',
|
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
|
// Terminal & Workstation
|
||||||
terminalWorkstationTag: '02 // INTERACTIVE WORKSTATION',
|
terminalWorkstationTag: '02 // INTERACTIVE WORKSTATION',
|
||||||
|
|
|
||||||
|
|
@ -75,17 +75,36 @@ export interface TranslationDictionary {
|
||||||
readonly fpAriaLabel: string;
|
readonly fpAriaLabel: string;
|
||||||
readonly fpPlaceholderImage: string;
|
readonly fpPlaceholderImage: string;
|
||||||
readonly fpAltPrefix: string;
|
readonly fpAltPrefix: string;
|
||||||
|
readonly projectDetailChallenge: string;
|
||||||
|
readonly projectDetailSolution: string;
|
||||||
|
readonly projectDetailResults: string;
|
||||||
|
readonly projectDetailCloseAria: string;
|
||||||
readonly prj1Subtitle: string;
|
readonly prj1Subtitle: string;
|
||||||
readonly prj1Desc: string;
|
readonly prj1Desc: string;
|
||||||
readonly prj1Impact: string;
|
readonly prj1Impact: string;
|
||||||
|
readonly prj1Challenge: string;
|
||||||
|
readonly prj1Solution: string;
|
||||||
|
readonly prj1Result1: string;
|
||||||
|
readonly prj1Result2: string;
|
||||||
|
readonly prj1Result3: string;
|
||||||
readonly prj2Subtitle: string;
|
readonly prj2Subtitle: string;
|
||||||
readonly prj2Institution: string;
|
readonly prj2Institution: string;
|
||||||
readonly prj2Desc: string;
|
readonly prj2Desc: string;
|
||||||
readonly prj2Impact: string;
|
readonly prj2Impact: string;
|
||||||
|
readonly prj2Challenge: string;
|
||||||
|
readonly prj2Solution: string;
|
||||||
|
readonly prj2Result1: string;
|
||||||
|
readonly prj2Result2: string;
|
||||||
|
readonly prj2Result3: string;
|
||||||
readonly prj3Subtitle: string;
|
readonly prj3Subtitle: string;
|
||||||
readonly prj3Institution: string;
|
readonly prj3Institution: string;
|
||||||
readonly prj3Desc: string;
|
readonly prj3Desc: string;
|
||||||
readonly prj3Impact: string;
|
readonly prj3Impact: string;
|
||||||
|
readonly prj3Challenge: string;
|
||||||
|
readonly prj3Solution: string;
|
||||||
|
readonly prj3Result1: string;
|
||||||
|
readonly prj3Result2: string;
|
||||||
|
readonly prj3Result3: string;
|
||||||
|
|
||||||
// Terminal & Workstation
|
// Terminal & Workstation
|
||||||
readonly terminalWorkstationTag: string;
|
readonly terminalWorkstationTag: string;
|
||||||
|
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
<div class="featured-projects-wrapper" role="region" [attr.aria-label]="i18n.t().fpAriaLabel">
|
|
||||||
<!-- Cabecera de la sección -->
|
|
||||||
<header class="section-micro-header">
|
|
||||||
<div class="header-left">
|
|
||||||
<span class="pulse-indicator" aria-hidden="true"></span>
|
|
||||||
<span class="header-tag">{{ i18n.t().fpHeaderTag }}</span>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<!-- Lista de las 3 Tarjetas de Proyecto -->
|
|
||||||
<div class="projects-stack">
|
|
||||||
@for (prj of projects(); track prj.id) {
|
|
||||||
<article class="project-card" [style.--prj-accent]="prj.accentColor">
|
|
||||||
<!-- Espacio reservado para la imagen del proyecto -->
|
|
||||||
<div class="project-image-slot">
|
|
||||||
@if (!imageErrors()[prj.id] && prj.imageUrl) {
|
|
||||||
<img [src]="prj.imageUrl" [alt]="i18n.t().fpAltPrefix + prj.title" class="project-img"
|
|
||||||
(error)="onImageError(prj.id)" loading="lazy" />
|
|
||||||
} @else {
|
|
||||||
<div class="image-placeholder">
|
|
||||||
<span class="placeholder-icon"></span>
|
|
||||||
<span class="placeholder-code">{{ prj.code }}</span>
|
|
||||||
<span class="placeholder-label">{{ i18n.t().fpPlaceholderImage }}</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Contenido descriptivo del proyecto -->
|
|
||||||
<div class="project-content">
|
|
||||||
<!-- Meta superior -->
|
|
||||||
<div class="project-meta-row">
|
|
||||||
<div class="institution-badge">
|
|
||||||
<span class="meta-bullet">●</span>
|
|
||||||
<span class="meta-institution">{{ prj.institution }}</span>
|
|
||||||
</div>
|
|
||||||
<span class="meta-tag">{{ prj.subtitle }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Título principal -->
|
|
||||||
<h3 class="project-title">{{ prj.title }}</h3>
|
|
||||||
|
|
||||||
<!-- Impacto y resultados clave extraídos del CV -->
|
|
||||||
<p class="project-impact">{{ prj.impact }}</p>
|
|
||||||
|
|
||||||
<!-- Chips de Tecnologías -->
|
|
||||||
<div class="project-tags-row">
|
|
||||||
@for (tag of prj.tags; track tag) {
|
|
||||||
<span class="tech-chip">{{ tag }}</span>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
@ -12,7 +12,6 @@
|
||||||
user-select: text;
|
user-select: text;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Micro-Cabecera de la sección */
|
|
||||||
.section-micro-header {
|
.section-micro-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
@ -45,13 +44,6 @@
|
||||||
letter-spacing: 0.08em;
|
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 {
|
.projects-stack {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
|
|
@ -60,40 +52,54 @@
|
||||||
|
|
||||||
@media (min-width: 960px) {
|
@media (min-width: 960px) {
|
||||||
.projects-stack {
|
.projects-stack {
|
||||||
grid-template-columns: repeat(3, 1fr);
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
gap: var(--space-5);
|
gap: var(--space-5);
|
||||||
|
align-items: start;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Tarjeta individual de proyecto */
|
|
||||||
.project-card {
|
.project-card {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
gap: var(--space-3);
|
gap: var(--space-3);
|
||||||
padding: var(--space-4);
|
padding: var(--space-4);
|
||||||
background-color: rgba(14, 18, 24, 0.75);
|
min-height: 100%;
|
||||||
border: 1px solid var(--zen-border-subtle);
|
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-top: 3px solid var(--prj-accent, var(--zen-accent-kintsugi));
|
||||||
border-radius: var(--zen-radius-xs);
|
border-radius: 1.15rem;
|
||||||
transition: transform var(--transition-fast), border-color var(--transition-fast), box-shadow var(--transition-fast), background-color var(--transition-fast);
|
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.18);
|
||||||
cursor: default;
|
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 {
|
.project-card:hover,
|
||||||
background-color: rgba(18, 24, 32, 0.95);
|
.project-card:focus-visible {
|
||||||
|
transform: translateY(-5px);
|
||||||
border-color: var(--prj-accent, var(--zen-accent-kintsugi));
|
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);
|
box-shadow: 0 16px 28px rgba(0, 0, 0, 0.24), 0 0 18px rgba(125, 207, 255, 0.1);
|
||||||
transform: translateY(-4px);
|
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 {
|
.project-image-slot {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 150px;
|
height: 180px;
|
||||||
background-color: #090b0e;
|
background-color: #090b0e;
|
||||||
border: 1px solid var(--zen-border-subtle);
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
border-radius: var(--zen-radius-xs);
|
border-radius: 0.9rem;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -106,9 +112,9 @@
|
||||||
height: 100%;
|
height: 100%;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
display: block;
|
display: block;
|
||||||
|
filter: saturate(1.08) contrast(1.04);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Marcador de posición técnico cuando aún no hay archivo subido */
|
|
||||||
.image-placeholder {
|
.image-placeholder {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
@ -124,9 +130,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.placeholder-icon {
|
.placeholder-icon {
|
||||||
font-size: 1.25rem;
|
font-size: 1.5rem;
|
||||||
color: var(--prj-accent, var(--zen-accent-kintsugi));
|
color: var(--prj-accent, var(--zen-accent-kintsugi));
|
||||||
opacity: 0.8;
|
opacity: 0.9;
|
||||||
}
|
}
|
||||||
|
|
||||||
.placeholder-code {
|
.placeholder-code {
|
||||||
|
|
@ -143,14 +149,12 @@
|
||||||
letter-spacing: 0.05em;
|
letter-spacing: 0.05em;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Contenido informativo del proyecto */
|
|
||||||
.project-content {
|
.project-content {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
gap: 0.8rem;
|
||||||
gap: 3px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-meta-row {
|
.project-meta-row {
|
||||||
|
|
@ -164,6 +168,7 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.meta-bullet {
|
.meta-bullet {
|
||||||
|
|
@ -190,47 +195,90 @@
|
||||||
|
|
||||||
.project-title {
|
.project-title {
|
||||||
font-family: var(--font-sans);
|
font-family: var(--font-sans);
|
||||||
font-size: 1.25rem;
|
font-size: 1.45rem;
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
color: var(--zen-text-primary);
|
color: var(--zen-text-primary);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
line-height: 1.25;
|
line-height: 1.2;
|
||||||
letter-spacing: -0.01em;
|
letter-spacing: -0.01em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-impact {
|
.project-impact {
|
||||||
font-family: var(--font-sans);
|
font-family: var(--font-sans);
|
||||||
font-size: 0.8125rem;
|
font-size: 0.82rem;
|
||||||
color: var(--zen-text-secondary);
|
color: var(--zen-text-secondary);
|
||||||
line-height: 1.5;
|
line-height: 1.6;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
-webkit-line-clamp: 2;
|
-webkit-line-clamp: 3;
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Fila de chips tecnológicos */
|
|
||||||
.project-tags-row {
|
.project-tags-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 4px;
|
gap: 0.45rem;
|
||||||
margin-top: 2px;
|
margin-top: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tech-chip {
|
.tech-chip {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.25rem 0.55rem;
|
||||||
font-family: var(--font-sans);
|
font-family: var(--font-sans);
|
||||||
font-size: 0.6875rem;
|
font-size: 0.67rem;
|
||||||
font-weight: 600;
|
font-weight: 700;
|
||||||
padding: 2px 7px;
|
letter-spacing: 0.04em;
|
||||||
background-color: rgba(255, 255, 255, 0.04);
|
background: rgba(255, 255, 255, 0.04);
|
||||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
border-radius: 2px;
|
border-radius: 999px;
|
||||||
color: var(--zen-text-muted);
|
color: var(--zen-text-muted);
|
||||||
transition: color var(--transition-fast), border-color var(--transition-fast);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-card:hover .tech-chip {
|
.project-action {
|
||||||
color: var(--zen-text-primary);
|
display: inline-flex;
|
||||||
border-color: rgba(255, 255, 255, 0.18);
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
<div class="featured-projects-wrapper" role="region" [attr.aria-label]="i18n.t().fpAriaLabel">
|
||||||
|
<header class="section-micro-header">
|
||||||
|
<div class="header-left">
|
||||||
|
<span class="pulse-indicator" aria-hidden="true"></span>
|
||||||
|
<span class="header-tag">{{ i18n.t().fpHeaderTag }}</span>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="projects-stack">
|
||||||
|
@for (prj of projects(); track prj.id) {
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="project-card"
|
||||||
|
[class.is-selected]="selectedProjectId() === prj.id"
|
||||||
|
[style.--prj-accent]="prj.accentColor"
|
||||||
|
[attr.aria-pressed]="selectedProjectId() === prj.id"
|
||||||
|
(click)="selectProject(prj.id)"
|
||||||
|
>
|
||||||
|
<div class="project-image-slot">
|
||||||
|
@if (!imageErrors()[prj.id] && prj.imageUrl) {
|
||||||
|
<img
|
||||||
|
[src]="prj.imageUrl"
|
||||||
|
[alt]="i18n.t().fpAltPrefix + prj.title"
|
||||||
|
class="project-img"
|
||||||
|
(error)="onImageError(prj.id)"
|
||||||
|
loading="lazy"
|
||||||
|
/>
|
||||||
|
} @else {
|
||||||
|
<div class="image-placeholder">
|
||||||
|
<span class="placeholder-icon">◧</span>
|
||||||
|
<span class="placeholder-code">{{ prj.code }}</span>
|
||||||
|
<span class="placeholder-label">{{ i18n.t().fpPlaceholderImage }}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="project-content">
|
||||||
|
<div class="project-meta-row">
|
||||||
|
<div class="institution-badge">
|
||||||
|
<span class="meta-bullet">●</span>
|
||||||
|
<span class="meta-institution">{{ prj.institution }}</span>
|
||||||
|
</div>
|
||||||
|
<span class="meta-tag">{{ prj.subtitle }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 class="project-title">{{ prj.title }}</h3>
|
||||||
|
<p class="project-impact">{{ prj.impact }}</p>
|
||||||
|
|
||||||
|
<div class="project-tags-row">
|
||||||
|
@for (tag of prj.tags; track tag) {
|
||||||
|
<span class="tech-chip">{{ tag }}</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span class="project-action">{{ selectedProjectId() === prj.id ? 'Detalle abierto' : 'Ver detalle' }}</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (selectedProject(); as selected) {
|
||||||
|
<div class="project-detail-overlay" (click)="clearSelection()">
|
||||||
|
<div class="project-detail-shell" (click)="$event.stopPropagation()">
|
||||||
|
<app-project-detail [project]="selected" (close)="clearSelection()" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { Component, ChangeDetectionStrategy, signal, computed, inject } from '@angular/core';
|
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 {
|
export interface ProjectCardData {
|
||||||
readonly id: string;
|
readonly id: string;
|
||||||
|
|
@ -9,6 +10,9 @@ export interface ProjectCardData {
|
||||||
readonly institution: string;
|
readonly institution: string;
|
||||||
readonly description: string;
|
readonly description: string;
|
||||||
readonly impact: string;
|
readonly impact: string;
|
||||||
|
readonly challenge: string;
|
||||||
|
readonly solution: string;
|
||||||
|
readonly results: readonly string[];
|
||||||
readonly tags: readonly string[];
|
readonly tags: readonly string[];
|
||||||
readonly imageUrl?: string;
|
readonly imageUrl?: string;
|
||||||
readonly accentColor: string;
|
readonly accentColor: string;
|
||||||
|
|
@ -17,6 +21,7 @@ export interface ProjectCardData {
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-featured-projects',
|
selector: 'app-featured-projects',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
|
imports: [ProjectDetailComponent],
|
||||||
templateUrl: './featured-projects.component.html',
|
templateUrl: './featured-projects.component.html',
|
||||||
styleUrl: './featured-projects.component.css',
|
styleUrl: './featured-projects.component.css',
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
|
|
@ -36,6 +41,9 @@ export class FeaturedProjectsComponent {
|
||||||
institution: 'IST Mayor Pedro Traversari',
|
institution: 'IST Mayor Pedro Traversari',
|
||||||
description: t.prj1Desc,
|
description: t.prj1Desc,
|
||||||
impact: t.prj1Impact,
|
impact: t.prj1Impact,
|
||||||
|
challenge: t.prj1Challenge,
|
||||||
|
solution: t.prj1Solution,
|
||||||
|
results: [t.prj1Result1, t.prj1Result2, t.prj1Result3],
|
||||||
tags: ['Angular', '.NET 8', 'MySQL', 'JWT', 'SharePoint'],
|
tags: ['Angular', '.NET 8', 'MySQL', 'JWT', 'SharePoint'],
|
||||||
imageUrl: 'assets/images/projects/siplece.webp',
|
imageUrl: 'assets/images/projects/siplece.webp',
|
||||||
accentColor: '#7dcfff'
|
accentColor: '#7dcfff'
|
||||||
|
|
@ -48,6 +56,9 @@ export class FeaturedProjectsComponent {
|
||||||
institution: t.prj2Institution,
|
institution: t.prj2Institution,
|
||||||
description: t.prj2Desc,
|
description: t.prj2Desc,
|
||||||
impact: t.prj2Impact,
|
impact: t.prj2Impact,
|
||||||
|
challenge: t.prj2Challenge,
|
||||||
|
solution: t.prj2Solution,
|
||||||
|
results: [t.prj2Result1, t.prj2Result2, t.prj2Result3],
|
||||||
tags: ['Angular', '.NET 8', 'MySQL', 'REST API'],
|
tags: ['Angular', '.NET 8', 'MySQL', 'REST API'],
|
||||||
imageUrl: 'assets/images/projects/vita.webp',
|
imageUrl: 'assets/images/projects/vita.webp',
|
||||||
accentColor: '#9ece6a'
|
accentColor: '#9ece6a'
|
||||||
|
|
@ -60,6 +71,9 @@ export class FeaturedProjectsComponent {
|
||||||
institution: t.prj3Institution,
|
institution: t.prj3Institution,
|
||||||
description: t.prj3Desc,
|
description: t.prj3Desc,
|
||||||
impact: t.prj3Impact,
|
impact: t.prj3Impact,
|
||||||
|
challenge: t.prj3Challenge,
|
||||||
|
solution: t.prj3Solution,
|
||||||
|
results: [t.prj3Result1, t.prj3Result2, t.prj3Result3],
|
||||||
tags: ['Angular', '.NET 8', 'MySQL', 'Clean Arch'],
|
tags: ['Angular', '.NET 8', 'MySQL', 'Clean Arch'],
|
||||||
imageUrl: 'assets/images/projects/ammi.webp',
|
imageUrl: 'assets/images/projects/ammi.webp',
|
||||||
accentColor: '#bb9af7'
|
accentColor: '#bb9af7'
|
||||||
|
|
@ -67,9 +81,28 @@ export class FeaturedProjectsComponent {
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
protected readonly selectedProjectId = signal<string | null>(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
|
// Manejo de fallback elegante si el usuario aún no ha colocado los archivos de imagen
|
||||||
protected readonly imageErrors = signal<Record<string, boolean>>({});
|
protected readonly imageErrors = signal<Record<string, boolean>>({});
|
||||||
|
|
||||||
|
protected selectProject(projectId: string): void {
|
||||||
|
this.selectedProjectId.set(projectId);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected clearSelection(): void {
|
||||||
|
this.selectedProjectId.set(null);
|
||||||
|
}
|
||||||
|
|
||||||
onImageError(projectId: string): void {
|
onImageError(projectId: string): void {
|
||||||
this.imageErrors.update(curr => ({ ...curr, [projectId]: true }));
|
this.imageErrors.update(curr => ({ ...curr, [projectId]: true }));
|
||||||
}
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
@if (project) {
|
||||||
|
<section class="project-detail-panel" [style.--prj-accent]="project.accentColor" aria-live="polite">
|
||||||
|
<div class="project-detail-header">
|
||||||
|
<div>
|
||||||
|
<span class="detail-kicker">{{ project.code }}</span>
|
||||||
|
<h3>{{ project.title }}</h3>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="close-button" (click)="onClose()" [attr.aria-label]="i18n.t().projectDetailCloseAria">
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="project-detail-layout">
|
||||||
|
<div class="project-detail-visual">
|
||||||
|
@if (project.imageUrl) {
|
||||||
|
<img [src]="project.imageUrl" [alt]="project.title" loading="lazy" />
|
||||||
|
} @else {
|
||||||
|
<div class="detail-placeholder">
|
||||||
|
<span>{{ project.code }}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="project-detail-body">
|
||||||
|
<div class="project-detail-meta">
|
||||||
|
<span class="meta-pill">{{ project.institution }}</span>
|
||||||
|
<span class="meta-pill subtle">{{ project.subtitle }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="project-detail-summary">{{ project.description }}</p>
|
||||||
|
|
||||||
|
<div class="detail-block">
|
||||||
|
<h4>{{ i18n.t().projectDetailChallenge }}</h4>
|
||||||
|
<p>{{ project.challenge }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="detail-block">
|
||||||
|
<h4>{{ i18n.t().projectDetailSolution }}</h4>
|
||||||
|
<p>{{ project.solution }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="detail-block results-block">
|
||||||
|
<h4>{{ i18n.t().projectDetailResults }}</h4>
|
||||||
|
<ul>
|
||||||
|
@for (result of project.results; track result) {
|
||||||
|
<li>{{ result }}</li>
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="detail-tags">
|
||||||
|
@for (tag of project.tags; track tag) {
|
||||||
|
<span>{{ tag }}</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
}
|
||||||
|
|
@ -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<void>();
|
||||||
|
|
||||||
|
protected onClose(): void {
|
||||||
|
this.close.emit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -24,19 +24,23 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.lateral-panel {
|
.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;
|
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-left { left: clamp(0.75rem, 1.8vw, 2.25rem); transform: translateY(calc(-50% + 18px)); }
|
||||||
.side-right { right: clamp(1.5rem, 4vw, 4rem); transform: translateY(calc(-50% + 16px)); }
|
.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%); }
|
.lateral-panel.active { opacity: 1; visibility: visible; pointer-events: auto; transform: translateY(-50%); }
|
||||||
|
|
||||||
.pos-f1-left { top: 48%; max-width: 460px; }
|
.pos-f1-left {
|
||||||
.pos-f1-right { top: 56%; max-width: 320px; }
|
top: 46%;
|
||||||
.pos-f2-left { top: 52%; max-width: 390px; }
|
left: clamp(2rem, 4vw, 4rem);
|
||||||
.pos-f2-right { top: 42%; max-width: 350px; }
|
max-width: 340px;
|
||||||
.pos-f3-left { top: 45%; max-width: 340px; }
|
}
|
||||||
.pos-f3-right { top: 52%; max-width: 400px; }
|
.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) */
|
/* Contenedores Flotantes (Sin Cards) */
|
||||||
.hero-floating-lead, .hero-floating-item {
|
.hero-floating-lead, .hero-floating-item {
|
||||||
|
|
@ -48,20 +52,22 @@
|
||||||
|
|
||||||
.lead-header {
|
.lead-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
flex-direction: column;
|
||||||
gap: 12px;
|
align-items: flex-start;
|
||||||
margin-bottom: 8px;
|
gap: 16px;
|
||||||
|
margin-bottom: 24px;
|
||||||
}
|
}
|
||||||
.lead-status {
|
.lead-status {
|
||||||
font-family: var(--font-sans);
|
font-family: var(--font-sans);
|
||||||
font-size: 0.75rem;
|
font-size: 0.66rem;
|
||||||
color: var(--zen-accent-matcha);
|
color: var(--zen-accent-matcha);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
letter-spacing: 0.04em;
|
letter-spacing: 0.12em;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
text-shadow: 0 1px 6px rgba(0, 0, 0, 0.8);
|
text-shadow: 0 1px 6px rgba(0, 0, 0, 0.8);
|
||||||
|
line-height: 1.65;
|
||||||
}
|
}
|
||||||
.lead-status::before {
|
.lead-status::before {
|
||||||
content: '';
|
content: '';
|
||||||
|
|
@ -74,85 +80,128 @@
|
||||||
}
|
}
|
||||||
.lead-tag {
|
.lead-tag {
|
||||||
font-family: var(--font-sans);
|
font-family: var(--font-sans);
|
||||||
font-size: 0.75rem;
|
font-size: 0.6rem;
|
||||||
color: var(--zen-text-muted);
|
color: var(--zen-text-muted);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
letter-spacing: 0.04em;
|
letter-spacing: 0.14em;
|
||||||
text-shadow: 0 1px 6px rgba(0, 0, 0, 0.8);
|
text-shadow: 0 1px 6px rgba(0, 0, 0, 0.8);
|
||||||
|
line-height: 1.8;
|
||||||
}
|
}
|
||||||
.lead-name {
|
.lead-name {
|
||||||
font-family: var(--font-sans);
|
font-family: var(--font-sans);
|
||||||
font-size: clamp(2.25rem, 3.5vw, 3rem);
|
font-size: clamp(2.1rem, 3vw, 3rem);
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
color: var(--zen-text-primary);
|
color: var(--zen-text-primary);
|
||||||
line-height: 1.05;
|
line-height: 1.16;
|
||||||
letter-spacing: -0.03em;
|
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);
|
text-shadow: 0 2px 14px rgba(0, 0, 0, 0.95), 0 4px 24px rgba(0, 0, 0, 0.9);
|
||||||
}
|
}
|
||||||
.lead-role, .panel-eyebrow {
|
.lead-role, .panel-eyebrow {
|
||||||
display: block;
|
display: block;
|
||||||
font-family: var(--font-sans);
|
font-family: var(--font-sans);
|
||||||
font-size: 0.8125rem;
|
font-size: 0.66rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
letter-spacing: 0.08em;
|
letter-spacing: 0.12em;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
color: var(--zen-accent-kintsugi);
|
color: var(--zen-accent-kintsugi);
|
||||||
margin-bottom: 8px;
|
margin-bottom: 18px;
|
||||||
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.85);
|
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.85);
|
||||||
|
line-height: 1.7;
|
||||||
}
|
}
|
||||||
.lead-desc {
|
.lead-desc {
|
||||||
font-family: var(--font-sans);
|
font-family: var(--font-sans);
|
||||||
font-size: 0.9375rem;
|
font-size: 0.78rem;
|
||||||
color: var(--zen-text-secondary);
|
color: var(--zen-text-secondary);
|
||||||
line-height: 1.6;
|
line-height: 1.75;
|
||||||
margin: 0 0 16px 0;
|
margin: 0 0 24px 0;
|
||||||
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.9);
|
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.9);
|
||||||
|
max-width: 25ch;
|
||||||
}
|
}
|
||||||
.lead-badges, .panel-tags {
|
.lead-badges, .panel-tags {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 8px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
.lead-footer {
|
.lead-footer {
|
||||||
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
padding-top: 10px;
|
padding-top: 16px;
|
||||||
margin-top: 14px;
|
margin-top: 28px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
}
|
}
|
||||||
.lead-metric {
|
.lead-metric {
|
||||||
font-family: var(--font-sans);
|
font-family: var(--font-sans);
|
||||||
font-size: 0.75rem;
|
font-size: 0.66rem;
|
||||||
|
line-height: 1.6;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
letter-spacing: 0.04em;
|
letter-spacing: 0.08em;
|
||||||
color: var(--zen-text-muted);
|
color: var(--zen-text-muted);
|
||||||
text-shadow: 0 1px 6px rgba(0, 0, 0, 0.8);
|
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 {
|
.panel-title {
|
||||||
font-family: var(--font-sans);
|
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;
|
font-weight: 800;
|
||||||
line-height: 1.2;
|
line-height: 1.18;
|
||||||
letter-spacing: -0.02em;
|
letter-spacing: -0.02em;
|
||||||
color: var(--zen-text-primary);
|
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);
|
text-shadow: 0 2px 14px rgba(0, 0, 0, 0.95), 0 4px 24px rgba(0, 0, 0, 0.9);
|
||||||
}
|
}
|
||||||
.panel-desc {
|
.panel-desc {
|
||||||
font-family: var(--font-sans);
|
font-family: var(--font-sans);
|
||||||
font-size: 0.875rem;
|
font-size: 0.74rem;
|
||||||
color: var(--zen-text-secondary);
|
color: var(--zen-text-secondary);
|
||||||
line-height: 1.55;
|
line-height: 1.38;
|
||||||
margin: 0 0 12px 0;
|
margin: 0 0 8px 0;
|
||||||
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.9);
|
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.9);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Chips de Etiquetas Flotantes */
|
/* Chips de Etiquetas Flotantes */
|
||||||
.zen-chip {
|
.zen-chip {
|
||||||
font-family: var(--font-sans);
|
font-family: var(--font-sans);
|
||||||
font-size: 0.75rem;
|
font-size: 0.64rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
padding: 3px 10px;
|
padding: 3px 8px;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
background: rgba(16, 20, 26, 0.7);
|
background: rgba(16, 20, 26, 0.7);
|
||||||
backdrop-filter: blur(8px);
|
backdrop-filter: blur(8px);
|
||||||
|
|
@ -160,6 +209,23 @@
|
||||||
color: var(--zen-text-primary);
|
color: var(--zen-text-primary);
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.6);
|
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 {
|
.chip-gold {
|
||||||
border-color: rgba(197, 155, 39, 0.45);
|
border-color: rgba(197, 155, 39, 0.45);
|
||||||
color: var(--zen-accent-kintsugi);
|
color: var(--zen-accent-kintsugi);
|
||||||
|
|
@ -173,10 +239,6 @@
|
||||||
|
|
||||||
@media (max-width: 900px) {
|
@media (max-width: 900px) {
|
||||||
.lateral-panel {
|
.lateral-panel {
|
||||||
max-width: min(90%, 380px) !important; left: 50% !important; right: auto !important;
|
max-width: min(90%, 300px) !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); }
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,43 @@
|
||||||
</div>
|
</div>
|
||||||
<footer class="lead-footer">
|
<footer class="lead-footer">
|
||||||
<span class="lead-metric">{{ i18n.t().p1LeadMetric }}</span>
|
<span class="lead-metric">{{ i18n.t().p1LeadMetric }}</span>
|
||||||
|
<nav class="lead-social-links" aria-label="Redes sociales">
|
||||||
|
<a
|
||||||
|
class="social-link"
|
||||||
|
href="https://github.com/bryancito1090"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
aria-label="GitHub"
|
||||||
|
>
|
||||||
|
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||||||
|
<path d="M12 2a10 10 0 0 0-3.16 19.49c.5.09.68-.22.68-.48v-1.7c-2.78.61-3.37-1.18-3.37-1.18-.45-1.15-1.11-1.46-1.11-1.46-.91-.62.07-.61.07-.61 1 .07 1.53 1.03 1.53 1.03.9 1.53 2.36 1.09 2.94.83.09-.65.35-1.09.64-1.34-2.22-.25-4.55-1.11-4.55-4.94 0-1.09.39-1.98 1.03-2.68-.1-.25-.45-1.27.1-2.65 0 0 .84-.27 2.75 1.02A9.58 9.58 0 0 1 12 7.99c.85 0 1.7.11 2.49.33 1.91-1.29 2.75-1.02 2.75-1.02.55 1.38.2 2.4.1 2.65.64.7 1.03 1.59 1.03 2.68 0 3.84-2.34 4.68-4.57 4.93.36.31.68.92.68 1.85v2.6c0 .26.18.57.69.47A10 10 0 0 0 12 2Z"/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
class="social-link"
|
||||||
|
href="https://www.linkedin.com/in/bryan-bano"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
aria-label="LinkedIn"
|
||||||
|
>
|
||||||
|
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||||||
|
<path d="M5.2 3.5a2.2 2.2 0 1 1 0 4.4 2.2 2.2 0 0 1 0-4.4ZM3.4 9.2h3.6V21H3.4V9.2Zm5.8 0h3.4v1.61h.05c.47-.9 1.63-1.85 3.36-1.85 3.6 0 4.26 2.37 4.26 5.45V21h-3.6v-5.84c0-1.39-.03-3.18-1.94-3.18-1.94 0-2.24 1.52-2.24 3.08V21H9.2V9.2Z"/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
class="social-link"
|
||||||
|
href="https://www.instagram.com/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
aria-label="Instagram"
|
||||||
|
>
|
||||||
|
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||||||
|
<rect x="3.5" y="3.5" width="17" height="17" rx="4"/>
|
||||||
|
<circle cx="12" cy="12" r="4"/>
|
||||||
|
<circle cx="17.5" cy="6.5" r="1"/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</nav>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, signal, inject }
|
||||||
import { Hero3dComponent } from '../hero-3d/hero-3d.component';
|
import { Hero3dComponent } from '../hero-3d/hero-3d.component';
|
||||||
import { RetroOsComponent } from '../retro-os/retro-os.component';
|
import { RetroOsComponent } from '../retro-os/retro-os.component';
|
||||||
import { TerminalComponent } from '../terminal/terminal.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 { ContactSectionComponent } from '../contact-section/contact-section.component';
|
||||||
import { TechnicalProfileComponent } from '../technical-profile/technical-profile.component';
|
import { TechnicalProfileComponent } from '../technical-profile/technical-profile.component';
|
||||||
import { I18nService } from '../../../application/services/i18n.service';
|
import { I18nService } from '../../../application/services/i18n.service';
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue