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;