# GAP Implementation - Code Snippets Ready to Use **Quick Copy-Paste Reference** **Fecha:** 2025-11-04 --- ## Issue #41: TOC Sticky con ScrollSpy ### Archivo: `assets/css/toc.css` **Agregar al final del archivo:** ```css /* ========================================================================== TOC STICKY + SCROLLSPY (Issue #41) ========================================================================== */ /* Contenedor Sticky */ .apus-toc { position: sticky; top: 5.5rem; /* Offset debajo del navbar */ max-height: calc(100vh - 6rem); overflow-y: auto; z-index: 100; } /* Scrollbar Personalizado - Chrome/Safari/Edge */ .apus-toc::-webkit-scrollbar { width: 6px; } .apus-toc::-webkit-scrollbar-track { background: #f1f3f4; border-radius: 3px; } .apus-toc::-webkit-scrollbar-thumb { background: #cbd5e0; border-radius: 3px; transition: background 0.3s ease; } .apus-toc::-webkit-scrollbar-thumb:hover { background: #a0aec0; } /* Scrollbar Personalizado - Firefox */ .apus-toc { scrollbar-width: thin; scrollbar-color: #cbd5e0 #f1f3f4; } /* Active Link Highlighting */ .apus-toc-link.active { color: #1a73e8; font-weight: 700; background: rgba(26, 115, 232, 0.1); border-left: 3px solid #1a73e8; padding-left: calc(0.5rem - 3px); /* Compensar border */ } /* Smooth Transition para Active State */ .apus-toc-link { transition: all 0.3s ease; padding-left: 0.5rem; } /* Mobile: Reducir max-height */ @media (max-width: 768px) { .apus-toc { position: relative; /* No sticky en mobile */ max-height: 400px; top: auto; } } ``` ### Archivo: `assets/js/toc.js` **Agregar al final del archivo, antes del cierre de función:** ```javascript /** * ScrollSpy para TOC * Resalta automáticamente el heading activo basado en scroll position * Issue #41 */ function initTOCScrollSpy() { const tocLinks = document.querySelectorAll('.apus-toc-link'); if (tocLinks.length === 0) { return; // No hay TOC en esta página } // Configurar IntersectionObserver const observerOptions = { rootMargin: '-20% 0px -35% 0px', // Activa cuando el heading está en el 20%-65% superior de la pantalla threshold: 0 }; const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { const id = entry.target.getAttribute('id'); const tocLink = document.querySelector(`.apus-toc-link[href="#${id}"]`); if (entry.isIntersecting && tocLink) { // Remover active de todos los links tocLinks.forEach(link => link.classList.remove('active')); // Agregar active al link actual tocLink.classList.add('active'); // Scroll suave del TOC para mantener link visible tocLink.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'nearest' }); } }); }, observerOptions); // Observar todos los headings que tienen ID tocLinks.forEach(link => { const href = link.getAttribute('href'); if (href && href.startsWith('#')) { const heading = document.querySelector(href); if (heading) { observer.observe(heading); } } }); // Cleanup al salir de la página window.addEventListener('beforeunload', () => { observer.disconnect(); }); } // Inicializar cuando el DOM esté listo if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initTOCScrollSpy); } else { initTOCScrollSpy(); } ``` --- ## Issue #42: Navbar Underline Hover Animado ### Archivo: `assets/css/header.css` **Encontrar la sección de `.navbar-nav .nav-link` y agregar:** ```css /* ========================================================================== NAVBAR UNDERLINE HOVER ANIMADO (Issue #42) ========================================================================== */ /* Desktop Navigation - Underline Effect */ .navbar-nav .nav-link { position: relative; padding-bottom: 0.75rem; /* Espacio para underline */ } /* Underline Pseudo-elemento */ .navbar-nav .nav-link::after { content: ''; position: absolute; bottom: 0; left: 0; width: 0; height: 2px; background-color: #61c7cd; /* Turquesa RDash */ transition: width 0.3s ease; } /* Hover State */ .navbar-nav .nav-link:hover { color: #61c7cd; background-color: rgba(97, 199, 205, 0.1); } .navbar-nav .nav-link:hover::after { width: 100%; } /* Active/Current Menu Item */ .navbar-nav .nav-link.active, .navbar-nav .current-menu-item > .nav-link, .navbar-nav .current_page_item > .nav-link { color: #61c7cd; } .navbar-nav .nav-link.active::after, .navbar-nav .current-menu-item > .nav-link::after, .navbar-nav .current_page_item > .nav-link::after { width: 100%; } /* Focus State (Accesibilidad) */ .navbar-nav .nav-link:focus { color: #61c7cd; background-color: rgba(97, 199, 205, 0.1); outline: 2px solid #61c7cd; outline-offset: 2px; } .navbar-nav .nav-link:focus::after { width: 100%; } /* Responsive: Ocultar underline en mobile menu */ @media (max-width: 767px) { .navbar-nav .nav-link::after { display: none; } } ``` --- ## Issue #43: Verificar Colores Notification Bar ### Archivo: `assets/css/notification-bar.css` **Reemplazar colores actuales con estos exactos:** ```css /* ========================================================================== NOTIFICATION BAR - COLORES EXACTOS RDASH (Issue #43) ========================================================================== */ .top-notification-bar { background-color: #4C5C6B; /* Gris RDash exacto */ color: #ffffff; padding: 0.75rem 1rem; font-size: 0.875rem; text-align: center; position: relative; z-index: 1001; border-bottom: 1px solid rgba(255, 255, 255, 0.1); } .top-notification-bar p { margin: 0; line-height: 1.5; } .top-notification-bar a { color: #61c7cd; /* Turquesa hover RDash */ text-decoration: underline; font-weight: 600; transition: color 0.3s ease; } .top-notification-bar a:hover { color: #4fb3b9; /* Turquesa hover oscuro */ text-decoration: none; } .top-notification-bar a:focus { outline: 2px solid #61c7cd; outline-offset: 2px; } .notification-close { position: absolute; right: 1rem; top: 50%; transform: translateY(-50%); background: transparent; border: none; color: rgba(255, 255, 255, 0.8); font-size: 1.25rem; cursor: pointer; padding: 0.25rem 0.5rem; transition: color 0.3s ease; line-height: 1; } .notification-close:hover { color: #ffffff; } .notification-close:focus { outline: 2px solid #61c7cd; outline-offset: 2px; } ``` --- ## Issue #44: Botón "Let's Talk" en Header ### Archivo: `header.php` **Agregar en el navbar, después de los nav items (buscar `` del menu):** ```php
``` ### Archivo: `assets/css/header.css` **Agregar al final:** ```css /* ========================================================================== BOTÓN "LET'S TALK" CTA (Issue #44) ========================================================================== */ .btn-lets-talk { background: linear-gradient(135deg, #FF6B35 0%, #FF8C42 100%); color: #ffffff !important; padding: 0.875rem 1.75rem; border-radius: 8px; font-weight: 600; font-size: 1rem; border: none; box-shadow: 0 4px 12px rgba(255, 107, 53, 0.3); transition: all 0.3s ease; text-decoration: none !important; display: inline-block; white-space: nowrap; } .btn-lets-talk:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(255, 107, 53, 0.4); background: linear-gradient(135deg, #FF5722 0%, #FF7043 100%); color: #ffffff !important; text-decoration: none !important; } .btn-lets-talk:active { transform: translateY(0); box-shadow: 0 2px 8px rgba(255, 107, 53, 0.3); } .btn-lets-talk:focus { outline: 2px solid #FF8C42; outline-offset: 3px; box-shadow: 0 4px 12px rgba(255, 107, 53, 0.3); } /* Responsive: Ocultar en tablets y móviles */ @media (max-width: 991px) { .btn-lets-talk { display: none; } } /* Opcional: Versión mobile en el menú hamburguesa */ .mobile-menu .btn-lets-talk-mobile { display: block; width: 100%; margin: 1rem 1.5rem; width: calc(100% - 3rem); text-align: center; } ``` --- ## Issue #45: Related Posts Cards Grises ### Archivo: `assets/css/related-posts.css` **Reemplazar la sección `.related-post-card` con:** ```css /* ========================================================================== RELATED POST CARD - DISEÑO GRIS CON BORDE LATERAL (Issue #45) ========================================================================== */ .related-post-card { display: flex; flex-direction: column; height: 100%; background: #f7fafc; /* Gris claro de fondo */ border-radius: 12px; overflow: hidden; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); position: relative; border: 1px solid #e2e8f0; } /* Borde lateral izquierdo con gradiente */ .related-post-card::before { content: ''; position: absolute; top: 0; left: 0; width: 4px; height: 100%; background: linear-gradient(180deg, #1e3a5f 0%, #1a73e8 100%); opacity: 0; transition: opacity 0.3s ease; z-index: 10; } /* Hover State */ .related-post-card:hover { background: #ffffff; /* Cambiar a blanco en hover */ border-color: #1a73e8; transform: translateY(-8px); /* Más pronunciado */ box-shadow: 0 12px 32px rgba(26, 115, 232, 0.15); } .related-post-card:hover::before { opacity: 1; /* Mostrar borde lateral */ } /* Ajustar padding del contenido para el borde */ .related-post-content { padding: 1.25rem; padding-left: calc(1.25rem + 4px); /* Compensar borde */ flex: 1; display: flex; flex-direction: column; } /* Título con transición de color */ .related-post-title { font-size: 1.125rem; font-weight: 600; color: #212529; margin: 0 0 0.75rem 0; line-height: 1.4; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; transition: color 0.3s ease; } .related-post-card:hover .related-post-title { color: #1a73e8; } /* Imagen con zoom sutil */ .related-post-thumbnail img { transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); } .related-post-card:hover .related-post-thumbnail img { transform: scale(1.08); /* Zoom más pronunciado */ } /* Responsive */ @media (max-width: 767.98px) { .related-post-card:hover { transform: translateY(-4px); /* Menos pronunciado en mobile */ } } /* Reducir movimiento para usuarios que lo prefieren */ @media (prefers-reduced-motion: reduce) { .related-post-card, .related-post-card::before, .related-post-thumbnail img { transition: none; } .related-post-card:hover { transform: none; } .related-post-card:hover .related-post-thumbnail img { transform: none; } } ``` --- ## Issue #46: Paginación Profesional ### Archivo: `style.css` **Reemplazar la sección de paginación (líneas 437-479) con:** ```css /* ========================================================================== PAGINACIÓN PROFESIONAL (Issue #46) ========================================================================== */ .pagination, .posts-pagination { margin-top: var(--spacing-xxl); margin-bottom: var(--spacing-xxl); } .nav-links { display: flex; justify-content: center; gap: 0.5rem; flex-wrap: wrap; } .nav-links .page-numbers { display: inline-flex; align-items: center; justify-content: center; min-width: 44px; min-height: 44px; padding: 0.5rem 0.75rem; background: #ffffff; border: 2px solid #e2e8f0; /* Borde más grueso */ border-radius: 8px; /* Más redondeado */ color: #4a5568; font-weight: 600; text-decoration: none; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); /* Curva de animación mejorada */ } /* Hover State */ .nav-links .page-numbers:hover { background: #1a73e8; border-color: #1a73e8; color: #ffffff; transform: translateY(-2px); box-shadow: 0 4px 12px rgba(26, 115, 232, 0.3); text-decoration: none; } /* Current/Active Page */ .nav-links .page-numbers.current { background: linear-gradient(135deg, #1e3a5f 0%, #2c5282 100%); border-color: transparent; color: #ffffff; box-shadow: 0 4px 12px rgba(30, 58, 95, 0.3); cursor: default; } /* Dots (ellipsis) */ .nav-links .page-numbers.dots { border: none; pointer-events: none; color: #cbd5e0; cursor: default; } /* Prev/Next Arrows */ .nav-links .page-numbers.prev, .nav-links .page-numbers.next { font-weight: 700; } /* Focus State (Accesibilidad) */ .nav-links .page-numbers:focus { outline: 2px solid #1a73e8; outline-offset: 2px; box-shadow: 0 0 0 0.2rem rgba(26, 115, 232, 0.25); } /* Active State */ .nav-links .page-numbers:active { transform: translateY(0); box-shadow: 0 2px 4px rgba(26, 115, 232, 0.2); } /* Reducir movimiento para usuarios que lo prefieren */ @media (prefers-reduced-motion: reduce) { .nav-links .page-numbers { transition: color 0.3s ease, background-color 0.3s ease; } .nav-links .page-numbers:hover { transform: none; } } /* Responsive */ @media (max-width: 575px) { .nav-links .page-numbers { min-width: 40px; min-height: 40px; padding: 0.375rem 0.625rem; font-size: 0.875rem; } } ``` --- ## Issue #47: Modal Contacto Refinamiento ### Archivo: `assets/css/modal-contact.css` **Agregar/Reemplazar estilos del modal:** ```css /* ========================================================================== MODAL CONTACTO REFINADO (Issue #47) ========================================================================== */ .contact-modal .modal-content { border-radius: 16px; /* Más redondeado */ border: none; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); /* Sombra más profunda */ overflow: hidden; } .contact-modal .modal-header { background: linear-gradient(135deg, #1e3a5f 0%, #2c5282 100%); color: #ffffff; border-radius: 16px 16px 0 0; padding: 1.5rem 2rem; border-bottom: none; } .contact-modal .modal-title { font-size: 1.5rem; font-weight: 700; color: #ffffff; } /* Botón Close Blanco */ .contact-modal .btn-close { filter: brightness(0) invert(1); /* Convertir a blanco */ opacity: 1; transition: opacity 0.3s ease; } .contact-modal .btn-close:hover { opacity: 0.8; } .contact-modal .btn-close:focus { outline: 2px solid #ffffff; outline-offset: 2px; box-shadow: 0 0 0 0.25rem rgba(255, 255, 255, 0.25); } /* Body del Modal */ .contact-modal .modal-body { padding: 2rem; } /* Form Controls */ .contact-modal .form-control, .contact-modal .form-select { border-radius: 8px; border: 2px solid #e2e8f0; padding: 0.75rem 1rem; transition: all 0.3s ease; } .contact-modal .form-control:focus, .contact-modal .form-select:focus { border-color: #1a73e8; box-shadow: 0 0 0 0.25rem rgba(26, 115, 232, 0.15); } /* Labels */ .contact-modal .form-label { font-weight: 600; color: #2d3748; margin-bottom: 0.5rem; } /* Botón Submit */ .contact-modal .btn-primary { background: linear-gradient(135deg, #1e3a5f 0%, #2c5282 100%); border: none; padding: 0.875rem 2rem; font-weight: 600; border-radius: 8px; transition: all 0.3s ease; width: 100%; } .contact-modal .btn-primary:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(30, 58, 95, 0.3); background: linear-gradient(135deg, #152e4a 0%, #1e3a5f 100%); } .contact-modal .btn-primary:active { transform: translateY(0); } .contact-modal .btn-primary:focus { box-shadow: 0 0 0 0.25rem rgba(30, 58, 95, 0.25); } /* Footer del Modal */ .contact-modal .modal-footer { padding: 1.5rem 2rem; border-top: 1px solid #e2e8f0; } /* Responsive */ @media (max-width: 575px) { .contact-modal .modal-body { padding: 1.5rem; } .contact-modal .modal-header { padding: 1.25rem 1.5rem; } .contact-modal .modal-title { font-size: 1.25rem; } } ``` --- ## Issue #48: Animación Pulse CTA Box (Opcional) ### Archivo: `assets/css/cta-box-sidebar.css` **Agregar al final:** ```css /* ========================================================================== ANIMACIÓN PULSE (Issue #48) ========================================================================== */ /* Keyframes de la animación */ @keyframes pulse { 0%, 100% { box-shadow: 0 4px 12px rgba(255, 134, 0, 0.3); transform: scale(1); } 50% { box-shadow: 0 6px 20px rgba(255, 134, 0, 0.5); transform: scale(1.02); } } /* Aplicar animación al CTA box */ .cta-box-sidebar { animation: pulse 3s ease-in-out infinite; transform-origin: center; } /* Detener animación al hacer hover (para no interferir con interacción) */ .cta-box-sidebar:hover { animation: none; } /* Respetar preferencia de movimiento reducido */ @media (prefers-reduced-motion: reduce) { .cta-box-sidebar { animation: none; } } /* Pausar animación cuando el tab no está activo (performance) */ @media (prefers-reduced-motion: no-preference) { .cta-box-sidebar { animation-play-state: running; } } /* Pausar si el usuario está inactivo (opcional - requiere JS) */ body.user-inactive .cta-box-sidebar { animation-play-state: paused; } ``` --- ## Issue #49: Hero Section Padding ### Archivo: `assets/css/hero-section.css` **Agregar/Modificar:** ```css /* ========================================================================== HERO SECTION - PADDING EXACTO (Issue #49) ========================================================================== */ .hero-section { background: linear-gradient(135deg, #1e3a5f 0%, #2c5282 100%); color: #ffffff; padding: 3rem 1rem; /* Padding exacto del template */ text-align: center; margin-bottom: 2rem; } .hero-content { max-width: 900px; margin: 0 auto; } /* Responsive: Reducir padding en mobile */ @media (max-width: 767px) { .hero-section { padding: 2rem 1rem; } } @media (max-width: 575px) { .hero-section { padding: 1.5rem 0.75rem; } } ``` --- ## Utilidades Adicionales ### Variable CSS para Colores RDash **Agregar en `style.css` en `:root`:** ```css :root { /* Colores RDash */ --rdash-navy: #0E2337; --rdash-blue-dark: #1e3a5f; --rdash-blue-light: #2c5282; --rdash-turquoise: #61c7cd; --rdash-turquoise-dark: #4fb3b9; --rdash-orange-start: #FF6B35; --rdash-orange-end: #FF8C42; --rdash-orange-sidebar-start: #FF8600; --rdash-orange-sidebar-end: #FFB800; --rdash-gray-notification: #4C5C6B; --rdash-gray-card: #f7fafc; --rdash-gray-border: #e2e8f0; } ``` ### Mixins SCSS (si usas Sass) **Crear archivo `_rdash-mixins.scss`:** ```scss // Gradiente Hero @mixin gradient-hero { background: linear-gradient(135deg, #1e3a5f 0%, #2c5282 100%); } // Gradiente Botón "Let's Talk" @mixin gradient-cta-button { background: linear-gradient(135deg, #FF6B35 0%, #FF8C42 100%); } // Gradiente CTA Box Sidebar @mixin gradient-cta-box { background: linear-gradient(135deg, #FF8600 0%, #FFB800 100%); } // Hover Effect con Transform @mixin hover-lift($distance: -2px, $shadow-color: rgba(0, 0, 0, 0.15)) { transition: all 0.3s ease; &:hover { transform: translateY($distance); box-shadow: 0 6px 20px $shadow-color; } } // Scrollbar Personalizado @mixin custom-scrollbar($width: 6px, $thumb-color: #cbd5e0, $track-color: #f1f3f4) { &::-webkit-scrollbar { width: $width; } &::-webkit-scrollbar-track { background: $track-color; border-radius: 3px; } &::-webkit-scrollbar-thumb { background: $thumb-color; border-radius: 3px; &:hover { background: darken($thumb-color, 15%); } } scrollbar-width: thin; scrollbar-color: $thumb-color $track-color; } ``` --- ## Testing Snippets ### JavaScript para Verificar ScrollSpy **Agregar temporalmente para debug:** ```javascript // Debug ScrollSpy document.querySelectorAll('.apus-toc-link').forEach(link => { link.addEventListener('click', (e) => { console.log('TOC Link clicked:', link.textContent); }); }); // Verificar que IntersectionObserver está funcionando const headings = document.querySelectorAll('h2[id], h3[id]'); console.log(`Observing ${headings.length} headings for ScrollSpy`); ``` ### CSS para Debug Visual **Agregar temporalmente para verificar posiciones:** ```css /* DEBUG: Visualizar áreas sticky */ .apus-toc { outline: 2px dashed red !important; } /* DEBUG: Visualizar ::after elements */ .navbar-nav .nav-link::after { background-color: lime !important; } /* DEBUG: Visualizar ::before en related posts */ .related-post-card::before { opacity: 1 !important; background: red !important; } ``` --- ## Comandos Git Útiles ### Crear Branch para Issue ```bash # Issue #41 git checkout -b issue-41-toc-sticky-scrollspy git add assets/css/toc.css assets/js/toc.js git commit -m "Issue #41: Implementar TOC sticky con scrollspy - Agregar position sticky con top offset - Implementar IntersectionObserver para active link - Scrollbar personalizado webkit y firefox - Respetar prefers-reduced-motion" git push origin issue-41-toc-sticky-scrollspy # Issue #42 git checkout -b issue-42-navbar-underline git add assets/css/header.css git commit -m "Issue #42: Agregar underline animado en navbar - ::after pseudo-elemento con width transition - Hover color turquesa #61c7cd - Background color hover rgba - Estados active y current" git push origin issue-42-navbar-underline ``` ### Crear PR desde CLI ```bash gh pr create --title "Issue #41: TOC Sticky con ScrollSpy" --body "Implementa TOC sticky con IntersectionObserver para scrollspy automático. Cambios: - Position sticky con top 5.5rem - IntersectionObserver con rootMargin optimizado - Scrollbar personalizado - Active link highlighting - Responsive: static en mobile Testing: - ✅ Chrome, Firefox, Safari, Edge - ✅ Mobile responsive - ✅ Keyboard navigation - ✅ Reduced motion support" ``` --- **Última actualización:** 2025-11-04 **Issues cubiertos:** #41-#49 **Tiempo total estimado:** 3.85 días