diff --git a/bavarian-rank-engine/includes/Admin/AdminMenu.php b/bavarian-rank-engine/includes/Admin/AdminMenu.php
index 83cc86a..cecc2bd 100644
--- a/bavarian-rank-engine/includes/Admin/AdminMenu.php
+++ b/bavarian-rank-engine/includes/Admin/AdminMenu.php
@@ -75,6 +75,15 @@ class AdminMenu {
'bre-robots',
array( new RobotsPage(), 'render' )
);
+
+ add_submenu_page(
+ 'bavarian-rank',
+ __( 'GEO Quick Overview', 'bavarian-rank-engine' ),
+ __( 'GEO Block', 'bavarian-rank-engine' ),
+ 'manage_options',
+ 'bre-geo',
+ array( new GeoPage(), 'render' )
+ );
}
public function render_dashboard(): void {
diff --git a/bavarian-rank-engine/includes/Admin/GeoEditorBox.php b/bavarian-rank-engine/includes/Admin/GeoEditorBox.php
new file mode 100644
index 0000000..8b7ec8d
--- /dev/null
+++ b/bavarian-rank-engine/includes/Admin/GeoEditorBox.php
@@ -0,0 +1,231 @@
+ID );
+ $enabled = get_post_meta( $post->ID, GeoBlock::META_ENABLED, true );
+ $lock = (bool) get_post_meta( $post->ID, GeoBlock::META_LOCK, true );
+ $generated_at = get_post_meta( $post->ID, GeoBlock::META_GENERATED, true );
+ $prompt_addon = get_post_meta( $post->ID, GeoBlock::META_ADDON, true ) ?: '';
+ $global = SettingsPage::getSettings();
+ $has_api_key = ! empty( $global['api_keys'][ $global['provider'] ] ?? '' );
+
+ wp_nonce_field( 'bre_geo_save_' . $post->ID, 'bre_geo_nonce' );
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ trim( $parts[0] ),
+ 'a' => trim( $parts[1] ),
+ );
+ }
+ }
+ update_post_meta( $post_id, GeoBlock::META_FAQ, wp_json_encode( $faq, JSON_UNESCAPED_UNICODE ) );
+
+ if ( isset( $_POST['bre_geo_prompt_addon'] ) ) {
+ update_post_meta( $post_id, GeoBlock::META_ADDON, sanitize_textarea_field( wp_unslash( $_POST['bre_geo_prompt_addon'] ) ) );
+ }
+ }
+
+ public function enqueue( string $hook ): void {
+ if ( ! in_array( $hook, array( 'post.php', 'post-new.php' ), true ) ) {
+ return;
+ }
+ wp_enqueue_script(
+ 'bre-geo-editor',
+ BRE_URL . 'assets/geo-editor.js',
+ array( 'jquery' ),
+ BRE_VERSION,
+ true
+ );
+ }
+
+ public function ajax_generate(): void {
+ check_ajax_referer( 'bre_admin', 'nonce' );
+ if ( ! current_user_can( 'edit_posts' ) ) {
+ wp_send_json_error( __( 'Insufficient permissions.', 'bavarian-rank-engine' ) );
+ }
+
+ $post_id = absint( wp_unslash( $_POST['post_id'] ?? 0 ) );
+ if ( ! $post_id || ! get_post( $post_id ) ) {
+ wp_send_json_error( __( 'Post not found.', 'bavarian-rank-engine' ) );
+ }
+
+ $geo = new GeoBlock();
+ if ( $geo->generate( $post_id, true ) ) {
+ $meta = GeoBlock::getMeta( $post_id );
+ wp_send_json_success(
+ array(
+ 'summary' => $meta['summary'],
+ 'bullets' => $meta['bullets'],
+ 'faq' => $meta['faq'],
+ )
+ );
+ } else {
+ wp_send_json_error( __( 'Generation failed. Check API key and provider settings.', 'bavarian-rank-engine' ) );
+ }
+ }
+
+ public function ajax_clear(): void {
+ check_ajax_referer( 'bre_admin', 'nonce' );
+ if ( ! current_user_can( 'edit_posts' ) ) {
+ wp_send_json_error( __( 'Insufficient permissions.', 'bavarian-rank-engine' ) );
+ }
+
+ $post_id = absint( wp_unslash( $_POST['post_id'] ?? 0 ) );
+ if ( ! $post_id ) {
+ wp_send_json_error( 'Invalid post ID' );
+ }
+
+ delete_post_meta( $post_id, GeoBlock::META_SUMMARY );
+ delete_post_meta( $post_id, GeoBlock::META_BULLETS );
+ delete_post_meta( $post_id, GeoBlock::META_FAQ );
+ delete_post_meta( $post_id, GeoBlock::META_GENERATED );
+ wp_send_json_success();
+ }
+}
diff --git a/bavarian-rank-engine/includes/Admin/GeoPage.php b/bavarian-rank-engine/includes/Admin/GeoPage.php
new file mode 100644
index 0000000..651a5fa
--- /dev/null
+++ b/bavarian-rank-engine/includes/Admin/GeoPage.php
@@ -0,0 +1,89 @@
+ array( $this, 'sanitize' ) )
+ );
+ }
+
+ public function enqueue_assets( string $hook ): void {
+ if ( $hook !== 'bavarian-rank_page_bre-geo' ) {
+ return;
+ }
+ wp_enqueue_style( 'bre-admin', BRE_URL . 'assets/admin.css', array(), BRE_VERSION );
+ }
+
+ public function sanitize( mixed $input ): array {
+ $input = is_array( $input ) ? $input : array();
+ $clean = array();
+
+ $clean['enabled'] = ! empty( $input['enabled'] );
+ $clean['regen_on_update'] = ! empty( $input['regen_on_update'] );
+ $clean['minimal_css'] = ! empty( $input['minimal_css'] );
+ $clean['allow_prompt_addon'] = ! empty( $input['allow_prompt_addon'] );
+
+ $allowed_modes = array( 'auto_on_publish', 'manual_only', 'hybrid' );
+ $clean['mode'] = in_array( $input['mode'] ?? '', $allowed_modes, true )
+ ? $input['mode'] : 'auto_on_publish';
+
+ $allowed_positions = array( 'after_first_p', 'top', 'bottom' );
+ $clean['position'] = in_array( $input['position'] ?? '', $allowed_positions, true )
+ ? $input['position'] : 'after_first_p';
+
+ $allowed_styles = array( 'details_collapsible', 'open_always', 'store_only_no_frontend' );
+ $clean['output_style'] = in_array( $input['output_style'] ?? '', $allowed_styles, true )
+ ? $input['output_style'] : 'details_collapsible';
+
+ $allowed_schemes = array( 'auto', 'light', 'dark' );
+ $clean['color_scheme'] = in_array( $input['color_scheme'] ?? 'auto', $allowed_schemes, true )
+ ? $input['color_scheme'] : 'auto';
+ $clean['accent_color'] = sanitize_hex_color( $input['accent_color'] ?? '' ) ?? '';
+
+ $clean['title'] = sanitize_text_field( $input['title'] ?? 'Quick Overview' );
+ $clean['label_summary'] = sanitize_text_field( $input['label_summary'] ?? 'Summary' );
+ $clean['label_bullets'] = sanitize_text_field( $input['label_bullets'] ?? 'Key Points' );
+ $clean['label_faq'] = sanitize_text_field( $input['label_faq'] ?? 'FAQ' );
+ $clean['custom_css'] = sanitize_textarea_field( $input['custom_css'] ?? '' );
+ $clean['prompt_default'] = sanitize_textarea_field(
+ ! empty( $input['prompt_default'] ) ? $input['prompt_default'] : GeoBlock::getDefaultPrompt()
+ );
+ $clean['word_threshold'] = max( 50, (int) ( $input['word_threshold'] ?? 350 ) );
+
+ $all_post_types = array_keys( get_post_types( array( 'public' => true ) ) );
+ $clean['post_types'] = array_values(
+ array_intersect(
+ array_map( 'sanitize_key', (array) ( $input['post_types'] ?? array() ) ),
+ $all_post_types
+ )
+ );
+ if ( empty( $clean['post_types'] ) ) {
+ $clean['post_types'] = array( 'post', 'page' );
+ }
+
+ return $clean;
+ }
+
+ public function render(): void {
+ if ( ! current_user_can( 'manage_options' ) ) {
+ return;
+ }
+ $settings = GeoBlock::getSettings();
+ $post_types = get_post_types( array( 'public' => true ), 'objects' );
+ include BRE_DIR . 'includes/Admin/views/geo.php';
+ }
+}