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:
bryancito1090 2026-09-01 08:02:04 -05:00
parent 74632bf2b4
commit 2192734b2f
13 changed files with 688 additions and 161 deletions

View File

@ -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',

View File

@ -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',

View File

@ -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;

View File

@ -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>

View File

@ -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;
}
}

View File

@ -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>

View File

@ -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 }));
} }

View File

@ -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;
}
}

View File

@ -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>
}

View File

@ -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();
}
}

View File

@ -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); }
} }

View File

@ -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>

View File

@ -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';