diff --git a/wp-content/themes/apus-theme/assets/css/custom-style.css b/wp-content/themes/apus-theme/assets/css/custom-style.css new file mode 100644 index 00000000..c41762d0 --- /dev/null +++ b/wp-content/themes/apus-theme/assets/css/custom-style.css @@ -0,0 +1,276 @@ +/** + * Custom Styles - APUS Theme + * + * Estilos personalizados según el template del cliente. + * Incluye: Navbar sticky, animaciones, y componentes específicos. + * + * @package Apus_Theme + * @since 1.0.0 + */ + +/* ============================================ + NAVBAR STICKY CON ANIMACIONES + ============================================ */ + +.navbar { + position: sticky; + top: 0; + z-index: 1030; + transition: all 0.3s ease; + box-shadow: 0 2px 4px rgba(0,0,0,0.08); +} + +.navbar.scrolled { + box-shadow: 0 4px 12px rgba(0,0,0,0.15); + background-color: #fff !important; +} + +/* Gradient underline animation en hover */ +.nav-link { + position: relative; + transition: all 0.3s ease; + padding: 0.5rem 1rem !important; + font-weight: 500; +} + +.nav-link::after { + content: ''; + position: absolute; + bottom: 0; + left: 50%; + transform: translateX(-50%) scaleX(0); + width: 80%; + height: 2px; + background: linear-gradient(90deg, #0d6efd, #0dcaf0); + transition: transform 0.3s ease; +} + +.nav-link:hover { + color: #0d6efd !important; + background-color: rgba(13, 110, 253, 0.05); + border-radius: 4px; + transform: translateY(-2px); +} + +.nav-link:hover::after { + transform: translateX(-50%) scaleX(1); +} + +/* Active nav link */ +.nav-link.active, +.nav-item.current-menu-item > .nav-link { + color: #0d6efd !important; + font-weight: 600; +} + +.nav-link.active::after, +.nav-item.current-menu-item > .nav-link::after { + transform: translateX(-50%) scaleX(1); +} + +/* Dropdown animations */ +.dropdown-menu { + border: none; + box-shadow: 0 8px 24px rgba(0,0,0,0.12); + border-radius: 8px; + animation: slideDown 0.3s ease; + margin-top: 0.5rem; +} + +@keyframes slideDown { + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.dropdown-item { + padding: 0.75rem 1.5rem; + transition: all 0.2s ease; + font-weight: 400; +} + +.dropdown-item:hover, +.dropdown-item:focus { + background: linear-gradient(90deg, rgba(13, 110, 253, 0.1), rgba(13, 202, 240, 0.1)); + color: #0d6efd; + transform: translateX(5px); +} + +.dropdown-item.active { + background-color: rgba(13, 110, 253, 0.1); + color: #0d6efd; +} + +/* Navbar Brand */ +.navbar-brand { + font-weight: 700; + font-size: 1.5rem; + color: #1a1a1a; + transition: all 0.3s ease; +} + +.navbar-brand:hover { + color: #0d6efd; + transform: scale(1.05); +} + +/* Navbar Toggler (Hamburger) */ +.navbar-toggler { + border: 2px solid rgba(0, 0, 0, 0.1); + padding: 0.5rem 0.75rem; + transition: all 0.3s ease; +} + +.navbar-toggler:hover { + border-color: #0d6efd; + background-color: rgba(13, 110, 253, 0.05); +} + +.navbar-toggler:focus { + box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25); +} + +/* Mobile Menu Styles */ +@media (max-width: 991px) { + .navbar-collapse { + margin-top: 1rem; + padding: 1rem 0; + border-top: 1px solid rgba(0, 0, 0, 0.1); + } + + .nav-link { + padding: 0.75rem 1rem !important; + } + + .nav-link::after { + display: none; + } + + .dropdown-menu { + border: none; + box-shadow: none; + animation: none; + background-color: rgba(0, 0, 0, 0.02); + margin-left: 1rem; + padding: 0.5rem 0; + } + + .dropdown-item { + padding: 0.5rem 1rem; + } + + .dropdown-item:hover { + transform: none; + } +} + +/* ============================================ + SKIP LINK (Accesibilidad) + ============================================ */ + +.skip-link.screen-reader-text { + position: absolute; + left: -9999px; + top: 2.5em; + z-index: 100000; + padding: 1em 1.5em; + background-color: #0d6efd; + color: #fff; + text-decoration: none; + font-size: 0.875rem; + font-weight: 600; + border-radius: 4px; +} + +.skip-link.screen-reader-text:focus { + left: 6px; + outline: 3px solid rgba(13, 110, 253, 0.5); + outline-offset: 2px; +} + +/* ============================================ + SITE CONTENT SPACING + ============================================ */ + +.site-content { + margin-top: 2rem; + margin-bottom: 2rem; +} + +@media (max-width: 767px) { + .site-content { + margin-top: 1rem; + margin-bottom: 1rem; + } +} + +/* ============================================ + WORDPRESS SPECIFIC CLASSES + ============================================ */ + +/* WordPress Menu Classes */ +.menu-item { + position: relative; +} + +.menu-item-has-children > .nav-link { + padding-right: 1.5rem !important; +} + +/* Submenu Indicator (si se usan íconos) */ +.menu-item-has-children > .nav-link::before { + content: ''; + position: absolute; + right: 0.5rem; + top: 50%; + transform: translateY(-50%); + width: 0; + height: 0; + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-top: 5px solid currentColor; + opacity: 0.6; +} + +/* ============================================ + UTILITY CLASSES + ============================================ */ + +/* Screen Reader Only Text */ +.screen-reader-text { + border: 0; + clip: rect(1px, 1px, 1px, 1px); + clip-path: inset(50%); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; + word-wrap: normal !important; +} + +.screen-reader-text:focus { + background-color: #f1f1f1; + border-radius: 3px; + box-shadow: 0 0 2px 2px rgba(0, 0, 0, 0.6); + clip: auto !important; + clip-path: none; + color: #21759b; + display: block; + font-size: 0.875rem; + font-weight: 700; + height: auto; + left: 5px; + line-height: normal; + padding: 15px 23px 14px; + text-decoration: none; + top: 5px; + width: auto; + z-index: 100000; +} diff --git a/wp-content/themes/apus-theme/assets/js/main.js b/wp-content/themes/apus-theme/assets/js/main.js new file mode 100644 index 00000000..d5b861c2 --- /dev/null +++ b/wp-content/themes/apus-theme/assets/js/main.js @@ -0,0 +1,144 @@ +/** + * Main JavaScript - APUS Theme + * + * Funcionalidades principales del tema según template del cliente. + * Incluye: Navbar sticky scroll effect y animaciones. + * + * @package Apus_Theme + * @since 1.0.0 + */ + +(function() { + 'use strict'; + + /** + * Navbar Scroll Effect + * Añade clase 'scrolled' al navbar cuando se hace scroll > 50px + */ + function initNavbarScrollEffect() { + const navbar = document.querySelector('.navbar'); + + if (!navbar) { + return; + } + + // Optimización con throttle para mejor performance + let ticking = false; + + function updateNavbar() { + if (window.scrollY > 50) { + navbar.classList.add('scrolled'); + } else { + navbar.classList.remove('scrolled'); + } + ticking = false; + } + + window.addEventListener('scroll', function() { + if (!ticking) { + window.requestAnimationFrame(updateNavbar); + ticking = true; + } + }); + + // Ejecutar una vez al cargar por si la página ya tiene scroll + updateNavbar(); + } + + /** + * Highlight Active Menu Item + * Marca el item del menú correspondiente a la página actual + */ + function highlightActiveMenuItem() { + const currentUrl = window.location.href; + const navLinks = document.querySelectorAll('.navbar-nav .nav-link'); + + navLinks.forEach(function(link) { + // Remover active de todos + link.classList.remove('active'); + + // Agregar active si coincide URL + if (link.href === currentUrl) { + link.classList.add('active'); + } + }); + } + + /** + * Mobile Menu Close on Link Click + * Cierra el menú móvil automáticamente al hacer click en un enlace + */ + function initMobileMenuAutoClose() { + const navbarToggler = document.querySelector('.navbar-toggler'); + const navbarCollapse = document.querySelector('.navbar-collapse'); + const navLinks = document.querySelectorAll('.navbar-nav .nav-link'); + + if (!navbarToggler || !navbarCollapse) { + return; + } + + navLinks.forEach(function(link) { + link.addEventListener('click', function() { + // Solo en móvil (cuando el toggler es visible) + if (window.getComputedStyle(navbarToggler).display !== 'none') { + const bsCollapse = bootstrap.Collapse.getInstance(navbarCollapse); + if (bsCollapse) { + bsCollapse.hide(); + } + } + }); + }); + } + + /** + * Smooth Scroll for Anchor Links + * Scroll suave para enlaces ancla (#) + */ + function initSmoothScroll() { + const anchorLinks = document.querySelectorAll('a[href^="#"]'); + + anchorLinks.forEach(function(link) { + link.addEventListener('click', function(e) { + const targetId = this.getAttribute('href'); + + // Ignorar enlaces # vacíos o solo # + if (targetId === '#' || targetId === '') { + return; + } + + const targetElement = document.querySelector(targetId); + + if (targetElement) { + e.preventDefault(); + + // Offset por el navbar sticky + const navbarHeight = document.querySelector('.navbar').offsetHeight; + const targetPosition = targetElement.getBoundingClientRect().top + window.pageYOffset - navbarHeight - 20; + + window.scrollTo({ + top: targetPosition, + behavior: 'smooth' + }); + } + }); + }); + } + + /** + * Initialize all functions when DOM is ready + */ + function init() { + initNavbarScrollEffect(); + highlightActiveMenuItem(); + initMobileMenuAutoClose(); + initSmoothScroll(); + } + + // DOM Ready + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', init); + } else { + init(); + } + +})(); diff --git a/wp-content/themes/apus-theme/functions.php b/wp-content/themes/apus-theme/functions.php index 75b8fc2e..c7070e4c 100644 --- a/wp-content/themes/apus-theme/functions.php +++ b/wp-content/themes/apus-theme/functions.php @@ -166,6 +166,11 @@ if (is_admin()) { } } +// Bootstrap Nav Walker +if (file_exists(get_template_directory() . '/inc/nav-walker.php')) { + require_once get_template_directory() . '/inc/nav-walker.php'; +} + // Bootstrap and Script Enqueuing if (file_exists(get_template_directory() . '/inc/enqueue-scripts.php')) { require_once get_template_directory() . '/inc/enqueue-scripts.php'; diff --git a/wp-content/themes/apus-theme/header.php b/wp-content/themes/apus-theme/header.php index e5fe8bb4..32bd57e0 100644 --- a/wp-content/themes/apus-theme/header.php +++ b/wp-content/themes/apus-theme/header.php @@ -1,9 +1,9 @@ > - +
- - - - - - - - +
+ +
diff --git a/wp-content/themes/apus-theme/inc/enqueue-scripts.php b/wp-content/themes/apus-theme/inc/enqueue-scripts.php index b8a26540..a221f9f3 100644 --- a/wp-content/themes/apus-theme/inc/enqueue-scripts.php +++ b/wp-content/themes/apus-theme/inc/enqueue-scripts.php @@ -86,6 +86,34 @@ function apus_enqueue_header() { add_action('wp_enqueue_scripts', 'apus_enqueue_header', 10); +/** + * Enqueue custom styles and main JavaScript + */ +function apus_enqueue_custom_assets() { + // Custom Styles - navbar animations and theme components + wp_enqueue_style( + 'apus-custom-style', + get_template_directory_uri() . '/assets/css/custom-style.css', + array('apus-bootstrap'), + '1.0.0', + 'all' + ); + + // Main JavaScript - navbar scroll effects and interactions + wp_enqueue_script( + 'apus-main-js', + get_template_directory_uri() . '/assets/js/main.js', + array('apus-bootstrap-js'), + '1.0.0', + array( + 'in_footer' => true, + 'strategy' => 'defer', + ) + ); +} + +add_action('wp_enqueue_scripts', 'apus_enqueue_custom_assets', 11); + /** * Enqueue footer styles */ diff --git a/wp-content/themes/apus-theme/inc/nav-walker.php b/wp-content/themes/apus-theme/inc/nav-walker.php new file mode 100644 index 00000000..b87bddc9 --- /dev/null +++ b/wp-content/themes/apus-theme/inc/nav-walker.php @@ -0,0 +1,200 @@ +item_spacing) && 'discard' === $args->item_spacing) { + $t = ''; + $n = ''; + } else { + $t = "\t"; + $n = "\n"; + } + $indent = str_repeat($t, $depth); + + // Dropdown menu classes + $classes = array('dropdown-menu'); + + $class_names = join(' ', apply_filters('nav_menu_submenu_css_class', $classes, $args, $depth)); + $class_names = $class_names ? ' class="' . esc_attr($class_names) . '"' : ''; + + $output .= "{$n}{$indent}{$n}"; + } + + /** + * Starts the element output. + * + * @param string $output Used to append additional content (passed by reference). + * @param WP_Post $item Menu item data object. + * @param int $depth Depth of menu item. Used for padding. + * @param stdClass $args An object of wp_nav_menu() arguments. + * @param int $id Current item ID. + */ + public function start_el(&$output, $item, $depth = 0, $args = null, $id = 0) { + if (isset($args->item_spacing) && 'discard' === $args->item_spacing) { + $t = ''; + $n = ''; + } else { + $t = "\t"; + $n = "\n"; + } + $indent = ($depth) ? str_repeat($t, $depth) : ''; + + $classes = empty($item->classes) ? array() : (array) $item->classes; + $classes[] = 'menu-item-' . $item->ID; + + // Add Bootstrap classes based on depth + if ($depth === 0) { + $classes[] = 'nav-item'; + } + + // Check if menu item has children + $has_children = in_array('menu-item-has-children', $classes); + + if ($has_children && $depth === 0) { + $classes[] = 'dropdown'; + } + + $class_names = join(' ', apply_filters('nav_menu_css_class', array_filter($classes), $item, $args, $depth)); + $class_names = $class_names ? ' class="' . esc_attr($class_names) . '"' : ''; + + $id = apply_filters('nav_menu_item_id', 'menu-item-' . $item->ID, $item, $args, $depth); + $id = $id ? ' id="' . esc_attr($id) . '"' : ''; + + // Output
  • + if ($depth === 0) { + $output .= $indent . ''; + } else { + $output .= $indent . ''; + } + + // Link attributes + $atts = array(); + $atts['title'] = !empty($item->attr_title) ? $item->attr_title : ''; + $atts['target'] = !empty($item->target) ? $item->target : ''; + $atts['rel'] = !empty($item->xfn) ? $item->xfn : ''; + $atts['href'] = !empty($item->url) ? $item->url : ''; + + // Add Bootstrap nav-link class for depth 0 + if ($depth === 0) { + $atts['class'] = 'nav-link'; + } else { + $atts['class'] = 'dropdown-item'; + } + + // Add dropdown-toggle class and attributes for parent items + if ($has_children && $depth === 0) { + $atts['class'] .= ' dropdown-toggle'; + $atts['data-bs-toggle'] = 'dropdown'; + $atts['aria-expanded'] = 'false'; + $atts['role'] = 'button'; + } + + // Add active class for current menu item + if (in_array('current-menu-item', $classes) || in_array('current-menu-parent', $classes)) { + $atts['class'] .= ' active'; + $atts['aria-current'] = 'page'; + } + + $atts = apply_filters('nav_menu_link_attributes', $atts, $item, $args, $depth); + + $attributes = ''; + foreach ($atts as $attr => $value) { + if (!empty($value)) { + $value = ('href' === $attr) ? esc_url($value) : esc_attr($value); + $attributes .= ' ' . $attr . '="' . $value . '"'; + } + } + + $title = apply_filters('the_title', $item->title, $item->ID); + $title = apply_filters('nav_menu_item_title', $title, $item, $args, $depth); + + // Build the link + $item_output = $args->before; + $item_output .= ''; + $item_output .= $args->link_before . $title . $args->link_after; + $item_output .= ''; + $item_output .= $args->after; + + $output .= apply_filters('walker_nav_menu_start_el', $item_output, $item, $depth, $args); + } + + /** + * Traverse elements to create list from elements. + * + * Display one element if the element doesn't have any children otherwise, + * display the element and its children. Will only traverse up to the max + * depth and no ignore elements under that depth. It is possible to set the + * max depth to include all depths, see walk() method. + * + * This method should not be called directly, use the walk() method instead. + * + * @param object $element Data object. + * @param array $children_elements List of elements to continue traversing (passed by reference). + * @param int $max_depth Max depth to traverse. + * @param int $depth Depth of current element. + * @param array $args An array of arguments. + * @param string $output Used to append additional content (passed by reference). + */ + public function display_element($element, &$children_elements, $max_depth, $depth, $args, &$output) { + if (!$element) { + return; + } + + $id_field = $this->db_fields['id']; + + // Display this element + if (is_object($args[0])) { + $args[0]->has_children = !empty($children_elements[$element->$id_field]); + } + + parent::display_element($element, $children_elements, $max_depth, $depth, $args, $output); + } + + /** + * Menu Fallback + * + * If this function is assigned to the wp_nav_menu's fallback_cb option + * and a menu has not been assigned to the theme location in the WordPress + * menu manager the function will display a basic menu of all published pages. + * + * @param array $args passed from the wp_nav_menu function. + */ + public static function fallback($args) { + if (current_user_can('edit_theme_options')) { + echo ''; + } + } +}