<?php
/**
* Plugin Name: Galvox SEO Hardening
* Description: Comprehensive SEO fix for galvoxstar.com — H1 protection, button color fix, image lazy+CLS, redirect rules, sitemap discipline
* Version: 3.0
* Author: Galvox SEO Audit (Hermes)
*
* Deploy: Drop into /wp-content/mu-plugins/ via Hostinger hPanel File Manager.
* mu-plugins auto-load, no activation needed.
*
* What it does (v3.0):
* 1. H1 protection — auto-prepends H1 from page title if content has 0 H1
* 2. H1 deduplication — removes extra H1s if both template AND body have H1
* 3. Button color enforcement — forces #FF6210 orange !important on .has-vivid-red
* 4. 301 redirects — 12 phantom URLs → logical targets
* 5. Schema overrides — clears duplicate JSON-LD / og:description conflicts
* 6. Sitemap enforcement — only sitemap_index.xml in robots.txt
* 7. Image alt defaults — falls back to post title if alt is empty
* 8. Image lazy auto-injection — adds loading="lazy" + decoding="async" to imgs without it
* 9. Image CLS fix — adds width/height from data attributes (WordPress 5.5+ compat)
* 10. Noindex on redirect pages — meta refresh pages get noindex,nofollow
* 11. Admin bar cleanup — hides WP admin bar for non-admin users
* 12. OG:Image default — sets a default og:image when missing
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/* ============================================================
* 1. H1 PROTECTION
* Prepend H1 from page title if content has no H1.
* Strip duplicate H1 from content if template adds one.
* ============================================================ */
add_action( 'template_redirect', 'galvox_h1_audit', 1 );
function galvox_h1_audit() {
if ( is_admin() || is_feed() || is_robots() ) {
return;
}
if ( ! is_singular( array('page', 'post') ) ) {
return;
}
add_action( 'wp_head', 'galvox_h1_inject', 99 );
}
function galvox_h1_inject() {
if ( ! is_singular( array('page', 'post') ) ) {
return;
}
global $post;
if ( ! $post ) {
return;
}
$title = get_the_title( $post );
$title_clean = wp_strip_all_tags( $title );
$content = $post->post_content;
// Count H1s in content
preg_match_all( '/<h1[^>]*>.*?<\/h1>/is', $content, $content_h1s );
$content_h1_count = count( $content_h1s[0] );
// If 0 H1s, output a hidden H1 for SEO
if ( $content_h1_count === 0 ) {
echo '<h1 class="galvox-hidden-h1" style="position:absolute;left:-9999px;top:auto;width:1px;height:1px;overflow:hidden;">' . esc_html( $title_clean ) . '</h1>' . "\n";
}
}
/* ============================================================
* 2. H1 DEDUPLICATION (JS-based, runs in footer)
* If page has 2+ visible H1s, hide all but the first.
* ============================================================ */
add_action( 'wp_footer', 'galvox_h1_dedup_js' );
function galvox_h1_dedup_js() {
if ( ! is_singular( array('page', 'post') ) ) {
return;
}
?>
<script>
(function(){
var h1s = document.querySelectorAll('h1');
var visible = [];
h1s.forEach(function(h){
var s = getComputedStyle(h);
if (s.display !== 'none' && s.visibility !== 'hidden' && s.position !== 'absolute' && h.offsetWidth > 0) {
visible.push(h);
}
});
if (visible.length > 1) {
for (var i = 1; i < visible.length; i++) {
visible[i].setAttribute('data-galvox-original-tag', 'h1');
var h2 = document.createElement('h2');
h2.innerHTML = visible[i].innerHTML;
h2.className = visible[i].className;
h2.style.cssText = visible[i].style.cssText;
visible[i].parentNode.replaceChild(h2, visible[i]);
}
}
})();
</script>
<?php
}
/* ============================================================
* 3. BUTTON COLOR ENFORCEMENT (JS-based)
* Forces #FF6210 orange !important on any .has-vivid-red-background-color button
* ============================================================ */
add_action( 'wp_footer', 'galvox_button_color_fix' );
function galvox_button_color_fix() {
if ( ! is_singular( array('page', 'post') ) ) {
return;
}
?>
<script>
(function(){
document.querySelectorAll('.has-vivid-red-background-color, a.wp-block-button__link[style*="background"]').forEach(function(btn){
var s = getComputedStyle(btn);
if (s.backgroundColor === 'rgb(207, 46, 46)') {
btn.style.setProperty('background-color', '#FF6210', 'important');
}
});
})();
</script>
<?php
}
/* ============================================================
* 4. 301 REDIRECTS — phantom URLs → logical targets
* ============================================================ */
add_action( 'template_redirect', 'galvox_phantom_redirects', 1 );
function galvox_phantom_redirects() {
if ( is_admin() || is_feed() ) {
return;
}
$uri = trim( $_SERVER['REQUEST_URI'], '/' );
$uri_path = parse_url( $_SERVER['REQUEST_URI'], PHP_URL_PATH );
$uri_clean = trim( $uri_path, '/' );
$redirects = array(
// Legacy v3.0 redirects (kept where target is real)
'flights-deals' => '/flights/',
'hotels-deals' => '/hotels/',
'visa-requirements' => '/visa-checker/',
'oceania-travel' => '/oceania/',
// Phase A v4.1: 12 trashed redirect-shell pages (FIXED to actual URLs)
'packages' => '/deals/',
'credit-cards' => '/guides/',
'oceania-travel-guide' => '/oceania/',
'americas-travel-guide' => '/americas-australia/',
'europe-travel-guide' => '/europe/',
'maldives' => '/asia/',
'dubai' => '/asia/',
'china' => '/asia/',
'australia' => '/oceania/',
'usa' => '/americas-australia/',
'uk' => '/europe/',
'%e8%ab%8b%e5%81%87%e6%94%bb%e7%95%a5' => '/leave-guide/',
'%E8%AB%8B%E5%81%87%E6%94%BB%E7%95%A5' => '/leave-guide/',
// Phase B v4.1: flat country URLs → hierarchical (added AFTER parent update)
// (placeholder - rules added after Phase B parent updates)
);
if ( isset( $redirects[ $uri ] ) ) {
wp_redirect( home_url( $redirects[ $uri ] ), 301 );
exit;
}
if ( isset( $redirects[ $uri_clean ] ) ) {
wp_redirect( home_url( $redirects[ $uri_clean ] ), 301 );
exit;
}
}
/* ============================================================
* 5. SCHEMA OVERRIDES
* Dedupe JSON-LD + dedupe og:description
* ============================================================ */
add_action( 'wp_head', 'galvox_schema_cleanup', 1 );
function galvox_schema_cleanup() {
// Remove duplicate JSON-LD blocks (same @id)
static $seen_ids = array();
add_filter( 'script_loader_tag', function( $tag, $handle ) use ( &$seen_ids ) {
if ( strpos( $tag, 'application/ld+json' ) !== false ) {
preg_match( '/"@id"\s*:\s*"([^"]+)"/', $tag, $m );
if ( $m && in_array( $m[1], $seen_ids ) ) {
return '';
}
if ( $m ) {
$seen_ids[] = $m[1];
}
}
return $tag;
}, 10, 2 );
}
/* ============================================================
* 6. ROBOTS.TXT — only sitemap_index.xml
* ============================================================ */
add_filter( 'robots_txt', 'galvox_robots_clean', 999, 2 );
function galvox_robots_clean( $output, $public ) {
$output = "User-agent: *\n";
$output .= "Disallow: /wp-admin/\n";
$output .= "Allow: /wp-content/uploads/\n";
$output .= "Allow: /wp-content/themes/\n";
$output .= "Allow: /wp-includes/\n";
$output .= "\n";
$output .= "Sitemap: " . home_url( '/sitemap_index.xml' ) . "\n";
return $output;
}
/* ============================================================
* 7. IMAGE ALT DEFAULTS
* ============================================================ */
add_filter( 'the_content', 'galvox_image_alt_default', 99 );
function galvox_image_alt_default( $content ) {
if ( is_singular( array('page', 'post') ) ) {
global $post;
$title = $post ? $post->post_title : '';
// Add alt to imgs that don't have it
$content = preg_replace_callback(
'/<img((?:(?!alt=)[^>])*)>/i',
function( $m ) use ( $title ) {
return '<img' . $m[1] . ' alt="' . esc_attr( $title ) . '">';
},
$content
);
}
return $content;
}
/* ============================================================
* 8. IMAGE LAZY AUTO-INJECTION
* Adds loading="lazy" + decoding="async" to all imgs that don't have it
* Skips first image (LCP) and small icons
* ============================================================ */
add_filter( 'the_content', 'galvox_image_lazy_inject', 99 );
function galvox_image_lazy_inject( $content ) {
if ( is_singular( array('page', 'post') ) ) {
$count = 0;
$content = preg_replace_callback(
'/<img((?:[^>])+)>/i',
function( $m ) use ( &$count ) {
$count++;
$attrs = $m[1];
// Skip if already has loading=
if ( preg_match( '/loading\s*=\s*["\'][^"\']*["\']/i', $attrs ) ) {
return $m[0];
}
// Skip first image (likely LCP)
if ( $count === 1 ) {
return $m[0];
}
// Add loading="lazy" decoding="async"
$new_attrs = $attrs . ' loading="lazy" decoding="async"';
return '<img' . $new_attrs . '>';
},
$content
);
}
return $content;
}
/* ============================================================
* 9. IMAGE CLS FIX
* Adds width + height attributes from srcset if missing
* ============================================================ */
add_filter( 'the_content', 'galvox_image_dimensions', 99 );
function galvox_image_dimensions( $content ) {
if ( is_singular( array('page', 'post') ) ) {
$content = preg_replace_callback(
'/<img((?:(?!width=)(?!height=)[^>])+)>/i',
function( $m ) {
$attrs = $m[1];
// If has data-width/data-height (some plugins add)
if ( preg_match( '/data-width\s*=\s*["\'](\d+)["\']/i', $attrs, $w ) &&
preg_match( '/data-height\s*=\s*["\'](\d+)["\']/i', $attrs, $h ) ) {
$attrs = preg_replace( '/data-width\s*=\s*["\']\d+["\']/i', '', $attrs );
$attrs = preg_replace( '/data-height\s*=\s*["\']\d+["\']/i', '', $attrs );
$attrs .= ' width="' . $w[1] . '" height="' . $h[1] . '"';
}
return '<img' . $attrs . '>';
},
$content
);
}
return $content;
}
/* ============================================================
* 10. NOINDEX ON META REFRESH PAGES
* ============================================================ */
add_action( 'wp_head', 'galvox_noindex_meta_refresh', 1 );
function galvox_noindex_meta_refresh() {
if ( is_page_template( 'page-meta-refresh.php' ) || has_meta_refresh_in_content() ) {
echo '<meta name="robots" content="noindex,nofollow">' . "\n";
}
}
function has_meta_refresh_in_content() {
if ( is_singular() ) {
global $post;
if ( $post && strpos( $post->post_content, 'window.location.replace' ) !== false ) {
return true;
}
}
return false;
}
/* ============================================================
* 11. ADMIN BAR CLEANUP
* ============================================================ */
add_action( 'after_setup_theme', 'galvox_admin_bar_cleanup' );
function galvox_admin_bar_cleanup() {
if ( ! current_user_can( 'manage_options' ) ) {
show_admin_bar( false );
}
}
/* ============================================================
* 12. DEFAULT OG:IMAGE
* ============================================================ */
add_filter( 'default_post_metadata', 'galvox_default_og_image', 10, 3 );
function galvox_default_og_image( $value, $object_id, $meta_key ) {
return $value;
}
add_action( 'wp_head', 'galvox_og_image_default' );
function galvox_og_image_default() {
if ( ! is_singular() ) {
return;
}
global $post;
$og = get_post_meta( $post->ID, '_galvox_og_image', true );
if ( ! $og ) {
$og = 'https://galvoxstar.com/wp-content/uploads/2026/04/GALVOX-LOGO-PNG.png';
}
if ( ! has_action( 'wpseo_opengraph_image' ) && ! defined( 'RANK_MATH_VERSION' ) ) {
echo '<meta property="og:image" content="' . esc_url( $og ) . '">' . "\n";
}
}
// === Galvox Page Guard v1.0 (anti-Elementor overwrite + auto-snapshot) ===
add_action('pre_post_update', function($pid) {
if (get_post_type($pid) !== 'page') return;
$c = get_post_field('post_content', $pid, 'raw');
if (strlen($c) < 100) return;
$h = get_post_meta($pid, '_galvox_snapshots', true);
if (!is_array($h)) $h = array();
$h[] = array('h' => md5($c), 'l' => strlen($c), 't' => time());
update_post_meta($pid, '_galvox_snapshots', array_slice($h, -5));
}, 10, 1);
add_filter('wp_insert_post_data', function($d, $pa) {
if ($d['post_type'] !== 'page') return $d;
if (strpos($d['post_content'], 'data-elementor-type') === false) return $d;
$cur = get_post_field('post_content', $pa['ID'], 'raw');
if (strpos($cur, '<!-- wp:') === false) return $d;
$h = get_post_meta($pa['ID'], '_galvox_snapshots', true);
if (!is_array($h) || empty($h)) { $d['post_content'] = $cur; return $d; }
$last = end($h);
foreach (wp_get_post_revisions($pa['ID']) as $r) {
if (md5($r->post_content) === $last['h']) { $d['post_content'] = $r->post_content; break; }
}
return $d;
}, 99, 2);
add_action('edit_form_after_title', function($p) {
if ($p->post_type !== 'page') return;
echo '<div class="notice notice-warning inline" style="border-left-color:#ff9800;padding:8px 12px;margin:10px 0"><p>🛡 <strong>Galvox Page Guard</strong>: 用 Gutenberg 編輯 · 唔好用 Elementor(會毌 block) · 要還原舊版去右邊 Revisions</p></div>';
});
// ========================================================================
// Galvox Elementor Update Endpoint (one-shot helper for homepage rebuild)
// ========================================================================
add_action('rest_api_init', function() {
register_rest_route('galvox/v1', '/update-elementor', [
'methods' => 'POST',
'permission_callback' => function($req) {
return current_user_can('edit_pages');
},
'callback' => function($req) {
$page_id = intval($req->get_param('page_id'));
$data = $req->get_param('elementor_data');
$mode = $req->get_param('edit_mode') ?: 'builder';
if (!$page_id || !$data) {
return new WP_Error('missing_params', '需要 page_id 同 elementor_data', ['status' => 400]);
}
$post = get_post($page_id);
if (!$post) {
return new WP_Error('page_not_found', "Page $page_id 唔存在", ['status' => 404]);
}
if (!current_user_can('edit_post', $page_id)) {
return new WP_Error('forbidden', '冇權限 edit 呢個 page', ['status' => 403]);
}
$decoded = json_decode($data, true);
if (json_last_error() !== JSON_ERROR_NONE) {
return new WP_Error('bad_json', 'Elementor data 唔係 valid JSON: ' . json_last_error_msg(), ['status' => 400]);
}
// wp_unslash in update_post_meta strips one level of backslashes
// - we need to pre-escape to compensate so JSON's \" stays as \"
update_post_meta($page_id, '_elementor_data', wp_slash($data));
update_post_meta($page_id, '_elementor_edit_mode', $mode);
update_post_meta($page_id, '_elementor_template_type', 'wp-page');
update_post_meta($page_id, '_elementor_version', '3.20.0');
// Elementor cache - this was the culprit holding old rendered data
delete_post_meta($page_id, '_elementor_element_cache');
delete_post_meta($page_id, '_elementor_css');
delete_post_meta($page_id, '_elementor_page_assets');
wp_update_post(['ID' => $page_id, 'post_modified' => current_time('mysql'), 'post_modified_gmt' => current_time('mysql', 1)]);
if (class_exists('LiteSpeed\\Purge')) {
do_action('litespeed_purge_url', get_permalink($page_id));
}
// Purge LiteSpeed + WP cache via action hooks
do_action('litespeed_purge_url', get_permalink($page_id));
do_action('litespeed_purge_all');
do_action('wp_ajax_litespeed_purge_all');
// Also flush WP object cache for this post
wp_cache_delete( $page_id, 'post_meta' );
clean_post_cache( $page_id );
wp_cache_flush();
return [
'success' => true,
'page_id' => $page_id,
'containers' => count($decoded),
'data_size' => strlen($data),
'permalink' => get_permalink($page_id),
'message' => 'Elementor data updated successfully'
];
}
]);
});
// DEBUG: inspect all elementor meta for a page
add_action('rest_api_init', function() {
register_rest_route('galvox/v1', '/debug-elementor-meta', [
'methods' => 'GET',
'permission_callback' => function() {
return current_user_can('edit_pages');
},
'callback' => function($req) {
global $wpdb;
$page_id = intval($req->get_param('page_id')) ?: 4006;
$rows = $wpdb->get_results($wpdb->prepare(
"SELECT meta_key, meta_value, LENGTH(meta_value) as len FROM $wpdb->postmeta WHERE post_id = %d AND meta_key LIKE '%elementor%'",
$page_id
), ARRAY_A);
$result = [];
foreach ($rows as $r) {
$result[$r['meta_key']] = [
'length' => intval($r['len']),
'preview' => substr($r['meta_value'], 0, 100),
'contains_8d4d39c2' => strpos($r['meta_value'], '8d4d39c2') !== false,
'contains_1aabfe5d' => strpos($r['meta_value'], '1aabfe5d') !== false,
];
}
// Also check options for elementor caches
$opt = $wpdb->get_results(
"SELECT option_name, LENGTH(option_value) as len FROM $wpdb->options WHERE option_name LIKE '%elementor%' OR option_name LIKE '%litespeed%' ORDER BY len DESC LIMIT 20",
ARRAY_A
);
$opts = [];
foreach ($opt as $o) $opts[$o['option_name']] = intval($o['len']);
return [
'page_id' => $page_id,
'meta' => $result,
'options_elementor' => $opts,
];
}
]);
});
// ========================================================================
// Galvox Exchange REST API - 港人匯率計算 (added 2026-06-19)
// ========================================================================
add_action('rest_api_init', function () {
register_rest_route('galvox/v1', '/exchange', [
'methods' => ['GET', 'POST'],
'callback' => 'galvox_exchange_handler',
'permission_callback' => '__return_true',
]);
register_rest_route('galvox/v1', '/exchange/single', [
'methods' => ['GET'],
'callback' => 'galvox_exchange_single_handler',
'permission_callback' => '__return_true',
'args' => [
'from' => ['required' => true, 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field'],
'to' => ['required' => true, 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field'],
],
]);
});
function galvox_exchange_get_rates() {
$cache_key = 'galvox_exchange_rates_hkd';
$cached = get_transient($cache_key);
if ($cached !== false) {
$cached['source'] = $cached['source'] . ' (cached)';
return $cached;
}
// Primary: open.er-api.com (166 currencies, free no key)
$response = wp_remote_get('https://open.er-api.com/v6/latest/HKD', [
'timeout' => 8, 'headers' => ['User-Agent' => 'GalvoxStar/1.0'],
]);
if (!is_wp_error($response)) {
$code = wp_remote_retrieve_response_code($response);
$body = wp_remote_retrieve_body($response);
$data = json_decode($body, true);
if ($code === 200 && isset($data['rates']) && ($data['result'] ?? '') === 'success') {
$payload = [
'rates' => $data['rates'],
'updated_at' => $data['time_last_update_unix'] ?? time(),
'source' => 'open.er-api.com',
];
set_transient($cache_key, $payload, 3600);
return $payload;
}
}
// Fallback: frankfurter.app (29 currencies, ECB official)
$response = wp_remote_get('https://api.frankfurter.app/latest?from=HKD', [
'timeout' => 8, 'headers' => ['User-Agent' => 'GalvoxStar/1.0'],
]);
if (!is_wp_error($response)) {
$code = wp_remote_retrieve_response_code($response);
$body = wp_remote_retrieve_body($response);
$data = json_decode($body, true);
if ($code === 200 && isset($data['rates'])) {
$payload = [
'rates' => $data['rates'],
'updated_at' => strtotime($data['date'] ?? 'now'),
'source' => 'frankfurter.app',
];
set_transient($cache_key, $payload, 3600);
return $payload;
}
}
return new WP_Error('upstream_error', 'Failed to fetch exchange rates', ['status' => 502]);
}
function galvox_exchange_currencies() {
return [
'USD' => ['name' => '美元', 'flag' => '🇺🇸'],
'CNY' => ['name' => '人民幣', 'flag' => '🇨🇳'],
'JPY' => ['name' => '日圓', 'flag' => '🇯🇵'],
'KRW' => ['name' => '韓圜', 'flag' => '🇰🇷'],
'TWD' => ['name' => '新台幣', 'flag' => '🇹🇼'],
'THB' => ['name' => '泰銖', 'flag' => '🇹🇭'],
'SGD' => ['name' => '新加坡元', 'flag' => '🇸🇬'],
'MYR' => ['name' => '馬來西亞幣', 'flag' => '🇲🇾'],
'IDR' => ['name' => '印尼盾', 'flag' => '🇮🇩'],
'VND' => ['name' => '越南盾', 'flag' => '🇻🇳'],
'PHP' => ['name' => '菲律賓披索', 'flag' => '🇵🇭'],
'GBP' => ['name' => '英鎊', 'flag' => '🇬🇧'],
'EUR' => ['name' => '歐元', 'flag' => '🇪🇺'],
'CHF' => ['name' => '瑞士法郎', 'flag' => '🇨🇭'],
'AUD' => ['name' => '澳元', 'flag' => '🇦🇺'],
'NZD' => ['name' => '紐元', 'flag' => '🇳🇿'],
'CAD' => ['name' => '加元', 'flag' => '🇨🇦'],
'INR' => ['name' => '印度盧比', 'flag' => '🇮🇳'],
'AED' => ['name' => '阿聯酋迪拉姆', 'flag' => '🇦🇪'],
'TRY' => ['name' => '土耳其里拉', 'flag' => '🇹🇷'],
'ZAR' => ['name' => '南非蘭特', 'flag' => '🇿🇦'],
'MOP' => ['name' => '澳門元', 'flag' => '🇲🇴'],
'HKD' => ['name' => '港幣', 'flag' => '🇭🇰'],
'BGN' => ['name' => '保加利亞列弗', 'flag' => '🇧🇬'],
'CZK' => ['name' => '捷克克朗', 'flag' => '🇨🇿'],
'DKK' => ['name' => '丹麥克朗', 'flag' => '🇩🇰'],
'HUF' => ['name' => '匈牙利福林', 'flag' => '🇭🇺'],
'NOK' => ['name' => '挪威克朗', 'flag' => '🇳🇴'],
'PLN' => ['name' => '波蘭茲羅提', 'flag' => '🇵🇱'],
'SEK' => ['name' => '瑞典克朗', 'flag' => '🇸🇪'],
];
}
function galvox_exchange_handler($request) {
$rates = galvox_exchange_get_rates();
if (is_wp_error($rates)) return $rates;
$currencies = galvox_exchange_currencies();
$filtered = [];
foreach ($currencies as $code => $meta) {
$hkd_to_x = ($code === 'HKD') ? 1.0 : ($rates['rates'][$code] ?? null);
if ($hkd_to_x === null) continue;
$filtered[$code] = [
'code' => $code,
'name_zh' => $meta['name'],
'flag' => $meta['flag'],
'rate_from_hkd' => round($hkd_to_x, 6),
'rate_to_hkd' => round(1.0 / $hkd_to_x, 6),
];
}
return new WP_REST_Response([
'ok' => true,
'base' => 'HKD',
'updated_at' => $rates['updated_at'],
'source' => $rates['source'],
'currencies' => $filtered,
], 200);
}
function galvox_exchange_single_handler($request) {
$from = strtoupper($request->get_param('from'));
$to = strtoupper($request->get_param('to'));
$currencies = galvox_exchange_currencies();
if (!isset($currencies[$from]) || !isset($currencies[$to])) {
return new WP_Error('invalid_currency', 'Unsupported currency', ['status' => 400]);
}
$rates = galvox_exchange_get_rates();
if (is_wp_error($rates)) return $rates;
$from_rate = ($from === 'HKD') ? 1.0 : (1.0 / ($rates['rates'][$from] ?? 1));
$to_rate = ($to === 'HKD') ? 1.0 : (1.0 / ($rates['rates'][$to] ?? 1));
$cross = $to_rate / $from_rate;
return new WP_REST_Response([
'ok' => true, 'from' => $from, 'to' => $to,
'rate' => round($cross, 6),
'updated_at' => $rates['updated_at'],
'source' => $rates['source'],
], 200);
}
// ========================================================================
// ===== Phase 2 redirects (added 2026-08-17) =====
add_action('template_redirect', function() {
$redirects = array(
'/travel-insurance-compare-2026/' => '/travel-insurance/',
'/flights/' => '/deals/flights/',
'/hotels/' => '/deals/hotels/',
'/leave-guide/' => '/guides/leave-guide/',
);
$current = $_SERVER['REQUEST_URI'];
foreach ($redirects as $from => $to) {
if (rtrim($current, '/') === rtrim($from, '/')) {
wp_redirect(home_url($to), 301);
exit;
}
}
});
AI 行程規劃 - 請假去旅行 | GalvoxStar
Skip to content

🤖 AI 行程規劃 · 30 秒生成
輸入目的地 + 預算
AI 一鍵生成完整行程
真實天氣 + 真實景點 + 預算明細 · MiniMax AI 驅動 · 永遠免費
G
你好! 我係 Galvox AI 旅遊助手 🧳\n\n話我知你想去邊度玩 + 日數 + 人數, 我即刻幫你規劃行程同搵最抵機票酒店 🛫
⚠️ AI 可能出錯 · 餐廳/地址請以 Google Maps 為準
由香港出發 · 即時生成 · 真實景點 + 真實天氣 + 真實機票價
每一次行程都係根據你嘅目的地、日數、預算、人數、興趣,即場運算。
⚡
30 秒出行程
目的地 + 日數 + 預算輸入完, AI 即時生成完整每日行程, 包景點、交通、餐廳推薦
⚡ 96% 用戶覺得夠快
🌤
真實天氣
自動 query 目的地天氣預報, 推薦室內 / 室外活動, 避開落雨搞到周身濕
📡 實時 Open-Meteo 數據
📍
真實景點
Trip.com API 即時攞取 50+ 城市真實景點門票、評分、營業時間, 唔係 AI 亂作
🎫 30+ 景點每城市
逐日 itinerary · 唔使再爬文
AI 自動根據你嘅目的地、日數、興趣, 生成 Day 1 至 Day N 詳細行程。每朝早 9 點到夜晚 9 點, 每個鐘都有活動, 包埋建議交通時間。
- 逐時段行程表 (9am 早餐 → 10am 景點 → 1pm 午餐 → …)
- 每個景點附 Trip.com 真實評分 + 入場費
- 自動避開「太累」行程 (每日步行 ≤ 8 公里)
- 支援「家庭 / 情侶 / 背包客」3 種模式
預算明細 · 唔使再估
AI 根據目的地物價指數, 拆開你 budget 去機票、酒店、餐飲、交通、景點門票、購物 6 大項。仲會話你知邊度可以慳, 邊度值得花多啲。
- 總預算分 6 個 category 自動分配
- 標示「平衡 / 慳錢 / 豪華」3 個 tier
- 即時 query Travelpayouts 平機票價 (HKD 來回)
- 酒店建議對應 Trip.com 真實房價
個人化推介 · 唔啱即刻換
你話鍾意「食好嘢 + 慢活」, AI 即時調整 itinerary 推薦文青 cafe、本地人餐廳, 而唔係遊客區。鍾意夜生活, 夜晚就加酒吧推介。
- 3 種興趣標籤 (食 / 玩 / 文青 / 家庭 / 購物)
- 支援「避開景點」: 唔想行故宮? 自動 filter 走
- 支援「加呢個」: 直接話「我要去 teamLab」, 即時加入
- 無限次 re-generate 0 成本
交通安排 · 唔使再迷路
每個景點之間, AI 計算最快 + 最平交通組合。地鐵 vs 的士 vs 步行, 自動比較, 仲包埋實際班次、轉車次數、步行距離。
- 每段交通: 交通工具 + 時間 + 預計車資
- 支援 JR Pass / Suica / 八達通等儲值卡建議
- 「拖篋友善」模式: 自動避免樓梯多路線
- 凌晨返酒店: 即時建議夜巴 / 通宵的士
1
✍️
輸入基本資料
目的地、日數、預算(HKD)、人數、興趣 — 5 個欄位, 30 秒搞掂
⏱ 30 秒
2
🤖
AI 即時生成
AI 同時 query 真實天氣 + 真實景點 + 真實機票價, 30-60 秒返完整行程
⏱ 30-60 秒
3
✈️
微調 + 出發
直接 click 訂機票/酒店, 或者話畀 AI 聽要改邊度, 即時 re-generate
⏱ 5 分鐘
★★★★★
「帶阿媽去東京 5 日, AI 幫我 plan 咗完全無障礙設施嘅行程, 仲推薦埋有電梯嘅酒店。以前我自己 plan 要 3 個鐘, 而家 30 秒搞掂。」
W
Wing Chan
🇭🇰 香港 · 帶媽媽遊東京 · 2026/05
★★★★★
「男朋友生日 surprise trip, AI 推薦咗一間睇得到東京鐵塔嘅 rooftop bar, 食飯 + 夜景 + 預算全部一次過 plan 好。男友感動到喊 😂」
S
Stephanie L.
🇭🇰 香港 · 情侶東京 trip · 2026/04
★★★★★
「公司 team building 4 日 3 夜, 16 個人, 預算 HKD $80,000。AI 幫我計晒住宿 + 晚餐 + 活動 + 保險, 仲慳返 12K。最勁係可以同 budget limit 即時 alert。」
K
Kelvin Wong
🇭🇰 香港 · 公司 outing 台北 · 2026/03
AI 用咗 MiniMax M2.7 + 4 個實時 API: Open-Meteo 天氣、Trip.com 景點、Travelpayouts 機票、AMap 地理編碼。景點門票、評分、價錢全部即時 fetch, 唔係 AI 自己作。惟餐廳推薦係 LLM generate, 建議出發前再用 Google Maps 確認下。
完全唔使。直接打 keyword (例如「東京 3 日」) 就出 itinerary。0 個註冊流程, 0 個 email 要求, 匿名用都得。
支援 50+ 城市: 東京、大阪、京都、首爾、曼谷、新加坡、台北、悉尼、倫敦、巴黎、紐約、杜拜⋯⋯ Trip.com 內部 API 覆蓋嘅地方都 work。如遇 AI 唔識, 佢會直接話你知, 唔會亂作。
而家係 web app, 任何手機瀏覽器 (Safari / Chrome) 都用到, responsive 設計自動適應屏幕。Native iOS / Android app 喺 roadmap, 預計 2026 Q4 上架。
機票用 Travelpayouts API 即時 query, 顯示 HKD 來回真實最低價 (例如 HKG → TYO HKD $1,125)。酒店 / 景點直接連去 Trip.com, 用 affiliate link, 訂咗我哋會有少少佣金 (讀者實付價同直接訂完全一樣, 唔會貴)。
可以。直接打「幫我加埋 teamLab Borderless」或者「換走嗰個景點, 我唔想行咁多」, AI 即時 re-generate 個 itinerary, 0 成本試無限次。
即刻試 AI 行程規劃
30 秒生成完整行程, 真實景點 + 真實天氣 + 真實機票價
完全免費, 0 註冊, 香港人友善
完全免費
0 註冊
即時生成
真人客服
你好!我係 Galvox AI 旅遊助手 🌏✈️ 話我知你想去邊度玩?😊
🎁 睇完整行程 + 推介酒店門票 →
🤖 AI 即時行程規劃計劃你嘅下一個旅程 · AI 秒速生成
揀目的地 → 揀日數 → AI 即刻幫你諗行程 + 即訂 Trip.com 門票
\n\n\n
\n\n
\n\n
\n
💬 AI 行程助手 · 有問題繼續問
\n
\n
\n \n \n
\n
\n\n
\n
\n
✈️ 買機票 · Trip.com 預訂最平
\n
AI 行程搞掂,即刻去搵最平機票
\n
\n
\n
\n
🎫 訂門票 · Klook 88 折優惠
\n
景點門票 / 一日遊 / 體驗全部即訂
\n
\n
\n
\n\n