'
. ''
. ''
. '',
esc_attr( $stroke ),
esc_attr( $fill )
);
}
// ── Public API ─────────────────────────────────────────────────────────────
/**
* Render a view count component.
*
* @param int $style 1–4
* @param int $views Raw view count
* @param array $options {
* variant?: string 'outline' | 'filled' | 'accent' (Style 1 only)
* accent?: string CSS colour for accent variant, e.g. '#7c6fef'
* }
*/
public static function render( int $style, int $views, array $options = [] ): string {
self::maybe_enqueue_css();
$count = self::format_count( $views );
/**
* Filter: modify the formatted view count string before output.
*
* @param string $count e.g. "24.8K"
* @param int $views Raw integer
* @param int $style Style number 1–4
* @param array $options Render options
* @since 1.0.0
*/
$count = (string) apply_filters( 'pulseviews/format_count', $count, $views, $style, $options );
$variant = sanitize_key( $options['variant'] ?? 'outline' );
// Label: shortcode attr > global setting > default 'views'
$settings = get_option( 'pulseviews_settings', [] );
$label = sanitize_text_field(
$options['label']
?? $settings['views_label']
?? 'views'
);
// Trim and fallback
$label = '' !== trim( $label ) ? trim( $label ) : 'views';
return match ( $style ) {
2 => self::style_block( $count, $variant, $label ),
3 => self::style_inline( $count, $label ),
4 => self::style_compact( $count, $label ),
default => self::style_pill( $count, $variant, $label ),
};
}
// ── Style renderers ────────────────────────────────────────────────────────
/**
* Style 1 — Pill.
* Variant: 'outline' | 'filled' | 'accent'
*/
private static function style_pill( string $count, string $variant, string $label ): string {
$class = 'pv-pill';
$icon_color = '#111';
$icon_fill = '#111';
if ( 'filled' === $variant ) {
$class .= ' pv-pill-filled';
$icon_color = '#fff';
$icon_fill = '#fff';
} elseif ( 'accent' === $variant ) {
$class .= ' pv-pill-accent';
$icon_color = '#fff';
$icon_fill = '#fff';
}
$aria = $count . ' ' . $label;
return sprintf(
''
. ''
. '%s'
. '%s'
. '%s'
. ''
. '',
esc_attr( $class ),
esc_attr( $aria ),
self::icon_eye( $icon_color, $icon_fill ),
esc_html( $count ),
esc_html( $label )
);
}
/**
* Style 2 — Bold Block.
* Standalone high-contrast block with large number and label.
* Variant: 'filled' (black/white) | 'accent'
*/
private static function style_block( string $count, string $variant, string $label ): string {
$class = 'pv-block';
$icon_color = '#fff';
$icon_fill = '#fff';
if ( 'accent' === $variant ) {
$class .= ' pv-block-accent';
}
$aria = $count . ' ' . $label;
return sprintf(
''
. ''
. '%s%s'
. '%s'
. ''
. '',
esc_attr( $class ),
esc_attr( $aria ),
self::icon_eye( $icon_color, $icon_fill ),
esc_html( $count ),
esc_html( ucfirst( $label ) )
);
}
/**
* Style 3 — Inline.
* Pure text. Zero decoration. Inherits theme font and colour entirely.
*/
private static function style_inline( string $count, string $label ): string {
$aria = $count . ' ' . $label;
return sprintf(
''
. ''
. '%s'
. '%s'
. '%s'
. ''
. '',
esc_attr( $aria ),
self::icon_eye( 'currentColor', 'currentColor' ),
esc_html( $count ),
esc_html( $label )
);
}
/**
* Style 4 — Compact Badge.
* Icon + number only. Minimal border. Mobile-first.
*/
private static function style_compact( string $count, string $label ): string {
$aria = $count . ' ' . $label;
return sprintf(
''
. ''
. '%s'
. '%s'
. ''
. '',
esc_attr( $aria ),
self::icon_eye( 'currentColor', 'currentColor' ),
esc_html( $count )
);
}
// ── Helpers ────────────────────────────────────────────────────────────────
/**
* Format a raw count to a compact string.
* 999 → "999"
* 1,234 → "1.2K"
* 10,500 → "10K"
* 1,234,567→ "1.2M"
*/
public static function format_count( int $views ): string {
if ( $views >= 1_000_000 ) {
return number_format_i18n( $views / 1_000_000, 1 ) . 'M';
}
if ( $views >= 10_000 ) {
return number_format_i18n( $views / 1_000, 0 ) . 'K';
}
if ( $views >= 1_000 ) {
return number_format_i18n( $views / 1_000, 1 ) . 'K';
}
return number_format_i18n( $views );
}
private static function maybe_enqueue_css(): void {
if ( self::$css_enqueued || is_admin() ) {
return;
}
self::$css_enqueued = true;
wp_enqueue_style(
'pulseviews',
PULSEVIEWS_URL . 'assets/frontend/styles.css',
[],
PULSEVIEWS_VERSION
);
}
}