diff --git a/inc/bootstrap.php b/inc/bootstrap.php
index aa2e6c2d..b19a0bea 100644
--- a/inc/bootstrap.php
+++ b/inc/bootstrap.php
@@ -48,6 +48,7 @@ require_once AKSRT_DIR . '/inc/helpers/aksrt-header-userbox.php';
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-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-graphic-card.php b/inc/helpers/aksrt-graphic-card.php
new file mode 100644
index 00000000..93952de6
--- /dev/null
+++ b/inc/helpers/aksrt-graphic-card.php
@@ -0,0 +1,276 @@
+ $args {
+ * @type string $type style-1|style-2|style-3|style-4|''.
+ * @type string $class Extra classes on .graphic.
+ * @type string $img Image URL.
+ * @type string $video Optional video URL (desktop).
+ * @type string $alt Image alt.
+ * @type array{url?:string,target?:string}|string $link Link.
+ * @type string $text1 Primary text.
+ * @type string $text2 Secondary text.
+ * @type string $text3 Tertiary / badge text (HTML allowed for icon+count).
+ * @type string $more Extra absolute HTML (badge etc).
+ * @type bool $lazy Use aksrt-lazy data-src.
+ * @type int|float $height_scale Padding-bottom % (0 = default CSS).
+ * @type int|float $mask_opacity 0–100 overlay opacity.
+ * @type string $hide pc|m|'' hide on device.
+ * }
+ * @param bool $echo Echo instead of return.
+ */
+ function aksrt_graphic_card(array $args = [], bool $echo = false): string {
+ $defaults = [
+ 'type' => '',
+ 'hide' => '',
+ 'class' => 'mb10',
+ 'img' => '',
+ 'video' => '',
+ 'alt' => __('图片', 'aksrt'),
+ 'link' => [
+ 'url' => '',
+ 'target' => '',
+ ],
+ 'text1' => '',
+ 'text2' => '',
+ 'text3' => '',
+ 'more' => '',
+ 'lazy' => true,
+ 'height_scale' => 0,
+ 'mask_opacity' => 0,
+ ];
+ $args = wp_parse_args($args, $defaults);
+
+ $img_url = trim((string) $args['img']);
+ $video_url = trim((string) $args['video']);
+ if ($img_url === '' && $video_url === '') {
+ return '';
+ }
+
+ // Device visibility.
+ $hide = sanitize_key((string) $args['hide']);
+ if ($hide === 'pc' || $hide === 'm') {
+ $is_mobile = function_exists('wp_is_mobile') ? wp_is_mobile() : false;
+ if ((!$is_mobile && $hide === 'pc') || ($is_mobile && $hide === 'm')) {
+ return '';
+ }
+ }
+
+ $type = sanitize_key((string) $args['type']);
+ if ($type !== '' && !in_array($type, ['style-1', 'style-2', 'style-3', 'style-4'], true)) {
+ // Accept bare "1"–"4".
+ if (preg_match('/^[1-4]$/', $type)) {
+ $type = 'style-' . $type;
+ } else {
+ $type = '';
+ }
+ }
+
+ $link = $args['link'];
+ if (is_string($link)) {
+ $link = ['url' => $link, 'target' => ''];
+ }
+ if (!is_array($link)) {
+ $link = ['url' => '', 'target' => ''];
+ }
+ $link_url = esc_url(trim((string) ($link['url'] ?? '')));
+ $link_target = sanitize_text_field((string) ($link['target'] ?? ''));
+
+ $lazy = !empty($args['lazy']);
+ $placeholder = get_template_directory_uri() . '/assets/img/placeholder.svg';
+ $media = '';
+ if ($video_url !== '' && !(function_exists('wp_is_mobile') && wp_is_mobile())) {
+ $poster = $img_url !== '' ? ' poster="' . esc_url($img_url) . '"' : '';
+ if ($lazy) {
+ $media = '';
+ } else {
+ $media = '';
+ }
+ } elseif ($img_url !== '') {
+ $alt = (string) ($args['alt'] !== '' ? $args['alt'] : __('图片', 'aksrt'));
+ if ($lazy) {
+ $media = '';
+ } else {
+ $media = '
';
+ }
+ }
+ if ($media === '') {
+ return '';
+ }
+
+ $mask_opacity = max(0, min(100, (int) $args['mask_opacity']));
+ $mask = $mask_opacity > 0
+ ? '