Todos los componentes del NIVEL 2 ya están implementados correctamente: - ✅ Notification Bar (#49) - ✅ Navbar (#50) - ✅ Hero Section (#51) - ✅ Sidebar (#52) - ✅ Footer (#53) Solo se actualizó notification-bar.css para usar variables CSS. Próximo paso: NIVEL 3 (Refinamientos visuales) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
23 KiB
23 KiB
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:
/* ==========================================================================
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:
/**
* 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:
/* ==========================================================================
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:
/* ==========================================================================
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 </ul> del menu):
<!-- Botón "Let's Talk" CTA (Issue #44) -->
<?php if ( has_nav_menu( 'primary' ) ) : ?>
<div class="ms-auto d-none d-lg-block">
<a href="<?php echo esc_url( get_permalink( get_page_by_path( 'contacto' ) ) ?: '#contacto' ); ?>"
class="btn btn-lets-talk"
aria-label="<?php esc_attr_e( 'Contáctanos', 'apus-theme' ); ?>">
<?php esc_html_e( "Let's Talk", 'apus-theme' ); ?>
</a>
</div>
<?php endif; ?>
Archivo: assets/css/header.css
Agregar al final:
/* ==========================================================================
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:
/* ==========================================================================
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:
/* ==========================================================================
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:
/* ==========================================================================
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:
/* ==========================================================================
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:
/* ==========================================================================
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:
: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:
// 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:
// 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:
/* 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
# 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
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