$data Payload from PGM_Ai_Visibility_Collector. * * @return void */ public function render( array $data ): void { $trendline = is_array( $data['trendline'] ?? null ) ? $data['trendline'] : array(); $standings = is_array( $data['standings'] ?? null ) ? $data['standings'] : array(); $last_run_at = isset( $data['last_run_at'] ) && $data['last_run_at'] ? esc_html( (string) $data['last_run_at'] ) : esc_html__( 'Never', 'peptide-geo-monitor' ); $mtd_cost = isset( $data['mtd_cost_usd'] ) ? (float) $data['mtd_cost_usd'] : 0.0; $cap = isset( $data['monthly_cap_usd'] ) ? (float) $data['monthly_cap_usd'] : PGM_DEFAULT_MONTHLY_BUDGET_USD; $this->render_proxy_note(); $this->render_meta_bar( $last_run_at, $mtd_cost, $cap ); $this->render_trendline( $trendline ); $this->render_standings( $standings ); } /** * {@inheritDoc} */ public function get_title(): string { return __( 'AI Visibility', 'peptide-geo-monitor' ); } /** * Render the proxy-metric disclaimer note. * * @return void */ private function render_proxy_note(): void { echo '

'; echo '' . esc_html__( 'Note:', 'peptide-geo-monitor' ) . ' '; echo esc_html__( 'Scores are measured via API probes — a directional proxy, not the consumer ChatGPT/Gemini apps (different retrieval, personalisation, and system prompts).', 'peptide-geo-monitor' ); echo '

'; } /** * Render the run metadata bar (last run, cost, cap). * * @param string $last_run_at Formatted last-run timestamp or "Never". * @param float $mtd_cost Month-to-date spend in USD. * @param float $cap Monthly cap in USD. * * @return void */ private function render_meta_bar( string $last_run_at, float $mtd_cost, float $cap ): void { $pct = $cap > 0 ? min( 100.0, round( $mtd_cost / $cap * 100, 1 ) ) : 0; echo '
'; echo '' . esc_html__( 'Last run:', 'peptide-geo-monitor' ) . ' ' . esc_html( $last_run_at ) . ''; echo '' . esc_html__( 'MTD cost:', 'peptide-geo-monitor' ) . ' $' . esc_html( number_format( $mtd_cost, 4 ) ) . ' / $' . esc_html( number_format( $cap, 2 ) ) . ' (' . esc_html( $pct ) . '% of cap)'; echo '
'; } /** * Render the Chart.js visibility score trendline. * * @param array $trendline * * @return void */ private function render_trendline( array $trendline ): void { echo '

' . esc_html__( 'Visibility Score — Trend', 'peptide-geo-monitor' ) . '

'; if ( empty( $trendline ) ) { echo '

' . esc_html__( 'No probe runs recorded yet. Click "Run now" to collect the first data point.', 'peptide-geo-monitor' ) . '

'; return; } $labels = array(); $scores = array(); foreach ( $trendline as $point ) { $labels[] = esc_js( date( 'M j', strtotime( $point['captured_at'] ) ) ); $scores[] = (float) $point['score']; } $labels_json = wp_json_encode( $labels ); $scores_json = wp_json_encode( $scores ); echo ''; echo ''; echo ''; } /** * Render the per-peptide current standings table. * * @param array $standings * * @return void */ private function render_standings( array $standings ): void { echo '

' . esc_html__( 'Current Standings', 'peptide-geo-monitor' ) . '

'; if ( empty( $standings ) ) { echo '

' . esc_html__( 'No standings data yet.', 'peptide-geo-monitor' ) . '

'; return; } echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; foreach ( $standings as $row ) { $cited_icon = $row['cited'] ? '✅' : '❌'; $position = null !== $row['our_position'] ? '#' . (int) $row['our_position'] : '—'; $domains = implode( ', ', array_map( 'esc_html', (array) $row['top_domains'] ) ); echo ''; echo ''; echo ''; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- only emoji echo ''; echo ''; echo ''; } echo '
' . esc_html__( 'Peptide', 'peptide-geo-monitor' ) . '' . esc_html__( 'Cited?', 'peptide-geo-monitor' ) . '' . esc_html__( 'Our Position', 'peptide-geo-monitor' ) . '' . esc_html__( 'Top Competing Domains', 'peptide-geo-monitor' ) . '
' . esc_html( (string) $row['label'] ) . '' . $cited_icon . '' . esc_html( $position ) . '' . wp_kses_post( $domains ) . '
'; } }