isFile() && $file->getFilename() === $filename ) { return $file->getPathname(); } } } catch ( UnexpectedValueException $exception ) { return ''; } return ''; } public function __construct() { add_action('admin_init', array($this, 'register_settings')); add_action('admin_post_ablycuic_upload_zip', array($this, 'handle_zip_upload')); add_action('admin_post_ablycuic_delete_collection', array($this, 'handle_delete_collection')); add_action('admin_menu', array($this, 'add_admin_menu')); // Enqueue custom fonts and FA on frontend and admin add_action('wp_enqueue_scripts', array($this, 'enqueue_custom_fonts'), 20); add_action('admin_enqueue_scripts', array($this, 'enqueue_custom_fonts'), 20); // Dynamically inject picker trigger into mapped ACF fields $this->setup_mapped_fields(); } public function register_settings() { register_setting('ablycuic_settings_group', 'ablycuic_fa_cdn', array( 'sanitize_callback' => array($this, 'sanitize_cdn_urls'), )); register_setting('ablycuic_settings_group', 'ablycuic_custom_data', array( 'sanitize_callback' => array($this, 'sanitize_custom_data'), )); // Stores the merged custom icons array register_setting('ablycuic_settings_group', 'ablycuic_mapped_fields', array( 'sanitize_callback' => array($this, 'sanitize_mapped_fields'), )); } public function sanitize_cdn_urls($value) { $urls = preg_split('/\r\n|\r|\n/', (string) $value); $urls = array_filter(array_map('trim', $urls)); $urls = array_filter(array_map('esc_url_raw', $urls), function ($url) { return (bool) preg_match('#^https?://#i', $url); }); return implode("\n", $urls); } public function sanitize_mapped_fields($value) { $fields = array_filter(array_map('trim', explode(',', (string) $value))); $fields = array_map('sanitize_key', $fields); return implode(', ', array_filter($fields)); } /** * Keep the internal custom icon data as an array when Settings API saves it. * * @param mixed $value Setting value. * @return array */ public function sanitize_custom_data($value) { if ( ! is_array($value) ) { return array(); } $clean = array(); $clean['icons'] = array(); foreach ( (array) ($value['icons'] ?? array()) as $class => $label ) { $class = sanitize_html_class($class); if ( $class ) { $clean['icons'][$class] = sanitize_text_field($label); } } foreach ( array('css_files', 'font_files') as $list_key ) { $clean[$list_key] = array(); foreach ( (array) ($value[$list_key] ?? array()) as $file ) { $file = sanitize_file_name($file); if ( $file ) { $clean[$list_key][] = $file; } } } $clean['collections'] = array(); foreach ( (array) ($value['collections'] ?? array()) as $collection ) { if ( ! is_array($collection) ) { continue; } $item = array( 'id' => sanitize_text_field($collection['id'] ?? ''), 'name' => sanitize_file_name($collection['name'] ?? ''), 'imported_at' => absint($collection['imported_at'] ?? 0), 'css_file' => sanitize_file_name($collection['css_file'] ?? ''), 'css_files' => array(), 'font_files' => array(), 'icons' => array(), ); foreach ( (array) ($collection['css_files'] ?? array()) as $file ) { $file = sanitize_file_name($file); if ( $file ) $item['css_files'][] = $file; } foreach ( (array) ($collection['font_files'] ?? array()) as $file ) { $file = sanitize_file_name($file); if ( $file ) $item['font_files'][] = $file; } foreach ( (array) ($collection['icons'] ?? array()) as $class ) { $class = sanitize_html_class($class); if ( $class ) $item['icons'][] = $class; } if ( $item['id'] ) $clean['collections'][] = $item; } return $clean; } public function add_admin_menu() { add_menu_page( __( 'Custom Icons', 'ably-custom-icon-field-for-acf' ), 'Custom Icons', 'manage_options', 'ablycuic-settings', array($this, 'render_admin_page'), 'dashicons-art', 80 ); } public function setup_mapped_fields() { $mapped = get_option('ablycuic_mapped_fields'); if ( empty($mapped) ) { return; } $fields = array_map('trim', explode(',', $mapped)); foreach ( $fields as $field_name ) { if ( ! empty($field_name) ) { add_action('acf/render_field/name=' . $field_name, array($this, 'render_mapped_field_picker')); } } } public function enqueue_picker_assets() { wp_enqueue_style( 'ably-custom-icon-field-for-acf-picker', ABLYCUIC_URL . 'assets/css/icon-picker.css', array(), ABLYCUIC_VERSION ); wp_enqueue_script( 'ably-custom-icon-field-for-acf-picker', ABLYCUIC_URL . 'assets/js/icon-picker.js', array( 'jquery' ), ABLYCUIC_VERSION, true ); wp_localize_script( 'ably-custom-icon-field-for-acf-picker', 'ablyCuic', array( 'ajaxurl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'ablycuic_nonce' ), )); } public function render_mapped_field_picker($field) { $this->enqueue_picker_assets(); $value = isset($field['value']) ? sanitize_text_field($field['value']) : ''; ?>
None
'application/zip')); if ( empty($file_type['type']) || ! is_uploaded_file($zip_file) ) { wp_safe_redirect(admin_url('admin.php?page=ablycuic-settings&error=invalid_zip')); exit; } if ( ! class_exists('ZipArchive') ) { wp_safe_redirect(admin_url('admin.php?page=ablycuic-settings&error=zip_unavailable')); exit; } $archive = new ZipArchive(); if ( true !== $archive->open($zip_file) || $archive->numFiles > 1000 ) { wp_safe_redirect(admin_url('admin.php?page=ablycuic-settings&error=invalid_zip')); exit; } $uncompressed_size = 0; $allowed_entries = array(); for ( $index = 0; $index < $archive->numFiles; $index++ ) { $entry_name = $archive->getNameIndex($index); $entry_path = str_replace('\\', '/', (string) $entry_name); $entry_base = basename($entry_path); $entry_stat = $archive->statIndex($index); $uncompressed_size += isset($entry_stat['size']) ? (int) $entry_stat['size'] : 0; if ( strpos($entry_path, '../') !== false || 0 === strpos($entry_path, '/') ) { $archive->close(); wp_safe_redirect(admin_url('admin.php?page=ablycuic-settings&error=unsafe_zip')); exit; } if ( false !== strpos($entry_path, "\0") ) { $archive->close(); wp_safe_redirect(admin_url('admin.php?page=ablycuic-settings&error=unsafe_zip')); exit; } if ( $uncompressed_size > 20 * 1024 * 1024 ) { $archive->close(); wp_safe_redirect(admin_url('admin.php?page=ablycuic-settings&error=unsafe_zip')); exit; } // Extract only files required by an IcoMoon export. Other entries // remain inside the uploaded archive and are never written here. $entry_extension = strtolower( (string) pathinfo($entry_base, PATHINFO_EXTENSION) ); $is_font_entry = preg_match('#(?:^|/)fonts/[^/]+$#i', $entry_path) && in_array( $entry_extension, array( 'eot', 'svg', 'ttf', 'woff', 'woff2' ), true ); if ( 'selection.json' === strtolower($entry_base) || 'style.css' === strtolower($entry_base) || $is_font_entry ) { $allowed_entries[] = $entry_name; } } require_once ABSPATH . 'wp-admin/includes/file.php'; WP_Filesystem(); global $wp_filesystem; $unzip_dir = $this->get_upload_dir() . '/temp_' . time(); $result = $archive->extractTo( $unzip_dir, $allowed_entries ); $archive->close(); if ( true !== $result ) { wp_safe_redirect(admin_url('admin.php?page=ablycuic-settings&error=unzip_failed')); exit; } // Parse selection.json $selection_file = $this->find_import_file( $unzip_dir, 'selection.json' ); if ( '' === $selection_file ) { $wp_filesystem->delete($unzip_dir, true); wp_safe_redirect(admin_url('admin.php?page=ablycuic-settings&error=no_selection')); exit; } $json_data = file_get_contents($selection_file); $font_data = json_decode((string) $json_data, true); if ( ! is_array($font_data) || empty($font_data['icons']) || ! is_array($font_data['icons']) ) { $wp_filesystem->delete($unzip_dir, true); wp_safe_redirect(admin_url('admin.php?page=ablycuic-settings&error=invalid_selection')); exit; } // Extract icons logic will go here $new_icons = array(); if (isset($font_data['icons'])) { $prefix = isset($font_data['preferences']['fontPref']['prefix']) ? $font_data['preferences']['fontPref']['prefix'] : 'icon-'; $font_family = isset($font_data['preferences']['fontPref']['metadata']['fontFamily']) ? $font_data['preferences']['fontPref']['metadata']['fontFamily'] : 'icomoon'; // Generate a unique identifier for this font family version $time_id = time(); $collection_id = wp_generate_uuid4(); $custom_font_family = 'custom_font_' . $time_id; $font_files = array(); foreach ($font_data['icons'] as $icon) { $name = isset($icon['properties']['name']) ? sanitize_title($icon['properties']['name']) : ''; if ( '' === $name ) { continue; } $class_name = $prefix . $name; $label = ucwords(str_replace(array('-', '_'), ' ', $name)); $new_icons[$class_name] = $label; } // Move font files $font_dir = $this->get_upload_dir(); $fonts_source = dirname( $selection_file ) . '/fonts'; if ( is_dir($fonts_source) ) { $files = scandir($fonts_source); foreach ($files as $file) { $allowed_font_extensions = array('eot', 'svg', 'ttf', 'woff', 'woff2'); if ($file !== '.' && $file !== '..' && is_file($fonts_source . '/' . $file)) { $ext = pathinfo($file, PATHINFO_EXTENSION); if ( ! in_array(strtolower($ext), $allowed_font_extensions, true) ) { continue; } // Rename file to prevent caching $new_filename = 'font_' . $time_id . '.' . $ext; if ( $wp_filesystem->copy($fonts_source . '/' . $file, $font_dir . '/' . $new_filename, true) ) { $font_files[] = $new_filename; } } } } // Build custom CSS $css_content = "@font-face {\n"; $css_content .= " font-family: '" . $custom_font_family . "';\n"; $css_content .= " src: url('font_" . $time_id . ".woff') format('woff'),\n"; $css_content .= " url('font_" . $time_id . ".ttf') format('truetype');\n"; $css_content .= " font-weight: normal; font-style: normal; font-display: block;\n"; $css_content .= "}\n\n"; $css_content .= "[class^=\"$prefix\"], [class*=\" $prefix\"] {\n"; $css_content .= " font-family: '" . $custom_font_family . "' !important;\n"; $css_content .= " speak: never; font-style: normal; font-weight: normal; font-variant: normal; text-transform: none; line-height: 1;\n"; $css_content .= " -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;\n"; $css_content .= "}\n\n"; // Extract CSS rules from style.css if present in Icomoon zip $style_css = $this->find_import_file( $unzip_dir, 'style.css' ); if ( file_exists($style_css) ) { $style_content = file_get_contents($style_css); // Extract before/after definitions, including multicolor rules. // IcoMoon may place color declarations after content, so a simple // content-only regex would silently omit those icons. preg_match_all('/\.([A-Za-z0-9_-]+):(before|after)\s*\{([^}]*)\}/i', $style_content, $matches, PREG_SET_ORDER); $font_classes = array(); foreach ( $matches as $match ) { $class_name = $match[1]; $pseudo = $match[2]; $declarations = $match[3]; if ( ! preg_match('/content\s*:\s*["\']([^"\']+)["\']/i', $declarations, $content_match) ) { continue; } if ( ! isset($font_classes[$class_name]) ) { // Scope the font family to each icon class so multiple ZIP // fonts using the same prefix can coexist. $css_content .= "." . $class_name . " { font-family: '" . $custom_font_family . "' !important; }\n"; $font_classes[$class_name] = true; } $css_content .= "." . $class_name . ":" . $pseudo . " { content: \"" . $content_match[1] . "\";"; if ( preg_match('/color\s*:\s*([^;}]*)/i', $declarations, $color_match) ) { $css_content .= " color: " . trim($color_match[1]) . ";"; } $css_content .= " }\n"; } } $wp_filesystem->put_contents( $font_dir . '/custom_icons_' . $time_id . '.css', $css_content, FS_CHMOD_FILE ); // Save new icons to WP Options, resolving duplicates $existing_data = get_option('ablycuic_custom_data', array()); $merged_icons = isset($existing_data['icons']) ? $existing_data['icons'] : array(); // Keep every imported stylesheet so multiple ZIP collections work together. $css_file = 'custom_icons_' . $time_id . '.css'; $css_files = isset($existing_data['css_files']) && is_array($existing_data['css_files']) ? $existing_data['css_files'] : array(); // Migrate the previous single-file format when present. if ( ! empty($existing_data['css_file']) && ! in_array($existing_data['css_file'], $css_files, true) ) { $css_files[] = $existing_data['css_file']; } if ( ! in_array($css_file, $css_files, true) ) { $css_files[] = $css_file; } $existing_data['css_files'] = $css_files; $collections = isset($existing_data['collections']) && is_array($existing_data['collections']) ? $existing_data['collections'] : array(); $collections[] = array( 'id' => $collection_id, 'name' => $zip_name ? $zip_name : 'Colección importada', 'imported_at'=> current_time('timestamp'), 'css_file' => $css_file, 'font_files' => $font_files, 'icons' => array_keys($new_icons), ); $existing_data['collections'] = $collections; foreach ($new_icons as $class => $label) { // If the exact class already exists, we overwrite safely because it just updates the label/unicode $merged_icons[$class] = $label; } $existing_data['icons'] = $merged_icons; update_option('ablycuic_custom_data', $existing_data); } $wp_filesystem->delete($unzip_dir, true); wp_safe_redirect(admin_url('admin.php?page=ablycuic-settings&success=1')); exit; } public function handle_delete_collection() { if ( ! current_user_can('manage_options') ) { wp_die('No tienes permisos.'); } check_admin_referer('ablycuic_delete_collection_action', 'ablycuic_delete_nonce'); $collection_id = isset($_POST['collection_id']) ? sanitize_text_field(wp_unslash($_POST['collection_id'])) : ''; $custom_data = get_option('ablycuic_custom_data', array()); $collections = isset($custom_data['collections']) && is_array($custom_data['collections']) ? $custom_data['collections'] : array(); if ( 'legacy-imported-icons' === $collection_id && empty($collections) ) { $legacy_fonts = array(); foreach ( (array) glob($this->get_upload_dir() . '/font_*') as $legacy_font ) { $legacy_fonts[] = basename($legacy_font); } $collections[] = array( 'id' => 'legacy-imported-icons', 'icons' => array_keys((array) ($custom_data['icons'] ?? array())), 'css_files' => array_values(array_filter(array_map('sanitize_file_name', (array) $custom_data['css_files']))), 'font_files' => $legacy_fonts, ); } $remaining = array(); $removed_icons = array(); $removed_css = array(); $deleted_collection = false; $font_dir = $this->get_upload_dir(); foreach ( $collections as $collection ) { if ( ! is_array($collection) || empty($collection['id']) || $collection['id'] !== $collection_id ) { $remaining[] = $collection; continue; } $deleted_collection = true; $removed_icons = isset($collection['icons']) && is_array($collection['icons']) ? $collection['icons'] : array(); $collection_css = isset($collection['css_files']) && is_array($collection['css_files']) ? $collection['css_files'] : array(isset($collection['css_file']) ? $collection['css_file'] : ''); foreach ( $collection_css as $css_file ) { $removed_css[] = sanitize_file_name($css_file); } foreach ( (array) ($collection['font_files'] ?? array()) as $font_file ) { $font_file = sanitize_file_name($font_file); if ( $font_file && file_exists($font_dir . '/' . $font_file) ) { wp_delete_file($font_dir . '/' . $font_file); } } } $remaining_css = array(); foreach ( $remaining as $other_collection ) { $other_css = isset($other_collection['css_files']) && is_array($other_collection['css_files']) ? $other_collection['css_files'] : array(isset($other_collection['css_file']) ? $other_collection['css_file'] : ''); foreach ( $other_css as $css_file ) { $css_file = sanitize_file_name($css_file); if ( $css_file ) { $remaining_css[$css_file] = true; } } } foreach ( array_unique($removed_css) as $css_file ) { if ( isset($remaining_css[$css_file]) ) { continue; } if ( $css_file && file_exists($font_dir . '/' . $css_file) ) { wp_delete_file($font_dir . '/' . $css_file); } } $remaining_icons = array(); foreach ( $remaining as $other_collection ) { foreach ( (array) ($other_collection['icons'] ?? array()) as $icon_class ) { $remaining_icons[$icon_class] = true; } } if ( ! empty($removed_icons) && isset($custom_data['icons']) && is_array($custom_data['icons']) ) { foreach ( $removed_icons as $icon_class ) { if ( ! isset($remaining_icons[$icon_class]) ) { unset($custom_data['icons'][$icon_class]); } } } if ( $deleted_collection ) { $custom_data['collections'] = $remaining; $custom_data['css_files'] = array_values(array_filter(array_map('sanitize_file_name', array_keys($remaining_css)))); update_option('ablycuic_custom_data', $custom_data); } wp_safe_redirect(admin_url('admin.php?page=ablycuic-settings&deleted=1')); exit; } public function render_settings_page() { $show_success = sanitize_key( (string) filter_input( INPUT_GET, 'success', FILTER_SANITIZE_FULL_SPECIAL_CHARS ) ); $show_deleted = sanitize_key( (string) filter_input( INPUT_GET, 'deleted', FILTER_SANITIZE_FULL_SPECIAL_CHARS ) ); $show_error = sanitize_key( (string) filter_input( INPUT_GET, 'error', FILTER_SANITIZE_FULL_SPECIAL_CHARS ) ); ?>

Iconos actualizados correctamente.

Colección eliminada correctamente.

Error al procesar el archivo. Revisa que sea un .zip válido de IcoMoon.

Importar Iconos de IcoMoon (ZIP)

Sube el archivo .zip exportado de tu biblioteca de IcoMoon.io. Los iconos nuevos se sumarán a la colección actual.

get_upload_dir() . '/font_*') as $legacy_font ) { $legacy_fonts[] = basename($legacy_font); } $collections[] = array( 'id' => 'legacy-imported-icons', 'name' => 'Colección importada anteriormente', 'icons' => array_keys((array) ($custom_data['icons'] ?? array())), 'css_files' => array_values(array_filter(array_map('sanitize_file_name', (array) $custom_data['css_files']))), 'font_files' => $legacy_fonts, ); } if ( ! empty($collections) ) : ?>
Colecciones importadas

Configuración General

Slugs de los campos de texto ACF donde deseas inyectar el selector (separados por comas).

Enlaces CDN para cargar iconos adicionales (uno por línea). Font Awesome es opcional y no se incluye en este plugin; utiliza únicamente URLs y recursos para los que tengas autorización o licencia.

'ab-btn-primary', 'style' => 'background: #1e293b; border: 0; padding: 10px 20px; height: auto; line-height: 1; border-radius: 6px; font-weight: 600; color: #fff; cursor: pointer;']); ?>
Colección actual: iconos personalizados importados.
$cdn_url) { $url = trim($cdn_url); if ( ! empty($url) ) { wp_enqueue_style('ablycuic-cdn-' . $index, esc_url_raw($url), array(), ABLYCUIC_VERSION); } } } // Enqueue every compiled stylesheet imported from a ZIP. $custom_data = get_option('ablycuic_custom_data', array()); $css_files = isset($custom_data['css_files']) && is_array($custom_data['css_files']) ? $custom_data['css_files'] : array(); if ( ! empty($custom_data['css_file']) && ! in_array($custom_data['css_file'], $css_files, true) ) { $css_files[] = $custom_data['css_file']; } foreach ( $css_files as $index => $css_file ) { $css_file = sanitize_file_name($css_file); $css_path = $this->get_upload_dir() . '/' . $css_file; if ( file_exists($css_path) ) { wp_enqueue_style('ablycuic-custom-uploaded-' . $index, $this->get_upload_url() . '/' . rawurlencode($css_file), array(), filemtime($css_path)); } } } public function render_admin_page() { $active_tab = sanitize_key( (string) filter_input( INPUT_GET, 'tab', FILTER_SANITIZE_FULL_SPECIAL_CHARS ) ); $active_tab = $active_tab ? $active_tab : 'general'; wp_register_style('ablycuic-admin', false, array(), ABLYCUIC_VERSION); wp_enqueue_style('ablycuic-admin'); ?> .ablycuic-tabs { display: flex; gap: 0; margin: 20px 20px 0 0; border-bottom: 1px solid #cbd5e1; } .ablycuic-tab { display: inline-block; padding: 13px 20px; color: #64748b; font-size: 14px; font-weight: 600; text-decoration: none; border-bottom: 3px solid transparent; } .ablycuic-tab:hover, .ablycuic-tab:focus { color: #15803d; } .ablycuic-tab.is-active { color: #15803d; border-bottom-color: #16a34a; } .ab-settings-card { background: #fff; padding: 30px; border: 1px solid #e2e8f0; border-radius: 8px; flex: 1 1 45%; box-sizing: border-box; } .ab-imported-collections { margin-top: 24px; padding-top: 18px; border-top: 1px solid #e2e8f0; } .ab-imported-collections > strong { display: block; margin-bottom: 10px; color: #334155; font-size: 13px; } .ab-imported-collection { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 9px 0; border-top: 1px solid #f1f5f9; } .ab-imported-collection span, .ab-imported-collection small { display: block; } .ab-imported-collection span { color: #334155; font-size: 12px; } .ab-imported-collection small { margin-top: 2px; color: #94a3b8; font-size: 11px; } .ab-delete-collection { color: #b91c1c !important; border-color: #fecaca !important; background: #fff !important; } .ab-delete-collection:hover { color: #fff !important; border-color: #dc2626 !important; background: #dc2626 !important; } .ab-settings-card h3 { font-size: 16px; font-weight: 700; margin-top: 0; margin-bottom: 20px; color: #1e293b; border-bottom: 1px solid #f1f5f9; padding-bottom: 12px; } .ab-input-field { width: 100%; padding: 10px 14px; border: 1px solid #cbd5e1; border-radius: 6px; font-size: 13px; background-color: #fff; color: #334155; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); transition: border-color 0.2s, box-shadow 0.2s; box-sizing: border-box; } .ab-input-field:focus { border-color: #390789; outline: none; box-shadow: 0 0 0 3px rgba(190, 153, 96, 0.15); } .ab-btn-primary { background: #1e293b; color: #fff; border: 0; padding: 10px 20px; border-radius: 6px; font-weight: 600; font-size: 13px; cursor: pointer; transition: background 0.2s; display: inline-block; text-decoration: none; } .ab-btn-primary:hover { background: #0f172a; } .icon-grid-admin { display: grid; grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); gap: 15px; margin-top: 20px; } .icon-item-admin { background: #f8fafc; padding: 20px 10px; border-radius: 8px; border: 1px solid #e2e8f0; text-align: center; transition: all 0.2s ease; position: relative; } .icon-item-admin:hover { transform: translateY(-3px); box-shadow: 0 8px 16px rgba(0, 0, 0, 0.05); border-color: #390789; background: #fff; } .icon-item-admin i.main-icon { font-size: 32px; display: block; margin-bottom: 10px; color: #1e293b; } /* Copy button style */ .btn-copy-box { position: absolute; top: 10px; right: 10px; background: #fff; border: 1px solid #cbd5e1; padding: 6px 10px; border-radius: 6px; cursor: pointer; transition: all 0.2s ease; color: #475467; display: flex; align-items: center; justify-content: center; font-size: 11px; font-weight: 600; gap: 4px; } .btn-copy-box:hover { background: #1e293b; color: #fff; border-color: #1e293b; } .btn-copy-box .dashicons { font-size: 14px; width: 14px; height: 14px; margin-top: 2px; } .icon-item-admin .btn-copy-box { top: 8px; right: 8px; opacity: 0; padding: 5px; } .icon-item-admin:hover .btn-copy-box { opacity: 1; } #alpaca-toast { position: fixed; bottom: 30px; left: 50%; transform: translateX(-50%); background: #1e293b; color: #fff; padding: 12px 25px; border-radius: 50px; box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15); z-index: 99999; font-weight: 600; opacity: 0; pointer-events: none; transition: all 0.3s ease; } #alpaca-toast.show { opacity: 1; transform: translateX(-50%) translateY(-10px); } /* Modern Promotions & Our Plugins Styling */ .ab-promotions-wrapper { margin-top: 24px; box-sizing: border-box; } .ab-promotions-hero { background: #0f172a; border-radius: 12px; padding: 36px 40px; margin-bottom: 28px; color: #fff; box-sizing: border-box; } .ab-promotions-hero-badge { display: inline-flex; align-items: center; gap: 6px; background: rgba(22, 163, 74, 0.18); color: #4ade80; border: 1px solid rgba(34, 197, 94, 0.35); font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; padding: 4px 12px; border-radius: 20px; margin-bottom: 14px; } .ab-promotions-hero h2 { color: #ffffff; font-size: 26px; font-weight: 800; margin: 0 0 10px 0; line-height: 1.25; } .ab-promotions-hero p { color: #94a3b8; font-size: 14px; line-height: 1.6; margin: 0 0 24px 0; max-width: 780px; } .ab-promotions-hero-features { display: flex; flex-wrap: wrap; gap: 24px; padding-top: 20px; border-top: 1px solid #1e293b; } .ab-promotions-hero-feature { display: flex; align-items: center; gap: 8px; color: #e2e8f0; font-size: 13px; font-weight: 600; } .ab-promotions-hero-feature .dashicons { color: #22c55e; font-size: 18px; width: 18px; height: 18px; } .ab-promotions-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); gap: 24px; margin-bottom: 28px; } .ab-plugin-card { background: #ffffff; border: 1px solid #e2e8f0; border-radius: 12px; overflow: hidden; display: flex; flex-direction: column; transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease; } .ab-plugin-card:hover { transform: translateY(-2px); box-shadow: 0 10px 25px -5px rgba(15, 23, 42, 0.06); border-color: #cbd5e1; } .ab-plugin-card-header { padding: 28px 32px; border-bottom: 1px solid #f1f5f9; position: relative; } .ab-plugin-card-badge { position: absolute; top: 24px; right: 28px; background: #fef3c7; color: #92400e; border: 1px solid #fde68a; font-size: 11px; font-weight: 700; padding: 3px 10px; border-radius: 20px; text-transform: uppercase; letter-spacing: 0.03em; } .ab-plugin-card-icon { width: 52px; height: 52px; border-radius: 10px; display: flex; align-items: center; justify-content: center; margin-bottom: 16px; } .ab-plugin-card-icon.is-booking { background: #f0fdf4; color: #16a34a; border: 1px solid #bbf7d0; } .ab-plugin-card-icon.is-availability { background: #f0f9ff; color: #0284c7; border: 1px solid #bae6fd; } .ab-plugin-card-icon .dashicons { font-size: 26px; width: 26px; height: 26px; line-height: 1; } .ab-plugin-card-header h3 { margin: 0 0 4px 0; font-size: 20px; font-weight: 800; color: #0f172a; line-height: 1.3; } .ab-plugin-card-subtitle { display: block; color: #16a34a; font-size: 13px; font-weight: 700; margin-bottom: 12px; } .ab-plugin-card-subtitle.is-sky { color: #0284c7; } .ab-plugin-card-desc { margin: 0; color: #475569; font-size: 13.5px; line-height: 1.55; } .ab-plugin-card-body { padding: 24px 32px; flex-grow: 1; background: #fafafa; border-bottom: 1px solid #f1f5f9; } .ab-plugin-card-body h4 { margin: 0 0 16px 0; font-size: 12px; font-weight: 700; color: #64748b; text-transform: uppercase; letter-spacing: 0.05em; } .ab-plugin-feature-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 12px; } .ab-plugin-feature-item { display: flex; align-items: flex-start; gap: 10px; } .ab-plugin-feature-icon { color: #16a34a; font-size: 15px; font-weight: 800; flex-shrink: 0; margin-top: 1px; } .ab-plugin-feature-text { font-size: 13px; line-height: 1.45; color: #334155; } .ab-plugin-feature-text strong { color: #0f172a; font-weight: 700; } .ab-plugin-card-footer { padding: 20px 32px; background: #ffffff; display: flex; flex-direction: column; gap: 10px; } .ab-plugin-cta-btn { display: inline-flex; align-items: center; justify-content: center; gap: 8px; background: #16a34a; color: #ffffff !important; text-decoration: none !important; font-size: 14px; font-weight: 700; padding: 12px 20px; border-radius: 8px; border: 1px solid #15803d; transition: all 0.2s ease; text-align: center; } .ab-plugin-cta-btn:hover, .ab-plugin-cta-btn:focus { background: #15803d; color: #ffffff !important; transform: translateY(-1px); } .ab-plugin-cta-btn .dashicons { font-size: 18px; width: 18px; height: 18px; } .ab-plugin-guarantee { display: flex; align-items: center; justify-content: center; gap: 6px; font-size: 12px; color: #64748b; font-weight: 500; text-align: center; } .ab-custom-banner { background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 12px; padding: 28px 36px; display: flex; align-items: center; justify-content: space-between; gap: 24px; } .ab-custom-banner-info h3 { margin: 0 0 6px 0; font-size: 17px; font-weight: 800; color: #0f172a; } .ab-custom-banner-info p { margin: 0; font-size: 13px; color: #475569; line-height: 1.5; max-width: 650px; } .ab-custom-banner-btn { display: inline-flex; align-items: center; justify-content: center; gap: 8px; background: #0f172a; color: #ffffff !important; text-decoration: none !important; font-size: 13px; font-weight: 700; padding: 11px 20px; border-radius: 8px; transition: all 0.2s ease; white-space: nowrap; } .ab-custom-banner-btn:hover { background: #1e293b; color: #ffffff !important; } @media (max-width: 768px) { .ab-promotions-hero { padding: 28px 24px; } .ab-promotions-hero h2 { font-size: 22px; } .ab-promotions-hero-features { flex-direction: column; gap: 12px; } .ab-promotions-grid { grid-template-columns: 1fr; } .ab-plugin-card-header, .ab-plugin-card-body, .ab-plugin-card-footer { padding: 20px 24px; } .ab-custom-banner { flex-direction: column; align-items: flex-start; padding: 24px; } .ab-custom-banner-btn { width: 100%; } }
Ecosistema Ably Perú

Plugins Premiums para Agencias de Turismo

Diseñados y optimizados exclusivamente para empresas del sector viajes. Aumenta tus conversiones, automatiza tus reservas y brinda información en tiempo real con nuestras soluciones especializadas para WordPress.

100% Compatibles con WordPress & Gutenberg / Elementor
Código seguro de alto rendimiento y cero sobrecarga
Soporte técnico directo y personalizado en español
Más Popular

Booking System

Tours & Treks Booking System

Sistema integral de reservas que gestiona itinerarios, pasarelas de pago, adicionales de equipo (rentals) y comprobantes de compra dinámicos en una sola interfaz.

Características Clave

  • ✓
    Gestión de Reservas y Treks: Control total de pasajeros, salidas y disponibilidad.
  • ✓
    Pagos con Tarjetas & PayPal: Integración directa con Izipay y PayPal en soles y dólares.
  • ✓
    Rentals & Upgrades: Selección dinámica de bolsas de dormir, bastones y servicios extras.
  • ✓
    Generador de Invoices PDF: Envío automático de comprobantes e itinerarios al cliente.
  • ✓
    Seguridad Anti-Spam: Filtro inteligente con Cloudflare Turnstile nativo.
  • ✓
    Validación de Pagos en VIVO: Confirmación automática antes de procesar el registro de reserva.
  • ✓
    Copia de seguridad: Posee descargar copias de seguridad de las reservas.
Especializado

Inca Trail Availability

Disponibilidad en Tiempo Real

Plugin premium para mostrar la disponibilidad oficial de espacios en el Camino Inca (4 Días y 2 Días) mediante un calendario moderno, veloz y multilenguaje.

Características Clave

  • ✓
    Disponibilidad en Tiempo Real: Sincronización continua de cupos para Camino Inca 4D y 2D.
  • ✓
    Frontend Multilenguaje: Totalmente compatible con Polylang y WPML para sitios multi-idioma.
  • ✓
    Control de Calendario: Selección de mes inicial por defecto y navegación fluida entre meses.
  • ✓
    Alerta de Cierre en Febrero: Notificaciones informativas automatizadas por mantenimiento anual.
  • ✓
    Shortcodes y Gutenberg: Integración limpia en cualquier página o maquetador visual.

¿Necesitas un plugin o desarrollo personalizado para tu agencia?

En Ably Perú desarrollamos módulos a medida, integraciones con sistemas ERP, pasarelas de pago locales, motores de reserva y conexión con APIs turísticas.

Hablar con un Especialista

Custom Icons Manager

Custom Icons Manager
Active v2.0.4

Colección de iconos personalizados e integración nativa con ACF. Sube tus fuentes ZIP de IcoMoon y mapea tus campos dinámicamente.

render_settings_page(); ?>

Biblioteca de Iconos Disponibles

Haz clic en el icono para copiar el código HTML: <i class="..."></i>.
Nota: Si agregaste CDNs externos, sus iconos funcionarán en toda la web pero no aparecerán gráficamente en esta grilla automática a menos que los incluyas en un ZIP de IcoMoon.

No hay iconos registrados.

$data ) : $label_text = is_array($data) ? $data['label'] : $data; ?>
¡Copiado con éxito!
'; copyToClipboard(text); showToast('¡Icono copiado con éxito!', 'success'); }); function copyToClipboard(text) { var temp = $("