diff --git a/assets/css/frontend/widget-batch5.css b/assets/css/frontend/widget-batch5.css index 61d5b6d6..45c11701 100644 --- a/assets/css/frontend/widget-batch5.css +++ b/assets/css/frontend/widget-batch5.css @@ -1825,3 +1825,187 @@ font-weight: var(--font-weight-sm, 600); } } + +/* ===== Batch15: icon-card / icon-cover / buttons / yiyan / text-title ===== */ + +.icon-card { + text-align: center; + padding: 16px 10px; +} +.icon-card .card-icon { + display: inline-flex; + align-items: center; + justify-content: center; + line-height: 1; + color: var(--theme-color, var(--focus-color, #f04494)); +} +.icon-card .card-icon.toggle-radius { + width: 3.2em; + height: 3.2em; + border-radius: 50%; + background: rgba(240, 68, 148, 0.1); +} +.icon-card .card-icon img { + width: 1em; + height: 1em; + object-fit: contain; +} +.icon-card .main-color { + color: inherit; + text-decoration: none; +} +.icon-card .main-color:hover { + color: var(--theme-color, var(--focus-color, #f04494)); +} + +.icon-cover-card { + display: flex; + align-items: center; + gap: 0; + padding: 12px; + border-radius: 8px; + background: var(--surface-1, #fff); + border: 1px solid var(--color-line, #e5e7eb); + transition: box-shadow .2s ease, transform .2s ease; +} +a.main-color > .icon-cover-card:hover, +.icon-cover-card:hover { + box-shadow: 0 6px 18px rgba(0,0,0,.06); + transform: translateY(-1px); +} +.icon-cover-icon.badg.cir { + display: inline-flex; + align-items: center; + justify-content: center; + width: 2.4em; + height: 2.4em; + min-width: 2.4em; + border-radius: 50%; + background: rgba(59, 130, 246, 0.12); + color: #2563eb; + flex-shrink: 0; +} +.icon-cover-icon.radius4 { + border-radius: 10px !important; +} +.icon-cover-icon img { + width: 70%; + height: 70%; + object-fit: contain; +} +.icon-cover-desc { + min-width: 0; +} +.icon-cover-desc .font-bold { + font-weight: 700; +} + +/* Buttons wrap (zibll flex hh) */ +.widget-buttons .widget-buttons-wrap { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: var(--pc-gap, 12px); +} +.widget-buttons .widget-buttons-wrap.align-center { justify-content: center; } +.widget-buttons .widget-buttons-wrap.align-left { justify-content: flex-start; } +.widget-buttons .widget-buttons-wrap.align-right { justify-content: flex-end; } +.widget-buttons .widget-buttons-wrap .but { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 4px; + padding: 8px 16px; + border-radius: 6px; + text-decoration: none; + font-size: 14px; + line-height: 1.3; + background: var(--theme-color, #3b82f6); + color: #fff; + border: 1px solid transparent; + min-width: var(--pc-width, auto); +} +.widget-buttons .widget-buttons-wrap .but.radius { + border-radius: 999px; +} +.widget-buttons .widget-buttons-wrap .but.hollow { + background: transparent; + color: var(--theme-color, #3b82f6); + border-color: currentColor; +} +.widget-buttons .widget-buttons-wrap .but.c-blue { background: #3b82f6; } +.widget-buttons .widget-buttons-wrap .but.c-green { background: #22c55e; } +.widget-buttons .widget-buttons-wrap .but.c-yellow { background: #eab308; color: #1a1a1a; } +.widget-buttons .widget-buttons-wrap .but.c-red { background: #ef4444; } +.widget-buttons .widget-buttons-wrap .but.c-purple { background: #a855f7; } +.widget-buttons .widget-buttons-wrap .but.hollow.c-blue { color: #3b82f6; background: transparent; } +.widget-buttons .widget-buttons-wrap .but.hollow.c-green { color: #22c55e; background: transparent; } +.widget-buttons .widget-buttons-wrap .but.hollow.c-red { color: #ef4444; background: transparent; } +.widget-buttons .widget-buttons-wrap .but.hollow.c-purple { color: #a855f7; background: transparent; } +.widget-buttons .widget-buttons-wrap .but.hollow.c-yellow { color: #ca8a04; background: transparent; } +.widget-buttons.pc-size-small .but { padding: 4px 10px; font-size: 12px; } +.widget-buttons.pc-size-medium .but { padding: 6px 14px; font-size: 13px; } +.widget-buttons.pc-size-large .but { padding: 10px 18px; font-size: 15px; } +@media (max-width: 767px) { + .widget-buttons .widget-buttons-wrap { + gap: var(--m-gap, 6px); + } + .widget-buttons .widget-buttons-wrap .but { + min-width: var(--m-width, auto); + } + .widget-buttons.m-size-small .but { padding: 4px 10px; font-size: 12px; } + .widget-buttons.m-size-medium .but { padding: 6px 12px; font-size: 13px; } + .widget-buttons.m-size-large .but { padding: 8px 14px; font-size: 14px; } +} + +/* Yiyan shell */ +.yiyan-box.main-bg, +.yiyan-box-wrap .yiyan-box { + background: var(--surface-1, #fff); + border-radius: 8px; + box-shadow: 0 0 10px var(--main-shadow, rgba(116,116,116,.08)); + padding: 18px 16px; + text-align: center; +} +.yiyan-box .aksrt-yiyan, +.yiyan-box .yiyan { + margin: 0; +} + +/* Text title char animation inherits data-aksrt-animate */ +.aksrt-text-title__main > span[data-aksrt-animate] { + display: inline-block; +} + +/* Shared icon grid columns (reuse term-cards pattern) */ +.widget_aksrt_icon_cards .row.gutters-5, +.widget_aksrt_icon_cover_cards .row.gutters-5 { + display: flex; + flex-wrap: wrap; + margin: 0 -4px; +} +.widget_aksrt_icon_cards .row.gutters-5 > [class*="col-"], +.widget_aksrt_icon_cover_cards .row.gutters-5 > [class*="col-"] { + padding: 0 4px; + box-sizing: border-box; +} +.widget_aksrt_icon_cards .col-sm-12, +.widget_aksrt_icon_cover_cards .col-sm-12 { width: 100%; } +.widget_aksrt_icon_cards .col-sm-6, +.widget_aksrt_icon_cover_cards .col-sm-6 { width: 50%; } +.widget_aksrt_icon_cards .col-sm-4, +.widget_aksrt_icon_cover_cards .col-sm-4 { width: 33.333%; } +.widget_aksrt_icon_cards .col-sm-3, +.widget_aksrt_icon_cover_cards .col-sm-3 { width: 25%; } +.widget_aksrt_icon_cards .col-sm-2, +.widget_aksrt_icon_cover_cards .col-sm-2 { width: 16.666%; } +@media (max-width: 767px) { + .widget_aksrt_icon_cards .col-xs-12, + .widget_aksrt_icon_cover_cards .col-xs-12 { width: 100%; } + .widget_aksrt_icon_cards .col-xs-6, + .widget_aksrt_icon_cover_cards .col-xs-6 { width: 50%; } + .widget_aksrt_icon_cards .col-xs-4, + .widget_aksrt_icon_cover_cards .col-xs-4 { width: 33.333%; } + .widget_aksrt_icon_cards .col-xs-3, + .widget_aksrt_icon_cover_cards .col-xs-3 { width: 25%; } +} diff --git a/inc/bootstrap.php b/inc/bootstrap.php index b19a0bea..e8ec4665 100644 --- a/inc/bootstrap.php +++ b/inc/bootstrap.php @@ -49,6 +49,7 @@ require_once AKSRT_DIR . '/inc/helpers/aksrt-header-menu-buttons.php'; require_once AKSRT_DIR . '/inc/helpers/aksrt-runtime-win.php'; require_once AKSRT_DIR . '/inc/helpers/aksrt-identity.php'; require_once AKSRT_DIR . '/inc/helpers/aksrt-graphic-card.php'; +require_once AKSRT_DIR . '/inc/helpers/aksrt-icon-cards.php'; require_once AKSRT_DIR . '/inc/helpers/aksrt-user-card.php'; require_once AKSRT_DIR . '/inc/helpers/aksrt-rewards.php'; require_once AKSRT_DIR . '/inc/helpers/menu-decoration.php'; diff --git a/inc/helpers/aksrt-icon-cards.php b/inc/helpers/aksrt-icon-cards.php new file mode 100644 index 00000000..79ff2824 --- /dev/null +++ b/inc/helpers/aksrt-icon-cards.php @@ -0,0 +1,276 @@ + $args + * @param bool $echo + */ + function aksrt_icon_card(array $args = [], bool $echo = false): string { + $defaults = [ + 'type' => '', + 'class' => 'box-body nopw-sm', + 'icon' => '', + 'icon_size' => 0, + 'customize_icon' => '', + 'icon_image' => '', + 'link' => [ + 'url' => '', + 'target' => '', + ], + 'icon_radius' => false, + 'icon_color' => '', + 'icon_custom_color' => '', + 'icon_class' => '', + 'title' => '', + 'desc' => '', + ]; + $args = wp_parse_args($args, $defaults); + + $icon_fa = sanitize_text_field((string) $args['icon']); + $icon_img = esc_url_raw(trim((string) ($args['icon_image'] !== '' ? $args['icon_image'] : $args['customize_icon']))); + // customize_icon may be raw HTML (img) from older data. + $custom_html = ''; + if ($icon_img === '' && is_string($args['customize_icon']) && str_contains((string) $args['customize_icon'], '<')) { + $custom_html = wp_kses((string) $args['customize_icon'], [ + 'i' => ['class' => true, 'aria-hidden' => true], + 'img' => ['src' => true, 'alt' => true, 'class' => true, 'width' => true, 'height' => true, 'loading' => true], + 'span'=> ['class' => true], + ]); + } + if ($icon_fa === '' && $icon_img === '' && $custom_html === '') { + return ''; + } + + $link = $args['link']; + if (is_string($link)) { + $link = ['url' => $link, 'target' => '']; + } + if (!is_array($link)) { + $link = ['url' => '', 'target' => '']; + } + $url = esc_url(trim((string) ($link['url'] ?? ''))); + $target = sanitize_text_field((string) ($link['target'] ?? '')); + $tgt = $target !== '' ? ' target="' . esc_attr($target) . '" rel="noopener noreferrer"' : ''; + + $icon_color = sanitize_html_class((string) $args['icon_color']); + $icon_class_extra = sanitize_html_class((string) $args['icon_class']); + $radius = !empty($args['icon_radius']); + $icon_class = $radius + ? 'card-icon toggle-radius fa-3x ' . ($icon_class_extra !== '' ? $icon_class_extra : $icon_color) + : 'card-icon fa-4x ' . $icon_color; + if ($icon_class_extra !== '' && !$radius) { + $icon_class .= ' ' . $icon_class_extra; + } + + $style = ''; + $custom_color = sanitize_hex_color((string) $args['icon_custom_color']); + if (!$radius && $custom_color) { + $style = ' style="color:' . esc_attr($custom_color) . ';"'; + } + + if ($custom_html !== '') { + $icon_inner = $custom_html; + } elseif ($icon_img !== '') { + $icon_inner = ''; + } else { + if ($icon_fa !== '' && !str_contains($icon_fa, 'fa-') && !str_contains($icon_fa, ' ')) { + $icon_fa = 'fa fa-' . $icon_fa; + } + $icon_inner = ''; + } + + $icon = '' . $icon_inner . ''; + if ($url !== '') { + $icon = '' . $icon . ''; + } + $icon_size = (int) $args['icon_size']; + if ($icon_size > 0) { + $icon = '' . $icon . ''; + } + + $title = trim((string) $args['title']); + $desc = trim((string) $args['desc']); + if ($url !== '' && $title !== '') { + $title_html = '' . esc_html($title) . ''; + } else { + $title_html = $title !== '' ? esc_html($title) : ''; + } + + $body = ''; + if ($title_html !== '') { + $body .= '
' . $title_html . '
'; + } + if ($desc !== '') { + $body .= '
' . esc_html($desc) . '
'; + } + + $class = trim((string) $args['class'] . ' ' . (string) $args['type']); + $html = '
'; + $html .= $icon; + $html .= $body !== '' ? '
' . $body . '
' : ''; + $html .= '
'; + + if ($echo) { + echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + return ''; + } + return $html; + } +endif; + +if (!function_exists('aksrt_icon_cover_card')) : + /** + * Horizontal icon cover card (icon left, title/desc right). + * + * @param array $args + * @param bool $echo + */ + function aksrt_icon_cover_card(array $args = [], bool $echo = false): string { + $defaults = [ + 'class' => 'zib-widget', + 'icon' => '', + 'icon_size' => 25, + 'customize_icon' => '', + 'icon_image' => '', + 'link' => [ + 'url' => '', + 'target' => '', + ], + 'icon_radius' => false, + 'icon_color' => '', + 'icon_custom_color' => '', + 'icon_class' => '', + 'title' => '', + 'desc' => '', + ]; + $args = wp_parse_args($args, $defaults); + + $icon_fa = sanitize_text_field((string) $args['icon']); + $icon_img = esc_url_raw(trim((string) ($args['icon_image'] !== '' ? $args['icon_image'] : ''))); + $custom_html = ''; + if ($icon_img === '' && is_string($args['customize_icon']) && str_contains((string) $args['customize_icon'], '<')) { + $custom_html = wp_kses((string) $args['customize_icon'], [ + 'i' => ['class' => true, 'aria-hidden' => true], + 'img' => ['src' => true, 'alt' => true, 'class' => true, 'loading' => true], + 'span'=> ['class' => true], + ]); + } elseif ($icon_img === '' && is_string($args['customize_icon']) && filter_var((string) $args['customize_icon'], FILTER_VALIDATE_URL)) { + $icon_img = esc_url_raw((string) $args['customize_icon']); + } + if ($icon_fa === '' && $icon_img === '' && $custom_html === '') { + return ''; + } + + $link = $args['link']; + if (is_string($link)) { + $link = ['url' => $link, 'target' => '']; + } + if (!is_array($link)) { + $link = ['url' => '', 'target' => '']; + } + $url = esc_url(trim((string) ($link['url'] ?? ''))); + $target = sanitize_text_field((string) ($link['target'] ?? '')); + $tgt = $target !== '' ? ' target="' . esc_attr($target) . '" rel="noopener noreferrer"' : ''; + + $icon_class = trim('icon-cover-icon badg cir ' . sanitize_html_class((string) $args['icon_class']) . ' ' . sanitize_html_class((string) $args['icon_color'])); + if (!empty($args['icon_radius'])) { + $icon_class .= ' radius4'; + } + $icon_size = max(14, min(64, (int) $args['icon_size'])); + $style = 'font-size:' . $icon_size . 'px;'; + $custom_color = sanitize_hex_color((string) $args['icon_custom_color']); + if ($custom_color) { + $style .= 'color:' . $custom_color . ';'; + } + + if ($custom_html !== '') { + $icon_inner = $custom_html; + } elseif ($icon_img !== '') { + $icon_inner = ''; + } else { + if ($icon_fa !== '' && !str_contains($icon_fa, 'fa-') && !str_contains($icon_fa, ' ')) { + $icon_fa = 'fa fa-' . $icon_fa; + } + $icon_inner = ''; + } + $icon = '
' . $icon_inner . '
'; + + $title = trim((string) $args['title']); + $desc = trim((string) $args['desc']); + $text = ''; + if ($title !== '') { + $text .= '
' . esc_html($title) . '
'; + } + if ($desc !== '') { + $text .= '
' . esc_html($desc) . '
'; + } + + $html = ''; + if ($url !== '') { + $html .= ''; + } + $html .= '
'; + $html .= $icon; + $html .= $text !== '' ? '
' . $text . '
' : ''; + $html .= '
'; + if ($url !== '') { + $html .= '
'; + } + + if ($echo) { + echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + return ''; + } + return $html; + } +endif; + +if (!function_exists('aksrt_obs_animate_attrs')) : + /** + * Build class + attribute string for scroll-in animation (maps to data-aksrt-animate). + * + * @param string $animation fade|slideup|slidedown|slideright|slideleft|zoomin|zoomout|'' + * @param bool $repeat + * @param float|int $delay_s + * @return array{class:string,attr:string} + */ + function aksrt_obs_animate_attrs(string $animation, bool $repeat = false, $delay_s = 0): array { + $animation = sanitize_key($animation); + $map = [ + 'fade' => 'fadeIn', + 'slideup' => 'fadeInUp', + 'slidedown' => 'fadeInDown', + 'slideright' => 'fadeInRight', + 'slideleft' => 'fadeInLeft', + 'zoomin' => 'zoomIn', + 'zoomout' => 'fadeIn', + 'fadein' => 'fadeIn', + 'fadeinup' => 'fadeInUp', + ]; + if ($animation === '' || !isset($map[$animation]) && !in_array($animation, $map, true)) { + if (!isset($map[$animation])) { + return ['class' => '', 'attr' => '']; + } + } + $aksrt = $map[$animation] ?? $animation; + $delay_ms = (int) round(((float) $delay_s) * 1000); + $class = ' obs-animate ani-' . $animation; + $attr = ' data-aksrt-animate="' . esc_attr($aksrt) . '"'; + if ($delay_ms > 0) { + $attr .= ' data-aksrt-animate-delay="' . esc_attr((string) $delay_ms) . '"'; + } + if ($repeat) { + $attr .= ' data-animation-repeat="true"'; + } + return ['class' => $class, 'attr' => $attr]; + } +endif; diff --git a/src/AKSRT/Widgets/ButtonsWidget.php b/src/AKSRT/Widgets/ButtonsWidget.php index 20eb79b2..b28a6bb3 100644 --- a/src/AKSRT/Widgets/ButtonsWidget.php +++ b/src/AKSRT/Widgets/ButtonsWidget.php @@ -1,6 +1,6 @@ esc_html__('显示一组自定义按钮,支持外链跳转与样式。', 'aksrt'), + 'description' => esc_html__('显示一组自定义按钮,支持外链跳转、色系与入场动画。', 'aksrt'), + 'classname' => 'widget_aksrt_buttons', ]); } + /** + * @param array $args + * @param array $instance + */ public function widget($args, $instance): void { $buttons = isset($instance['buttons']) && is_array($instance['buttons']) ? $instance['buttons'] : []; - $layout = sanitize_key((string) ($instance['layout'] ?? 'horizontal')); - if (!in_array($layout, ['vertical', 'horizontal'], true)) { - $layout = 'horizontal'; + // Also accept zibll-style `items`. + if (!$buttons && isset($instance['items']) && is_array($instance['items'])) { + $buttons = $instance['items']; } + $align = sanitize_key((string) ($instance['align'] ?? 'center')); if (!in_array($align, ['left', 'center', 'right'], true)) { $align = 'center'; @@ -30,31 +36,94 @@ class ButtonsWidget extends \WP_Widget { $radius = !empty($instance['radius']); $hollow = !empty($instance['hollow']); $go_link = !empty($instance['go_link']); - $gap = max(4, min(30, (int) ($instance['gap'] ?? 10))); + $pc_gap = max(0, min(40, (int) ($instance['pc_gap'] ?? $instance['gap'] ?? 12))); + $m_gap = max(0, min(30, (int) ($instance['m_gap'] ?? 6))); + $pc_pad = sanitize_key((string) ($instance['pc_padding'] ?? 'large')); + $m_pad = sanitize_key((string) ($instance['m_padding'] ?? 'medium')); + if (!in_array($pc_pad, ['small', 'medium', 'large'], true)) { + $pc_pad = 'large'; + } + if (!in_array($m_pad, ['small', 'medium', 'large'], true)) { + $m_pad = 'medium'; + } + $anim = sanitize_key((string) ($instance['obs_animation'] ?? '')); + $anim_repeat = !empty($instance['animation_repeat']); + $pc_width = max(0, min(400, (int) ($instance['pc_width'] ?? 0))); + $m_width = max(0, min(400, (int) ($instance['m_width'] ?? 0))); - $items = []; + $but_class = 'but'; + if ($radius) { + $but_class .= ' radius'; + } + if ($hollow) { + $but_class .= ' hollow'; + } + + $buts = ''; + $key = 0; foreach ($buttons as $btn) { if (!is_array($btn)) { continue; } $text = sanitize_text_field((string) ($btn['text'] ?? '')); - $url = esc_url_raw(trim((string) ($btn['url'] ?? ''))); if ($text === '') { continue; } + $url = ''; + $target = ''; + if (is_array($btn['link'] ?? null)) { + $url = esc_url_raw(trim((string) ($btn['link']['url'] ?? ''))); + $target = sanitize_text_field((string) ($btn['link']['target'] ?? '')); + } else { + $url = esc_url_raw(trim((string) ($btn['url'] ?? ''))); + $target = !empty($btn['new_window']) || !empty($btn['target']) ? '_blank' : ''; + } if ($url !== '' && $go_link && function_exists('aksrt_go_link')) { $url = aksrt_go_link($url, true); } - $items[] = [ - 'text' => $text, - 'url' => $url, - 'style' => sanitize_key((string) ($btn['style'] ?? 'primary')), - 'icon' => sanitize_text_field((string) ($btn['icon'] ?? $btn['icon_left'] ?? '')), - 'icon_right' => sanitize_text_field((string) ($btn['icon_right'] ?? '')), - 'new_window' => !empty($btn['new_window']) || !empty($btn['target']), + if ($url === '') { + $url = 'javascript:void(0)'; + } + + $color = sanitize_html_class((string) ($btn['color'] ?? $btn['style'] ?? '')); + // Map legacy style keys to color classes. + $style_map = [ + 'primary' => 'c-blue', + 'secondary' => 'c-blue-2', + 'outline' => '', + 'text' => '', + 'pink' => 'c-red', + 'blue' => 'c-blue', + 'green' => 'c-green', + 'yellow' => 'c-yellow', + 'purple' => 'c-purple', ]; + if (isset($style_map[$color])) { + $color = $style_map[$color]; + } + $item_class = trim($but_class . ' ' . $color); + + $icon_left = sanitize_text_field((string) ($btn['icon'] ?? $btn['icon_left'] ?? '')); + $icon_right = sanitize_text_field((string) ($btn['icon_right'] ?? '')); + $left_html = $icon_left !== '' ? ' ' : ''; + $right_html = $icon_right !== '' ? ' ' : ''; + + $anim_pack = function_exists('aksrt_obs_animate_attrs') + ? aksrt_obs_animate_attrs($anim, $anim_repeat, $key * 0.15) + : ['class' => '', 'attr' => '']; + + $tgt = $target !== '' && strpos($url, 'javascript:') !== 0 + ? ' target="' . esc_attr($target) . '" rel="noopener noreferrer"' + : ''; + + $buts .= '
'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + $buts .= '' + . $left_html . esc_html($text) . $right_html . ''; + $buts .= '
'; + $key++; } - if (!$items) { + + if ($buts === '') { return; } @@ -64,36 +133,26 @@ class ButtonsWidget extends \WP_Widget { echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } - $wrap = 'widget-buttons widget-buttons--' . $layout . ' align-' . $align; - if ($radius) { - $wrap .= ' is-radius'; + $style_attr = ''; + if ($pc_width > 0) { + $style_attr .= '--pc-width:' . $pc_width . 'px;'; } - if ($hollow) { - $wrap .= ' is-hollow'; + if ($m_width > 0) { + $style_attr .= '--m-width:' . $m_width . 'px;'; } - echo '
'; - foreach ($items as $btn) { - $style = in_array($btn['style'], ['primary', 'secondary', 'outline', 'text', 'pink', 'blue', 'green', 'yellow', 'purple'], true) - ? $btn['style'] - : 'primary'; - $cls = 'widget-buttons__btn widget-buttons__btn--' . $style; - $href = $btn['url'] !== '' ? $btn['url'] : 'javascript:void(0)'; - $tgt = $btn['new_window'] && $btn['url'] !== '' ? ' target="_blank" rel="noopener noreferrer"' : ''; - echo ''; - if ($btn['icon'] !== '') { - echo ' '; - } - echo esc_html($btn['text']); - if ($btn['icon_right'] !== '') { - echo ' '; - } - echo ''; - } - echo '
'; + $style_attr .= '--pc-gap:' . $pc_gap . 'px;--m-gap:' . $m_gap . 'px;--btn-gap:' . $pc_gap . 'px;'; + + echo '
'; + echo '
'; + echo $buts; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo '
'; echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } + /** + * @param array $instance + */ public function form($instance): void { $buttons = isset($instance['buttons']) && is_array($instance['buttons']) ? $instance['buttons'] : []; while (count($buttons) < 4) { @@ -106,12 +165,6 @@ class ButtonsWidget extends \WP_Widget {

-

+

+ +

$btn) : ?>
" value="" placeholder=""> @@ -132,7 +196,7 @@ class ButtonsWidget extends \WP_Widget { " value="" placeholder=""> " value="" placeholder=""> @@ -141,6 +205,11 @@ class ButtonsWidget extends \WP_Widget { $new_instance + * @param array $old_instance + * @return array + */ public function update($new_instance, $old_instance): array { $buttons = []; if (isset($new_instance['buttons']) && is_array($new_instance['buttons'])) { @@ -166,14 +235,17 @@ class ButtonsWidget extends \WP_Widget { } } return [ - 'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')), - 'layout' => ($new_instance['layout'] ?? '') === 'vertical' ? 'vertical' : 'horizontal', - 'align' => in_array(($new_instance['align'] ?? ''), ['left', 'center', 'right'], true) ? $new_instance['align'] : 'center', - 'radius' => !empty($new_instance['radius']) ? 1 : 0, - 'hollow' => !empty($new_instance['hollow']) ? 1 : 0, - 'go_link' => !empty($new_instance['go_link']) ? 1 : 0, - 'gap' => max(4, min(30, (int) ($new_instance['gap'] ?? 10))), - 'buttons' => $buttons, + 'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')), + 'align' => in_array(($new_instance['align'] ?? ''), ['left', 'center', 'right'], true) ? $new_instance['align'] : 'center', + 'radius' => !empty($new_instance['radius']) ? 1 : 0, + 'hollow' => !empty($new_instance['hollow']) ? 1 : 0, + 'go_link' => !empty($new_instance['go_link']) ? 1 : 0, + 'gap' => max(4, min(30, (int) ($new_instance['gap'] ?? 12))), + 'pc_gap' => max(0, min(40, (int) ($new_instance['pc_gap'] ?? $new_instance['gap'] ?? 12))), + 'm_gap' => max(0, min(30, (int) ($new_instance['m_gap'] ?? 6))), + 'obs_animation' => sanitize_key((string) ($new_instance['obs_animation'] ?? '')), + 'animation_repeat'=> !empty($new_instance['animation_repeat']) ? 1 : 0, + 'buttons' => $buttons, ]; } } diff --git a/src/AKSRT/Widgets/IconCardsWidget.php b/src/AKSRT/Widgets/IconCardsWidget.php index 1237a020..c34988b2 100644 --- a/src/AKSRT/Widgets/IconCardsWidget.php +++ b/src/AKSRT/Widgets/IconCardsWidget.php @@ -1,6 +1,6 @@ esc_html__('图标在上、文案在下的竖版图标卡片网格。', 'aksrt'), + 'classname' => 'widget_aksrt_icon_cards', ]); } + /** + * @param array $args + * @param array $instance + */ public function widget($args, $instance): void { - $cards = isset($instance['cards']) && is_array($instance['cards']) ? $instance['cards'] : []; - $columns = max(1, min(4, (int) ($instance['columns'] ?? $instance['pc_row'] ?? 2))); - $m_cols = max(1, min(3, (int) ($instance['m_row'] ?? 2))); - $go_link = !empty($instance['go_link']); - $show_bg = !isset($instance['show_widget_bg']) || !empty($instance['show_widget_bg']); - $icon_size = max(18, min(48, (int) ($instance['icon_size'] ?? 28))); - - $valid = []; - foreach ($cards as $card) { - if (!is_array($card)) { - continue; - } - $title = sanitize_text_field((string) ($card['title'] ?? '')); - if ($title === '') { - continue; - } - $url = esc_url_raw(trim((string) ($card['link'] ?? $card['url'] ?? ''))); - if ($url !== '' && $go_link && function_exists('aksrt_go_link')) { - $url = aksrt_go_link($url, true); - } - $valid[] = [ - 'title' => $title, - 'icon' => sanitize_text_field((string) ($card['icon'] ?? '')), - 'desc' => sanitize_text_field((string) ($card['description'] ?? $card['desc'] ?? '')), - 'url' => $url, - ]; - } - if (!$valid) { + $cards = $this->normalize($instance['cards'] ?? []); + if (!$cards) { return; } + $pc_row = max(1, min(6, (int) ($instance['columns'] ?? $instance['pc_row'] ?? 2))); + $m_row = max(1, min(4, (int) ($instance['m_row'] ?? 2))); + $go_link = !empty($instance['go_link']); + $show_bg = !isset($instance['show_widget_bg']) || !empty($instance['show_widget_bg']); + $icon_size = max(0, min(64, (int) ($instance['icon_size'] ?? $instance['size'] ?? 0))); + $icon_radius = !empty($instance['icon_radius']); + $anim = sanitize_key((string) ($instance['obs_animation'] ?? '')); + $anim_repeat = !empty($instance['animation_repeat']); + echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped $title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base); if ($title !== '') { echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } - $style = sprintf('--ic-columns:%d;--ic-m-columns:%d;--ic-icon:%dpx;', $columns, $m_cols, $icon_size); - echo '
'; - foreach ($valid as $card) { - $tag = $card['url'] !== '' ? 'a' : 'div'; - $extra = $card['url'] !== '' ? ' href="' . esc_url($card['url']) . '" target="_blank" rel="noopener noreferrer"' : ''; - echo '<' . $tag . ' class="widget-icon-cards__item is-vertical"' . $extra . '>'; - if ($card['icon'] !== '') { - echo '
'; + $is_row = count($cards) > 1; + $pc_span = max(1, (int) floor(12 / $pc_row)); + $m_span = max(1, (int) floor(12 / $m_row)); + $row_class = 'col-sm-' . $pc_span . ($m_row > 1 ? ' col-xs-' . $m_span : ''); + + echo $show_bg ? '
' : '
'; + if ($is_row) { + echo '
'; + } + + $idx = 0; + foreach ($cards as $card) { + $url = (string) ($card['url'] ?? ''); + if ($url !== '' && $go_link && function_exists('aksrt_go_link')) { + $url = aksrt_go_link($url, true); } - echo '
'; - echo '
' . esc_html($card['title']) . '
'; - if ($card['desc'] !== '') { - echo '
' . esc_html($card['desc']) . '
'; + $card_args = [ + 'class' => $show_bg ? 'box-body nopw-sm' : 'mb20', + 'icon' => (string) ($card['icon'] ?? ''), + 'icon_image' => (string) ($card['icon_image'] ?? ''), + 'icon_size' => $icon_size > 0 ? (16 + $icon_size) : 0, + 'icon_radius' => $icon_radius || !empty($card['icon_radius']), + 'icon_color' => (string) ($card['icon_color'] ?? ''), + 'icon_class' => (string) ($card['icon_class'] ?? ''), + 'title' => (string) ($card['title'] ?? ''), + 'desc' => (string) ($card['desc'] ?? ''), + 'link' => [ + 'url' => $url, + 'target' => (string) ($card['target'] ?? ''), + ], + ]; + + $anim_pack = function_exists('aksrt_obs_animate_attrs') + ? aksrt_obs_animate_attrs($anim, $anim_repeat, $idx * 0.08) + : ['class' => '', 'attr' => '']; + + if ($is_row) { + echo '
'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } elseif ($anim_pack['class'] !== '') { + echo '
'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } - echo '
'; + + if (function_exists('aksrt_icon_card')) { + echo aksrt_icon_card($card_args); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + + if ($is_row || $anim_pack['class'] !== '') { + echo '
'; + } + $idx++; + } + + if ($is_row) { + echo '
'; } echo '
'; echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } + /** + * @param mixed $raw + * @return array> + */ + private function normalize($raw): array { + if (is_string($raw)) { + $decoded = json_decode($raw, true); + $raw = is_array($decoded) ? $decoded : []; + } + if (!is_array($raw)) { + return []; + } + $out = []; + foreach ($raw as $card) { + if (!is_array($card)) { + continue; + } + $title = sanitize_text_field((string) ($card['title'] ?? '')); + $icon = sanitize_text_field((string) ($card['icon'] ?? '')); + $icon_image = esc_url_raw(trim((string) ($card['icon_image'] ?? $card['customize_icon'] ?? ''))); + if ($title === '' && $icon === '' && $icon_image === '') { + continue; + } + $url = esc_url_raw(trim((string) ($card['link'] ?? $card['url'] ?? ''))); + if (is_array($card['link'] ?? null)) { + $url = esc_url_raw(trim((string) ($card['link']['url'] ?? ''))); + $target = sanitize_text_field((string) ($card['link']['target'] ?? '')); + } else { + $target = sanitize_text_field((string) ($card['target'] ?? '')); + } + $out[] = [ + 'title' => $title, + 'desc' => sanitize_text_field((string) ($card['description'] ?? $card['desc'] ?? '')), + 'icon' => $icon, + 'icon_image' => $icon_image, + 'icon_color' => sanitize_html_class((string) ($card['icon_color'] ?? '')), + 'icon_class' => sanitize_html_class((string) ($card['icon_class'] ?? '')), + 'url' => $url, + 'target' => $target, + ]; + if (count($out) >= 12) { + break; + } + } + return $out; + } + + /** + * @param array $instance + */ public function form($instance): void { - $cards = isset($instance['cards']) && is_array($instance['cards']) ? $instance['cards'] : []; + $cards = $this->normalize($instance['cards'] ?? []); while (count($cards) < 4) { - $cards[] = ['title' => '', 'icon' => '', 'link' => '', 'description' => '']; + $cards[] = ['title' => '', 'icon' => '', 'url' => '', 'desc' => '']; } ?>

@@ -89,56 +166,43 @@ class IconCardsWidget extends \WP_Widget {

+


$card) : ?>
" value="" placeholder=""> " value="" placeholder=""> - " value="" placeholder=""> - " value="" placeholder=""> + " value="" placeholder=""> + " value="" placeholder="">
$new_instance + * @param array $old_instance + * @return array + */ public function update($new_instance, $old_instance): array { - $cards = []; - if (isset($new_instance['cards']) && is_array($new_instance['cards'])) { - foreach ($new_instance['cards'] as $card) { - if (!is_array($card)) { - continue; - } - $title = sanitize_text_field((string) ($card['title'] ?? '')); - if ($title === '') { - continue; - } - $cards[] = [ - 'title' => $title, - 'icon' => sanitize_text_field((string) ($card['icon'] ?? '')), - 'link' => esc_url_raw(trim((string) ($card['link'] ?? ''))), - 'description' => sanitize_text_field((string) ($card['description'] ?? '')), - ]; - if (count($cards) >= 12) { - break; - } - } - } return [ 'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')), - 'columns' => max(1, min(4, (int) ($new_instance['columns'] ?? 2))), - 'm_row' => max(1, min(3, (int) ($new_instance['m_row'] ?? 2))), + 'columns' => max(1, min(6, (int) ($new_instance['columns'] ?? 2))), + 'm_row' => max(1, min(4, (int) ($new_instance['m_row'] ?? 2))), 'go_link' => !empty($new_instance['go_link']) ? 1 : 0, 'show_widget_bg' => !empty($new_instance['show_widget_bg']) ? 1 : 0, - 'cards' => $cards, + 'icon_radius' => !empty($new_instance['icon_radius']) ? 1 : 0, + 'obs_animation' => sanitize_key((string) ($new_instance['obs_animation'] ?? '')), + 'cards' => $this->normalize($new_instance['cards'] ?? []), ]; } } diff --git a/src/AKSRT/Widgets/WidgetIconCoverCards.php b/src/AKSRT/Widgets/WidgetIconCoverCards.php index 4aca3f14..f00c221d 100644 --- a/src/AKSRT/Widgets/WidgetIconCoverCards.php +++ b/src/AKSRT/Widgets/WidgetIconCoverCards.php @@ -1,6 +1,6 @@ $args + * @param array $instance + */ public function widget($args, $instance): void { $cards = $this->normalize($instance['cards'] ?? []); if (!$cards) { return; } - $cols = max(1, min(4, (int) ($instance['pc_row'] ?? 2))); - $m_cols = max(1, min(2, (int) ($instance['m_row'] ?? 1))); + $pc_row = max(1, min(4, (int) ($instance['pc_row'] ?? 2))); + $m_row = max(1, min(2, (int) ($instance['m_row'] ?? 1))); $icon_size = max(16, min(48, (int) ($instance['icon_size'] ?? 25))); $radius = !empty($instance['icon_radius4']); $go_link = !empty($instance['go_link']); + $show_bg = !empty($instance['show_widget_bg']); echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped $title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base); @@ -39,31 +44,49 @@ class WidgetIconCoverCards extends \WP_Widget { echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } - $style = sprintf('--aksrt-icc-pc:%d;--aksrt-icc-m:%d;--aksrt-icc-icon:%dpx;', $cols, $m_cols, $icon_size); - echo '
'; + $is_row = count($cards) > 1; + $pc_span = max(1, (int) floor(12 / $pc_row)); + $m_span = max(1, (int) floor(12 / $m_row)); + $row_class = 'col-sm-' . $pc_span . ($m_row > 1 ? ' col-xs-' . $m_span : ''); + + echo $show_bg ? '
' : '
'; + if ($is_row) { + echo '
'; + } + foreach ($cards as $card) { - $url = $card['url']; + $url = (string) ($card['url'] ?? ''); if ($url !== '' && $go_link && function_exists('aksrt_go_link')) { $url = aksrt_go_link($url, true); } - $tag = $url !== '' ? 'a' : 'div'; - $href = $url !== '' ? ' href="' . esc_url($url) . '"' . ($card['target'] !== '' ? ' target="' . esc_attr($card['target']) . '" rel="noopener noreferrer"' : '') : ''; - echo '<' . $tag . ' class="aksrt-icon-cover__item"' . $href . '>'; - echo '
'; - if ($card['icon_image'] !== '') { - echo ''; - } elseif ($card['icon'] !== '') { - echo ''; + $card_args = [ + 'class' => $show_bg ? 'padding-10' : 'zib-widget mb10', + 'icon' => (string) ($card['icon'] ?? ''), + 'icon_image' => (string) ($card['icon_image'] ?? ''), + 'icon_size' => $icon_size, + 'icon_class' => $radius ? 'radius4' : '', + 'icon_color' => (string) ($card['icon_color'] ?? ''), + 'title' => (string) ($card['title'] ?? ''), + 'desc' => (string) ($card['desc'] ?? ''), + 'link' => [ + 'url' => $url, + 'target' => (string) ($card['target'] ?? ''), + ], + ]; + + if ($is_row) { + echo '
'; } + if (function_exists('aksrt_icon_cover_card')) { + echo aksrt_icon_cover_card($card_args); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + if ($is_row) { + echo '
'; + } + } + + if ($is_row) { echo '
'; - echo '
'; - if ($card['title'] !== '') { - echo '
' . esc_html($card['title']) . '
'; - } - if ($card['desc'] !== '') { - echo '
' . esc_html($card['desc']) . '
'; - } - echo '
'; } echo '
'; @@ -94,6 +117,7 @@ class WidgetIconCoverCards extends \WP_Widget { continue; } $url = ''; + $target = ''; if (is_array($row['link'] ?? null)) { $url = esc_url_raw(trim((string) ($row['link']['url'] ?? ''))); $target = sanitize_text_field((string) ($row['link']['target'] ?? '')); @@ -106,6 +130,7 @@ class WidgetIconCoverCards extends \WP_Widget { 'desc' => sanitize_text_field((string) ($row['desc'] ?? '')), 'icon' => $icon, 'icon_image' => $icon_image, + 'icon_color' => sanitize_html_class((string) ($row['icon_color'] ?? '')), 'url' => $url, 'target' => $target, ]; @@ -116,6 +141,9 @@ class WidgetIconCoverCards extends \WP_Widget { return $out; } + /** + * @param array $instance + */ public function form($instance): void { $json = is_string($instance['cards'] ?? null) ? (string) $instance['cards'] @@ -144,20 +172,27 @@ class WidgetIconCoverCards extends \WP_Widget {


+

$new_instance + * @param array $old_instance + * @return array + */ public function update($new_instance, $old_instance): array { return [ - 'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')), - 'cards' => $this->normalize($new_instance['cards'] ?? []), - 'pc_row' => max(1, min(4, (int) ($new_instance['pc_row'] ?? 2))), - 'm_row' => max(1, min(2, (int) ($new_instance['m_row'] ?? 1))), - 'icon_size' => max(16, min(48, (int) ($new_instance['icon_size'] ?? 25))), - 'icon_radius4' => !empty($new_instance['icon_radius4']) ? 1 : 0, - 'go_link' => !empty($new_instance['go_link']) ? 1 : 0, + 'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')), + 'cards' => $this->normalize($new_instance['cards'] ?? []), + 'pc_row' => max(1, min(4, (int) ($new_instance['pc_row'] ?? 2))), + 'm_row' => max(1, min(2, (int) ($new_instance['m_row'] ?? 1))), + 'icon_size' => max(16, min(48, (int) ($new_instance['icon_size'] ?? 25))), + 'icon_radius4' => !empty($new_instance['icon_radius4']) ? 1 : 0, + 'show_widget_bg' => !empty($new_instance['show_widget_bg']) ? 1 : 0, + 'go_link' => !empty($new_instance['go_link']) ? 1 : 0, ]; } } diff --git a/src/AKSRT/Widgets/WidgetTextTitle.php b/src/AKSRT/Widgets/WidgetTextTitle.php index 76a64bac..5b208df7 100644 --- a/src/AKSRT/Widgets/WidgetTextTitle.php +++ b/src/AKSRT/Widgets/WidgetTextTitle.php @@ -1,6 +1,6 @@ esc_html__('自定义标题 + 副标题区块,用于模块分隔。', 'aksrt'), + 'description' => esc_html__('自定义标题 + 副标题区块,支持逐字入场动画。', 'aksrt'), 'classname' => 'widget_aksrt_text_title', ] ); } + /** + * @param array $args + * @param array $instance + */ public function widget($args, $instance): void { $title = trim((string) ($instance['title'] ?? '')); $desc = trim((string) ($instance['desc'] ?? '')); @@ -39,6 +43,9 @@ class WidgetTextTitle extends \WP_Widget { $desc_size = max(11, min(24, (int) ($instance['desc_size'] ?? 14))); $title_color = sanitize_hex_color((string) ($instance['title_color'] ?? '')) ?: ''; $desc_color = sanitize_hex_color((string) ($instance['desc_color'] ?? '')) ?: ''; + $anim = sanitize_key((string) ($instance['obs_animation'] ?? '')); + $anim_repeat = !empty($instance['animation_repeat']); + $icon_width = max(0, min(64, (int) ($instance['title_icon_width'] ?? 0))); echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped @@ -50,26 +57,63 @@ class WidgetTextTitle extends \WP_Widget { $desc_color !== '' ? '--aksrt-tt-desc-color:' . $desc_color . ';' : '' ); - echo '
'; - if ($title !== '') { - echo '
'; - if ($icon_left !== '') { - echo ''; + $icon_w_attr = $icon_width > 0 ? ' style="--icon-width:' . (int) $icon_width . 'px;"' : ''; + $left_html = $icon_left !== '' + ? '' + : ''; + $right_html = $icon_right !== '' + ? '' + : ''; + + $title_html = ''; + if ($title !== '' && $anim !== '' && function_exists('aksrt_obs_animate_attrs')) { + // Split title into per-char spans with staggered delay (zibll). + $chars = preg_split('//u', $title, -1, PREG_SPLIT_NO_EMPTY) ?: []; + $pieces = []; + if ($left_html !== '') { + $pieces[] = $left_html; } - echo '' . esc_html($title) . ''; - if ($icon_right !== '') { - echo ''; + foreach ($chars as $ch) { + $pieces[] = $ch; } - echo '
'; + if ($right_html !== '') { + $pieces[] = $right_html; + } + foreach ($pieces as $i => $piece) { + $pack = aksrt_obs_animate_attrs($anim, $anim_repeat, $i * 0.05); + $is_html = str_starts_with((string) $piece, '<'); + $inner = $is_html ? $piece : esc_html((string) $piece); + $title_html .= '' . $inner . ''; + } + } elseif ($title !== '') { + $title_html = $left_html . esc_html($title) . $right_html; } + + $desc_html = ''; if ($desc !== '') { - echo '
' . esc_html($desc) . '
'; + if ($anim !== '' && function_exists('aksrt_obs_animate_attrs')) { + $pack = aksrt_obs_animate_attrs($anim, $anim_repeat, 0.6); + $desc_html = '
' . esc_html($desc) . '
'; + } else { + $desc_html = '
' . esc_html($desc) . '
'; + } } + + echo '
'; + if ($title_html !== '') { + echo '
' . $title_html . '
'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + echo $desc_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo '
'; echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } + /** + * @param array $instance + */ public function form($instance): void { ?>

@@ -109,6 +153,16 @@ class WidgetTextTitle extends \WP_Widget {

+

+ +

'; + if (function_exists('aksrt_render_yiyan_block')) { aksrt_render_yiyan_block([ - 'class' => 'aksrt-yiyan', + 'class' => 'aksrt-yiyan yiyan', 'type' => $type, 'count' => $count, 'interval' => $interval, 'icon' => $icon, ]); } else { - // Fallback if single-render helpers are not loaded. ?> -
'; + echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } + /** + * @param array $instance + */ public function form($instance): void { - $title = !empty($instance['title']) ? $instance['title'] : '一言'; + $title = !empty($instance['title']) ? (string) $instance['title'] : '一言'; $count = !empty($instance['count']) ? (int) $instance['count'] : 5; $interval = !empty($instance['interval']) ? (int) $instance['interval'] : 5; $type = self::sanitize_type((string) ($instance['type'] ?? '')); - $icon = !empty($instance['icon']) ? $instance['icon'] : 'fas fa-quote-left'; + $icon = !empty($instance['icon']) ? (string) $instance['icon'] : 'fas fa-quote-left'; $labels = self::type_labels(); - // Curated subset for the form (full map still used for sanitization). $form_types = ['', 'a', 'd', 'i', 'k', 'h', 'j']; ?>

@@ -135,16 +155,37 @@ class WidgetYiyan extends \WP_Widget { name="get_field_name('icon')); ?>" type="text" value="">

+

+ +

+

+ +

$new_instance + * @param array $old_instance + * @return array + */ public function update($new_instance, $old_instance): array { - $instance = []; - $instance['title'] = sanitize_text_field($new_instance['title'] ?? ''); - $instance['count'] = max(1, min(10, absint($new_instance['count'] ?? 5))); - $instance['interval'] = max(3, min(30, absint($new_instance['interval'] ?? 5))); - $instance['type'] = self::sanitize_type((string) ($new_instance['type'] ?? '')); - $instance['icon'] = sanitize_text_field($new_instance['icon'] ?? 'fas fa-quote-left'); - return $instance; + return [ + 'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')), + 'count' => max(1, min(10, absint($new_instance['count'] ?? 5))), + 'interval' => max(3, min(30, absint($new_instance['interval'] ?? 5))), + 'type' => self::sanitize_type((string) ($new_instance['type'] ?? '')), + 'icon' => sanitize_text_field((string) ($new_instance['icon'] ?? 'fas fa-quote-left')), + 'in_affix' => !empty($new_instance['in_affix']) ? 1 : 0, + 'obs_animation' => sanitize_key((string) ($new_instance['obs_animation'] ?? '')), + 'animation_repeat' => !empty($new_instance['animation_repeat']) ? 1 : 0, + ]; } }