<?php
// file: client/index.php
// 前端路由：根据域名找site_id，?p=home/article路由

defined('IN_SITE') || define('IN_SITE', true);
require __DIR__ . '/config.php';
require __DIR__ . '/common.php';
require __DIR__ . '/marketing_widget.php';
require_once __DIR__ . '/city_module.php';
require_once __DIR__ . '/single_page_injector.php';

/**
 * 将营销组件注入到HTML的</body>前，而非输出到文档之外
 * @param string $html 主HTML内容
 * @param int $site_id 站点ID
 * @return string 注入后的HTML
 */
function inject_marketing_into_html($html, $site_id) {
    // 网站地图锚文本归一化：AI 可能把 {SITEMAP_LINK} 同时写进 href 和链接文字
    // （<a href="{SITEMAP_LINK}">{SITEMAP_LINK}</a>），占位符替换后链接文字变成 /sitemap_1.xml
    // 这类 URL，前台底部显示一串地址而不是"网站地图"。此处处于所有页面最终汇聚点、占位符已替换，
    // 把"href 指向 sitemap 且锚文本本身也是 sitemap 链接/占位符"的 <a> 文字统一改为"网站地图"，href 不动。
    // /i 使 /sitemap_1.xml 与 {SITEMAP_LINK} 均命中；中文锚文本"网站地图"不含 sitemap 字母，不会被误伤。
    if (stripos($html, 'sitemap') !== false) {
        $html = preg_replace(
            '#<a\b([^>]*\bhref\s*=\s*["\'][^"\']*sitemap[^"\']*["\'][^>]*)>\s*[^\s<]*sitemap[^\s<]*\s*</a>#i',
            '<a$1>网站地图</a>',
            $html
        );
    }

    ob_start();
    render_marketing_component($site_id);
    $marketing_html = ob_get_clean();

    // 底部/浮动联系条（复用 contact_phone / contact_wechat）
    $contact_bar_html = '';
    if (function_exists('render_bottom_contact_bar')) {
        $site = isset($GLOBALS['site']) ? $GLOBALS['site'] : null;
        if ($site) {
            $contact_bar_html = render_bottom_contact_bar($site);
        } elseif ($site_id) {
            // 降级：按 site_id 查站点（避免 $site 未设置时拿不到）
            $contact_bar_html = render_bottom_contact_bar(['id' => $site_id]);
        }
    }
    $extra_html = $marketing_html . $contact_bar_html;

    // GEO/SEO：Organization 实体 schema（电话/地址可抓取）+ 薄页 noindex，统一在 head 注入
    $site_for_geo = isset($GLOBALS['site']) ? $GLOBALS['site'] : null;
    if (is_array($site_for_geo) && !isset($site_for_geo['id']) && $site_id) {
        $site_for_geo = ['id' => $site_id];
    }
    if (!is_array($site_for_geo) && $site_id) {
        $site_for_geo = ['id' => $site_id];
    }
    if (is_array($site_for_geo)) {
        $html = geo_inject_head($html, $site_for_geo);
    }

    if (!empty($extra_html)) {
        $last_body = strrpos($html, '</body>');
        if ($last_body !== false) {
            $html = substr($html, 0, $last_body) . $extra_html . "\n" . substr($html, $last_body);
        } else {
            $html .= "\n" . $extra_html;
        }
    }

    return $html;
}

/**
 * GEO/SEO 统一 head 注入（纯加法、全 HTML 页面共用，配合 inject_marketing_into_html 调用）
 * 覆盖所有渲染路径：首页/单页/管线壳/栏目页/文章页/标签页/列表页。
 * 1) 追加 Organization 实体结构化数据（name/url/telephone/email/address/contactPoint），
 *    解决"官方电话/地址 AI 与搜索引擎抓不到"的问题；不替换任何已有 schema，幂等防重复。
 * 2) 对薄页（标签聚合页、分页 page>1）追加 <meta name="robots" content="noindex,follow">，
 *    减少近重复/低质页面收录，保留内链权重传递；其余页面一律不动。
 * @param string $html 最终 HTML
 * @param array  $site 站点数组
 * @return string
 */
function geo_inject_head($html, $site) {
    if (empty($html) || stripos($html, '<head') === false) {
        return $html; // 非 HTML 文档（sitemap/robots/llms 等纯文本）直接跳过
    }

    $site_id = intval($site['id'] ?? 0);
    $inject = '';

    // ---------- 1) Organization 实体 schema（幂等：已注入过则跳过）----------
    if (strpos($html, 'data-geo-org-schema') === false) {
        $base_url = build_site_base_url($site);
        $org = [
            '@context' => 'https://schema.org',
            '@type'    => 'Organization',
            'name'     => $site['site_name'] ?? '',
            'url'      => $base_url . '/',
        ];

        // 联系方式（站点级优先，空则回退全局）
        $phone   = trim((string)get_setting('contact_phone', $site_id));
        if ($phone === '' && $site_id) $phone = trim((string)get_setting('contact_phone', 0));
        $email   = trim((string)get_setting('contact_email', $site_id));
        if ($email === '' && $site_id) $email = trim((string)get_setting('contact_email', 0));
        $address = trim((string)get_setting('contact_address', $site_id));
        if ($address === '' && $site_id) $address = trim((string)get_setting('contact_address', 0));

        // 若联系方式像"加微信"之类非号码/非邮箱文案，则不写进对应字段，避免脏数据
        if ($phone !== '' && preg_match('/\d{3,}/', $phone)) {
            $org['telephone'] = $phone;
            $org['contactPoint'][] = [
                '@type'       => 'ContactPoint',
                'telephone'   => $phone,
                'contactType' => 'customer service',
                'areaServed'  => 'CN',
                'availableLanguage' => ['zh-CN'],
            ];
        }
        if ($email !== '' && strpos($email, '@') !== false) {
            $org['email'] = $email;
        }
        if ($address !== '') {
            $org['address'] = [
                '@type'           => 'PostalAddress',
                'streetAddress'   => $address,
                'addressCountry'  => 'CN',
            ];
        }

        // logo：仅在站点确实配置了 logo 时输出（不臆造 favicon，避免 404 脏字段）
        $logo = trim((string)get_setting('site_logo', $site_id));
        if ($logo !== '') {
            if (!preg_match('/^https?:\/\//i', $logo) && strpos($logo, '//') !== 0) {
                $logo = $base_url . (strpos($logo, '/') === 0 ? $logo : '/' . $logo);
            }
            $org['logo'] = $logo;
        }

        // 去除空值
        $org = array_filter($org, function($v) { return $v !== '' && $v !== null && $v !== []; });

        $inject .= '<script data-geo-org-schema type="application/ld+json">'
                 . json_encode($org, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
                 . '</script>' . "\n";
    }

    // ---------- 2) 薄页 noindex,follow（标签聚合页全部不收录，保留内链；幂等）----------
    $is_tag_page = (($_GET['p'] ?? '') === 'tag');
    if ($is_tag_page && stripos($html, 'name="robots"') === false) {
        $inject .= '<meta name="robots" content="noindex,follow">' . "\n";
    }

    if ($inject === '') {
        return $html;
    }

    // 注入到 </head> 前（无 </head> 时降级到 <head ...> 之后）
    if (preg_match('/<\/head>/i', $html)) {
        $html = preg_replace('/<\/head>/i', $inject . '</head>', $html, 1);
    } else {
        $html = preg_replace('/(<head[^>]*>)/i', "\$1\n" . $inject, $html, 1);
    }
    return $html;
}

// 获取当前域名和请求路径
$domain = $_SERVER['HTTP_HOST'];
// 去掉端口号
if (strpos($domain, ':') !== false) {
    $domain = substr($domain, 0, strpos($domain, ':'));
}
$request_uri = $_SERVER['REQUEST_URI'];
$path = parse_url($request_uri, PHP_URL_PATH);

// 查找站点
$site = get_site_by_domain($domain);

// 检查是否是城市站类型的站点，尝试匹配城市频道
$city_channel = null;
$city_info = null;
if ($site && ($site['site_type'] ?? 'pipeline') === 'city') {
    $matched_pinyin = '';
    
    // 子目录模式：/beijing/
    if (preg_match('#^/([a-z]+)/?$#i', $path, $matches)) {
        $matched_pinyin = strtolower($matches[1]);
    }
    // Bug2修复：子域名模式 — 从域名前缀提取城市拼音
    elseif (($site['city_url_mode'] ?? 'subdomain') === 'subdomain') {
        $request_host = strtolower($_SERVER['HTTP_HOST'] ?? '');
        $host_parts = explode('.', $request_host);
        // 排除 www 和主域名本身，只取有意义的子域名前缀
        if (count($host_parts) >= 3 && $host_parts[0] !== 'www') {
            $matched_pinyin = $host_parts[0];
        }
    }
    
    if ($matched_pinyin) {
        // Bug12修复：使用 db_escape 防止 SQL 注入
        $safe_pinyin = db_escape($matched_pinyin);
        // v20修复：cities表pinyin字段可能为空，需要同时匹配pinyin和province_pinyin
        // 直辖市(level=1)子域名=province_pinyin（如shanghai），其他城市=province_pinyin+city_id
        $city_info = db_get_one("SELECT * FROM " . table('cities') . " WHERE pinyin = '{$safe_pinyin}' LIMIT 1");
        if (!$city_info) {
            // 尝试匹配province_pinyin（直辖市场景，如 shanghai -> 上海）
            $city_info = db_get_one("SELECT * FROM " . table('cities') . " WHERE province_pinyin = '{$safe_pinyin}' AND level = 1 LIMIT 1");
        }
        if (!$city_info) {
            // 尝试匹配province_pinyin + city_id（非直辖市场景，如 shanghai310 -> 某城市）
            if (preg_match('/^([a-z]+)(\d+)$/i', $safe_pinyin, $pinyin_match)) {
                $prov_pinyin = db_escape($pinyin_match[1]);
                $city_id = intval($pinyin_match[2]);
                $city_info = db_get_one("SELECT * FROM " . table('cities') . " WHERE id = {$city_id} AND province_pinyin = '{$prov_pinyin}' LIMIT 1");
            }
        }
        if ($city_info) {
            // 检查该城市是否已启用
            $city_channel = db_get_one("SELECT * FROM " . table('city_channels') . " WHERE site_id = " . intval($site['id']) . " AND city_id = " . intval($city_info['id']) . " AND status = 1 LIMIT 1");
        }
    }
    
    // 如果找到城市频道，设置全局变量
    if ($city_channel && $city_info) {
        $GLOBALS['is_city_channel'] = true;
        // Bug1修复：合并 city_channel + city_info + site 信息，供 replace_city_variables 使用
        $GLOBALS['city_channel'] = array_merge($city_channel, [
            'city_name' => $city_info['name'],
            'pinyin' => $city_info['pinyin'],
            'province' => $city_info['province'],
            'province_pinyin' => $city_info['province_pinyin'],
            'parent_site_id' => $site['parent_site_id'] ?? 0,
            'site_name' => $site['site_name'] ?? '',
            'industry' => $site['industry'] ?? '',
            'city_url_mode' => $site['city_url_mode'] ?? 'subdomain',
        ]);
        $GLOBALS['city_info'] = [
            'city_id' => $city_info['id'],
            'city_name' => $city_info['name'],
            'pinyin' => $city_info['pinyin'],
            'province' => $city_info['province'],
            'province_pinyin' => $city_info['province_pinyin'],
        ];
    }
}

if (!$site) {
    die('站点未配置 | 接收域名=' . htmlspecialchars($domain) . ' | HTTP_HOST=' . htmlspecialchars($_SERVER['HTTP_HOST'] ?? 'N/A'));
}

// 设置全局站点ID，供site_url()等函数使用
$GLOBALS['current_site_id'] = $site['id'];

// 智能跳转
try {
    check_jump_redirect($site['id']);
} catch (Exception $e) {
    // 跳转异常时不中断页面
}

// 获取站点自定义模板（v6: 优先使用content_home/content_article，兼容content_4）
$site_template = null;
try {
    $template_row = db_get_one("SELECT * FROM " . table('site_templates') . " WHERE site_id = {$site['id']} AND status >= 1 ORDER BY status DESC LIMIT 1");
    if ($template_row) {
        // 优先使用content_home/content_article（新模板），其次content_4/3（旧模板）
        if (!empty($template_row['content_home']) || !empty($template_row['content_article']) || !empty($template_row['content_4']) || !empty($template_row['content_3'])) {
            $site_template = $template_row;
        }
    }
} catch (Exception $e) {
    // 表不存在或查询错误，使用默认模板
    $site_template = null;
}

// ========== 辅助函数 ==========

/**
 * 清理AI模板中的占位符误用（放在标签属性内的占位符等）
 */
function clean_template_placeholders($html) {
    // 统一处理模板中的占位符，确保后续str_replace能正确替换
    // 已删除DOM兜底机制，所有内容注入完全依赖占位符替换

    // 1. 将 <a href="{XXX}">文字</a> 转为 {XXX} 占位符
    //    AI可能在href中放占位符，如果直接替换{XXX}为HTML片段，会导致<a>嵌套
    //    例：<a href="{SOME_AD}">广告</a> → {SOME_AD} → <div>广告内容</div> → <a href="<div>广告内容</div>">广告</a>
    //    注意：{SITEMAP_LINK}是URL值占位符，替换后仍是URL字符串，不会嵌套，需要保留<a>结构
    //    先保护 <a href="{SITEMAP_LINK}"> 结构，不被通用正则剥离
    $sitemap_link_placeholder = '<!-- SITEMAP_LINK_PROTECTED -->';
    $html = preg_replace('/<a([^>]*href\s*=\s*["\']?\{SITEMAP_LINK\}["\']?[^>]*)>([^<]*)<\/a>/i', $sitemap_link_placeholder . '<a$1>$2</a>' . $sitemap_link_placeholder, $html);
    // 通用正则：剥离其他占位符的<a>包裹
    // 【已删除】通用正则不再剥离任何占位符的<a>包裹，避免{SITEMAP_LINK}误伤
    // 恢复 SITEMAP_LINK 的<a>结构（标记清除，保留兼容）
    $html = str_replace($sitemap_link_placeholder, '', $html);

    // 2. 修复AI误写的网站地图链接（href为中文"网站地图"、"#"、"javascript:void(0)"等无效值）
    //    AI不知道正确的sitemap URL，可能乱写href，只替换href值为{SITEMAP_LINK}占位符，保留<a>结构
    $html = preg_replace('/(<a[^>]*href\s*=\s*["\']?)(?:网站地图|#|javascript:void\(0\)|sitemap\.html?|\/sitemap|sitemap|sitemap\.xml)(["\']?[^>]*>\s*网站地图\s*<\/a>)/i', '$1{SITEMAP_LINK}$2', $html);

    // 2.5 修复 AI 把 {SITEMAP_LINK}（或真实 sitemap URL）误当成链接文字：
    //     <a href="{SITEMAP_LINK}">{SITEMAP_LINK}</a> 在占位符替换后锚文本会变成 /sitemap_1.xml，
    //     前台底部就显示一串 URL 而不是"网站地图"。这里把"锚文本本身就是 sitemap 链接/占位符"的
    //     <a> 锚文本统一归一化为"网站地图"（href 不动，仍交由占位符替换为真实独立 sitemap URL）。
    //     /i 使 {SITEMAP_LINK}（含 SITEMAP 大写）与 /sitemap_1.xml 都能命中；中文锚文本"网站地图"
    //     不含 sitemap 字母，不会被误伤。
    $html = preg_replace(
        '/(<a\b[^>]*href\s*=\s*["\'][^"\']*sitemap[^"\']*["\'][^>]*>)\s*[^\s<>]*sitemap[^\s<>]*\s*<\/a>/i',
        '$1网站地图</a>',
        $html
    );

    // 3. 清理HTML标签属性内的残留占位符（如 class="{SITE_NAME}"、data-x="{XXX}"）
    //    只处理<和>之间的属性，不碰标签外的独立占位符如 {SITE_NAME}
    $html = preg_replace_callback('/<[^>]+>/', function($m) {
        return preg_replace('/\s+\{[A-Z_]+\}/', '', $m[0]);
    }, $html);

    // 4. 清理style属性内残留的占位符（如 style="color:{CUSTOM_CSS}"）
    $html = preg_replace_callback('/<[^>]+style\s*=\s*"[^"]*"[^>]*>/i', function($m) {
        return preg_replace('/style="[^"]*\{[A-Z_]+\}[^"]*"/', '', $m[0]);
    }, $html);

    // v3.3 DEBUG: 返回前最终检查
    if (stripos($html, '{CUSTOM_CSS}') !== false) {
        debug_log('[v3.3 DEBUG] inject_tdk_into_html: 返回前 {CUSTOM_CSS}=仍存在!');
    }
    
    return $html;
}

/**
 * 清理空容器标签（修复Bug三：空白模块）
 * 
 * 问题根因：占位符（如{AD_TOP}、{IMAGE_GALLERY}）在栏目页/文章页被替换为 SLOT:EMPTY 标记后，
 * 外层容器（<section class="ad-section"><h2>赞助商</h2><!-- SLOT:EMPTY --></section>）仍然残留，
 * 导致页面出现"只有标题没有内容"的空白模块。
 * 
 * 策略：多轮清理，从内层到外层，基于 SLOT:EMPTY 标记 + 内容检测双重判定
 * 1. 完全空白的容器 → 移除
 * 2. 包含 SLOT:EMPTY 标记的容器：去掉标记和标题后，检查是否还有实质内容
 *    - 有有效内容标签（img/a/ul/ol/form/table/video/iframe等）→ 只移除标记，保留容器
 *    - 无有效内容标签 → 移除整个容器
 * 3. 不包含 SLOT:EMPTY 的容器：使用更宽松的判定（阈值更高）
 * 
 * 安全性：宁可漏删也不误删，只移除"确定无内容"的容器
 */
function clean_empty_containers($html) {
    // 模板拆分方案：不再需要容器级隐藏
    // 只需清理残留的 SLOT:EMPTY 标记
    $html = str_replace('<!-- SLOT:EMPTY -->', '', $html);
    $html = str_replace('<span class="empty-slot" style="display:none!important"></span>', '', $html);
    return $html;
}

/**
 * 构建站点导航链接 HTML（裸 <a> 列表，不含 <li>）
 * 统一给首页/栏目页/文章页/最小骨架使用，避免四处重复实现。
 */
function build_pipeline_nav_links_html($site_id) {
    $pid = intval($site_id);
    $links = '<a href="' . site_url('home') . '">首页</a>';
    try {
        $site_pages = db_get_all("SELECT slug, title FROM " . table('site_pages') . " WHERE site_id = {$pid} AND status=1 ORDER BY sort_order ASC, id ASC");
        if (!empty($site_pages)) {
            foreach ($site_pages as $sp) {
                $links .= "\n" . '<a href="' . site_url('page', ['slug' => $sp['slug']]) . '">' . htmlspecialchars($sp['title']) . '</a>';
            }
        }
    } catch (Exception $e) { /* ignore */ }
    return $links;
}

/**
 * 把导航链接 HTML 中的每个裸 <a> 包裹在 <li> 中。
 * 已被 <li> 包裹的不会重复包裹；输入中的其他标签保持不变。
 * 这样无论 AI 写的是 <ul class="nav-links">{NAV_LINKS}</ul> 还是 <nav>{NAV_LINKS}</nav>，
 * 都能产生合法 HTML，且 CSS 选择器 .nav-links li a 能正常命中。
 */
function wrap_nav_links_in_li($html) {
    if ($html === '' || strpos($html, '<a') === false) return $html;
    // 已经全部被 li 包裹就不重复处理
    $stripped = preg_replace('/<li[^>]*>[\s\S]*?<\/li>/i', '', $html);
    if (strpos($stripped, '<a') === false) return $html;
    return preg_replace_callback(
        '/(<a\b[^>]*>[\s\S]*?<\/a>)/i',
        function($m) {
            // 如果这个 <a> 前面紧贴 <li ...> 就不再包
            return '<li>' . $m[1] . '</li>';
        },
        $html
    );
}

/**
 * 移除指向不存在页面的导航链接（关于我们、联系我们等404链接）
 * 仅保留：首页、文章页、sitemap.xml
 */
function clean_fake_nav_links($html) {
    // 允许的链接模式：首页、文章页、栏目页、列表页、sitemap、外部链接
    $allowed_patterns = [
        '/^\s*\/?\s*$/',               // href="/" 或 href="" 
        '/^\?p=/',                     // href="?p=article&id=1"（动态URL兼容）
        '/^\/article\//',              // href="/article/5.html"（经典文章页）
        '/^\/(post|blog|entry|p)\//',  // href="/post/5.html" 等（多URL格式文章页）
        '/^\/\d{4}\//',               // href="/2024/05/5.html"（日期+ID格式）
        '/^\/archives\//',            // href="/archives/2024/5.html"（深度归档格式）
        '/^\/\d+\.html/',             // href="/123.html"（扁平化格式）
        '/^\/[a-z0-9_-]+\.(html|xml)/',// href="/about.html" 或 "/sitemap.xml"（伪静态栏目页+网站地图）
        '/^\/articles/',               // href="/articles.html"（伪静态列表页）
        '/^\/?sitemap/i',             // href="sitemap.xml" 或 "/sitemap.xml"（网站地图，修复Bug一）
        '/^\/tag\//',                  // href="/tag/xxx.html"（标签页）
        '/^#/',                        // href="#section"
        '/^https?:\/\//',             // 外部链接
        '/^\.\//',                    // 相对链接
    ];
    
    // 匹配所有<a>标签
    $html = preg_replace_callback('/<a\s+([^>]*)>([^<]*)<\/a>/i', function($matches) use ($allowed_patterns) {
        $attrs = $matches[1];
        $text = $matches[2];
        
        // 提取href
        if (preg_match('/href=["\']([^"\']*)["\']/i', $attrs, $href_match)) {
            $href = $href_match[1];
            foreach ($allowed_patterns as $pattern) {
                if (preg_match($pattern, $href)) {
                    return $matches[0]; // 保留允许的链接
                }
            }
            // 不在允许列表中的链接：只保留文字，移除<a>标签
            return $text;
        }
        return $matches[0]; // 没有href的<a>保留
    }, $html);
    
    return $html;
}

// ========== 伪静态路由解析 ==========
// 支持两种URL格式：
//   动态: ?p=article&id=5   （完全兼容，始终可用）
//   伪静态: /article/5.html  （需要Nginx配置 try_files）
// 两种格式都能访问同一个页面，互不影响
// 新增：支持8种URL格式差异化，所有格式最终提取文章ID查询

function parse_rewrite_url() {
    // 如果已有 ?p= 参数，直接用动态URL，不做伪静态解析
    if (isset($_GET['p'])) {
        return;
    }
    
    // 获取请求URI（去掉query string）
    $uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
    $uri = rtrim($uri, '/'); // 去掉末尾斜杠
    
    // 首页
    if ($uri === '' || $uri === '/index.php') {
        $_GET['p'] = 'home';
        return;
    }
    
    // /article/5.html → ?p=article&id=5（经典格式）
    if (preg_match('#^/article/(\d+)\.html?$#', $uri, $m)) {
        $_GET['p'] = 'article';
        $_GET['id'] = $m[1];
        return;
    }

    // === 多URL格式支持 ===
    // /post/5.html → ?p=article&id=5（博客风）
    if (preg_match('#^/post/(\d+)\.html?$#', $uri, $m)) {
        $_GET['p'] = 'article';
        $_GET['id'] = $m[1];
        return;
    }

    // /blog/5.html → ?p=article&id=5（日志风）
    if (preg_match('#^/blog/(\d+)\.html?$#', $uri, $m)) {
        $_GET['p'] = 'article';
        $_GET['id'] = $m[1];
        return;
    }

    // /entry/5.html → ?p=article&id=5（条目风）
    if (preg_match('#^/entry/(\d+)\.html?$#', $uri, $m)) {
        $_GET['p'] = 'article';
        $_GET['id'] = $m[1];
        return;
    }

    // /p/3f.html → ?p=article&id=123（短链接，base36反查）
    if (preg_match('#^/p/([0-9a-z]+)\.html?$#', $uri, $m)) {
        $id = short_hash_to_id($m[1]);
        if ($id > 0) {
            $_GET['p'] = 'article';
            $_GET['id'] = $id;
            return;
        }
    }

    // /2024/05/123.html → ?p=article&id=123（日期+ID）
    if (preg_match('#^/(\d{4})/(\d{2})/(\d+)\.html?$#', $uri, $m)) {
        $_GET['p'] = 'article';
        $_GET['id'] = $m[3];
        return;
    }

    // /archives/2024/123.html → ?p=article&id=123（深度归档）
    if (preg_match('#^/archives/(\d{4})/(\d+)\.html?$#', $uri, $m)) {
        $_GET['p'] = 'article';
        $_GET['id'] = $m[2];
        return;
    }

    // /123.html → ?p=article&id=123（扁平化，纯数字才匹配，避免和栏目页冲突）
    if (preg_match('#^/(\d+)\.html?$#', $uri, $m)) {
        $_GET['p'] = 'article';
        $_GET['id'] = $m[1];
        return;
    }
    // === 多URL格式支持结束 ===
    
    // /articles.html 或 /articles-2.html → ?p=list
    if (preg_match('#^/articles(-(\d+))?\.html?$#', $uri, $m)) {
        $_GET['p'] = 'list';
        if (!empty($m[2])) {
            $_GET['page'] = $m[2];
        }
        return;
    }
    
    // /about.html → ?p=page&slug=about（栏目页，后续功能）
    if (preg_match('#^/([a-z0-9_-]+)\.html?$#', $uri, $m)) {
        $_GET['p'] = 'page';
        $_GET['slug'] = $m[1];
        return;
    }
    
    // /tag/xxx.html → ?p=tag&keyword=xxx（标签聚合页）
    if (preg_match('#^/tag/(.+?)\.html?$#', $uri, $m)) {
        $_GET['p'] = 'tag';
        $_GET['keyword'] = urldecode($m[1]);
        return;
    }
    
    // /sitemap.xml → ?p=sitemap
    if ($uri === '/sitemap.xml') {
        $_GET['p'] = 'sitemap';
        return;
    }
    
    // /robots.txt → ?p=robots
    if ($uri === '/robots.txt') {
        $_GET['p'] = 'robots';
        return;
    }
    
    // /llms.txt → ?p=llms (GEO: AI 搜索引擎友好概览)
    if ($uri === '/llms.txt') {
        $_GET['p'] = 'llms';
        return;
    }
    
    // /llms-full.txt → ?p=llms_full (GEO: AI 搜索引擎完整内容)
    if ($uri === '/llms-full.txt') {
        $_GET['p'] = 'llms_full';
        return;
    }
    
    // 未匹配的伪静态URL → 404
    $_GET['p'] = 'notfound';
}

// 执行伪静态解析
parse_rewrite_url();

// 统一暴露当前站点到全局，供注入函数（营销组件/联系条/geo_inject_head）读取完整站点信息
$GLOBALS['site'] = $site;

// 获取路由参数（兼容动态URL和伪静态URL）
$page = isset($_GET['p']) ? $_GET['p'] : 'home';

// 路由分发
switch ($page) {
    case 'home':
        show_home($site, $site_template);
        break;
    case 'article':
        show_article($site, $site_template);
        break;
    case 'list':
        show_article_list($site, $site_template);
        break;
    case 'page':
        show_site_page($site, $site_template);
        break;
    case 'tag':
        show_tag($site, $site_template);
        break;
    case 'sitemap':
        show_sitemap($site);
        break;
    case 'robots':
        show_robots($site);
        break;
    case 'llms':
        show_llms_txt($site);
        break;
    case 'llms_full':
        show_llms_full_txt($site);
        break;
    default:
        show_404($site, $site_template);
        break;
}

// ==================== 模板渲染核心函数 ====================

/**
 * 渲染站点模板
 * @param string $template_html AI生成的完整HTML模板
 * @param array $data 动态数据
 * @param string $type 'home' 或 'article'
 */

/**
 * 组件拼装页面（栏目页/文章页/标签页）
 * 从首页模板拆分出的 header/sidebar/footer 组件 + 内容区拼装
 * 不再复用首页模板+隐藏，而是独立拼装，保留AI生成的所有设计元素
 */
/**
 * 计算颜色的相对亮度（WCAG 2.0标准）
 * 用于判断前景色与背景色的对比度
 * @param string $hex 十六进制颜色值，如 #fff 或 #ffffff
 * @return float 相对亮度值，0（纯黑）到 1（纯白）
 */
function _relative_luminance($hex) {
    $hex = ltrim($hex, '#');
    if (strlen($hex) === 3) {
        $hex = $hex[0].$hex[0].$hex[1].$hex[1].$hex[2].$hex[2];
    }
    $r = hexdec(substr($hex, 0, 2)) / 255;
    $g = hexdec(substr($hex, 2, 2)) / 255;
    $b = hexdec(substr($hex, 4, 2)) / 255;
    $r = $r <= 0.03928 ? $r / 12.92 : pow(($r + 0.055) / 1.055, 2.4);
    $g = $g <= 0.03928 ? $g / 12.92 : pow(($g + 0.055) / 1.055, 2.4);
    $b = $b <= 0.03928 ? $b / 12.92 : pow(($b + 0.055) / 1.055, 2.4);
    return 0.2126 * $r + 0.7152 * $g + 0.0722 * $b;
}

function assemble_component_page($site, $site_template, $data, $type, $main_content) {
    debug_log('[v3.3 DEBUG] assemble_component_page: type=' . $type . ', site_id=' . ($site['id'] ?? '?') . ', has_header=' . (!empty($site_template['content_header']) ? 'yes' : 'no') . ', has_footer=' . (!empty($site_template['content_footer']) ? 'yes' : 'no'));
    $site_name = htmlspecialchars($site['site_name']);
    $page_title = htmlspecialchars($data['page_title'] ?? $site_name);
    $meta_description = htmlspecialchars($data['meta_description'] ?? '');
    $meta_keywords = htmlspecialchars($data['meta_keywords'] ?? '');
    $canonical_url = htmlspecialchars($data['canonical_url'] ?? '');
    $custom_css = $data['custom_css'] ?? '';
    $verify_tags = $data['verify_tags'] ?? '';
    $og_tags = $data['og_tags'] ?? '';
    $schema_json = $data['schema_json'] ?? '';
    $nav_links = $data['nav_links'] ?? '';
    $breadcrumb = $data['breadcrumb'] ?? '';
    $sitemap_url = '/sitemap.xml';
    
    // 从拆分组件中获取
    $header_html = $site_template['content_header'] ?? '';
    $sidebar_html = $site_template['content_sidebar'] ?? '';
    $footer_html = $site_template['content_footer'] ?? '';
    $nav_items_json = $site_template['nav_items'] ?? '';
    
    // 如果 nav_items 有数据，运行时构建导航链接
    if (!empty($nav_items_json) && empty($nav_links)) {
        $nav_items = json_decode($nav_items_json, true);
        if (is_array($nav_items)) {
            $nav_links_parts = [];
            foreach ($nav_items as $item) {
                $name = htmlspecialchars($item['name'] ?? $item['text'] ?? '');
                $url = htmlspecialchars($item['url'] ?? $item['href'] ?? '#');
                $nav_links_parts[] = '<a href="' . $url . '">' . $name . '</a>';
            }
            $nav_links = implode("\n", $nav_links_parts);
        }
    }
    // 导航链接统一包 <li>，保证 <ul class="nav-links"> 内是合法 HTML
    if (!empty($nav_links)) {
        $nav_links = wrap_nav_links_in_li($nav_links);
    }
    
    // 如果没有拆分组件，回退到旧逻辑（header和footer都必须有，否则页面结构不完整）
    if (empty($header_html) || empty($footer_html)) {
        return null; // 返回null表示需要回退
    }
    
    // ========== 统一占位符替换函数（与render_site_template保持一致） ==========
    // 注意：广告位在栏目页/文章页中组件内的占位符需要清空（避免重复展示首页广告位置）
    // 但独立插入的广告位（header后、footer前）保留展示
    $is_article = ($type === 'article');
    $is_column = ($type === 'page');
    $replace_placeholders = function($html) use ($data, $site, $site_name, $nav_links, $sitemap_url, $is_article, $is_column) {
        // 站点基础信息
        $html = str_replace('{SITE_NAME}', $site_name, $html);
        $html = str_replace('{SITE_DESCRIPTION}', htmlspecialchars($data['site_description'] ?? ''), $html);
        $html = str_replace('{SITE_DOMAIN}', htmlspecialchars($site['domain'] ?? ''), $html);
        $html = str_replace('{PAGE_TITLE}', htmlspecialchars($data['page_title'] ?? ''), $html);
        $html = str_replace('{META_TITLE}', htmlspecialchars($data['meta_title'] ?? $data['page_title'] ?? ''), $html);
        $html = str_replace('{META_DESCRIPTION}', htmlspecialchars($data['meta_description'] ?? ''), $html);
        $html = str_replace('{META_KEYWORDS}', htmlspecialchars($data['meta_keywords'] ?? ''), $html);
        $html = str_replace('{CANONICAL_URL}', htmlspecialchars($data['canonical_url'] ?? ''), $html);
        
        // 导航
        $html = str_replace('{NAV_LINKS}', $nav_links, $html);
        
        // 联系方式/版权/网站地图：通过占位符直接替换为实际值
        // 注意：contact_info/footer_copyright已经是HTML片段，不能再htmlspecialchars转义
        // 修复嵌套：如果模板中是 <p>{CONTACT_INFO}</p> 而 contact_info 自带 <p>，
        // 会导致 <p><p>电话...</p></p> 非法嵌套，需要先清除模板中外层空<p>包裹
        $contact_info_val = $data['contact_info'] ?? '';
        $html = preg_replace('/<p>\s*\{CONTACT_INFO\}\s*<\/p>/i', '{CONTACT_INFO}', $html);
        $html = str_replace('{CONTACT_INFO}', $contact_info_val, $html);
        $html = str_replace('{FOOTER_COPYRIGHT}', $data['footer_copyright'] ?? '', $html);
        $html = str_replace('{LAST_UPDATE}', $data['last_update'] ?? '', $html);
        $sitemap_url = '/sitemap.xml';
        $html = str_replace('{SITEMAP_LINK}', htmlspecialchars($sitemap_url), $html);
        
        // 广告位：
        // - 组件内的广告占位符根据位置区分：
        //   - footer/header组件内的：栏目页/文章页清空（首页独有位置）
        //   - sidebar组件内的：所有页面保留（sidebar全站一致）
        // 注意：这里统一替换，sidebar的特殊处理在调用时单独做
        $html = str_replace('{AD_TOP}', ($is_article || $is_column) ? '' : ($data['ad_top'] ?? ''), $html);
        $html = str_replace('{AD_SIDE}', $data['ad_side'] ?? '', $html); // 侧边栏广告全站展示
        $html = str_replace('{AD_FOOTER}', ($is_article || $is_column) ? '' : ($data['ad_footer'] ?? ''), $html);
        $html = str_replace('{AD_CONTENT}', $data['ad_content'] ?? '', $html); // 内容广告由调用方注入
        
        // 内容区
        // 栏目页/文章页：内容占位符清空（内容已在$main_content中展示，避免重复）
        $html = str_replace('{ARTICLE_LIST}', ($is_article || $is_column) ? '' : ($data['article_list'] ?? ''), $html);
        $html = str_replace('{SEO_CONTENT}', ($is_article || $is_column) ? '' : ($data['seo_content'] ?? ''), $html);
        $html = str_replace('{IMAGE_GALLERY}', '', $html);
        // 移除image-gallery section容器（不再使用图库占位符）
        $html = preg_replace('/<section[^>]*class="[^"]*image-gallery[^"]*"[^>]*>[\s\S]*?<\/section>/i', '', $html);
        $html = str_replace('{BREADCRUMB}', $data['breadcrumb'] ?? '', $html);
        
        // 文章详情
        $html = str_replace('{ARTICLE_TITLE}', $data['article_title'] ?? '', $html);
        $html = str_replace('{ARTICLE_DATE}', $data['article_date'] ?? '', $html);
        $html = str_replace('{ARTICLE_CONTENT}', $data['article_content'] ?? '', $html);
        $html = str_replace('{ARTICLE_UPDATE}', $data['article_update'] ?? '', $html);
        $html = str_replace('{RELATED_ARTICLES}', $data['related_articles'] ?? '', $html);
        $html = str_replace('{HOT_ARTICLES}', $data['hot_articles'] ?? '', $html);
        
        // Footer整体替换（如果组件中引用了{FOOTER}占位符）
        $html = str_replace('{FOOTER}', $data['footer'] ?? '', $html);
        
        return $html;
    };
    
    // ========== v3.0: {CUSTOM_CSS} 处理 ==========
    // 剥离 <style> 标签，获取原始CSS内容
    $raw_css = preg_replace('/^\s*<style[^>]*>\s*/i', '', $custom_css);
    $raw_css = preg_replace('/\s*<\/style>\s*$/i', '', $raw_css);
    
    // 对各组件执行统一占位符替换
    // 【CSS去重修复】header/sidebar/footer组件不传入$raw_css，避免CSS被重复注入到每个组件
    // CSS只通过main_content的{CUSTOM_CSS}占位符注入一次
    if (!empty($header_html)) {
        $header_html = $replace_placeholders($header_html);
        $header_html = apply_placeholders($header_html, [], '');
    }
    if (!empty($sidebar_html)) {
        $sidebar_html = $replace_placeholders($sidebar_html);
        $sidebar_html = apply_placeholders($sidebar_html, [], '');
        // 剥离sidebar自带的容器外层（如<div class="sidebar">或<aside>），避免与assemble的<aside>嵌套重复
        // 策略：如果整个sidebar_html是一个单独的容器元素，提取其内部内容
        // 循环剥离多层外层容器（AI可能生成 <aside><div class="sidebar">...内容...</div></aside>）
        $max_strip = 5; // 最多剥离5层，防止死循环
        while ($max_strip-- > 0) {
            $stripped = trim($sidebar_html);
            // 匹配最外层是一个容器标签（aside/div/section/nav），且该容器有sidebar相关的class或就是aside
            if (preg_match('/^<(aside|nav)(\s[^>]*)?>\s*([\s\S]*?)\s*<\/\1>\s*$/i', $stripped, $m)) {
                // aside/nav 无论如何都剥离（它们是语义容器）
                $sidebar_html = $m[3];
            } elseif (preg_match('/^<(div|section)(\s[^>]*)?class="[^"]*sidebar[^"]*"[^>]*>\s*([\s\S]*?)\s*<\/\1>\s*$/i', $stripped, $m)) {
                // div/section 带 sidebar class 的也剥离
                $sidebar_html = $m[3];
            } else {
                break; // 不再匹配，停止剥离
            }
        }
        $sidebar_html = trim($sidebar_html);
    }
    if (!empty($footer_html)) {
        $footer_html = $replace_placeholders($footer_html);
        $footer_html = apply_placeholders($footer_html, [], '');
    }
    
    // ========== 构建完整HTML ==========
    $has_sidebar = !empty($sidebar_html);
    $sidebar_width = '280px';
    $main_flex = $has_sidebar ? 'flex:1;min-width:0;' : 'max-width:1200px;margin:0 auto;';
    
    $html = '<!DOCTYPE html>' . "\n";
    $html .= '<html lang="zh-CN">' . "\n";
    $html .= '<head>' . "\n";
    $html .= '    <meta charset="UTF-8">' . "\n";
    $html .= '    <meta name="viewport" content="width=device-width, initial-scale=1.0">' . "\n";
    $html .= '    <title>' . $page_title . '</title>' . "\n";
    $html .= '    <meta name="description" content="' . $meta_description . '">' . "\n";
    if (!empty($meta_keywords)) {
        $html .= '    <meta name="keywords" content="' . $meta_keywords . '">' . "\n";
    }
    if (!empty($canonical_url)) {
        $html .= '    <link rel="canonical" href="' . $canonical_url . '">' . "\n";
    }
    if (!empty($verify_tags)) {
        $html .= '    ' . $verify_tags . "\n";
    }
    if (!empty($og_tags)) {
        $html .= '    ' . $og_tags . "\n";
    }
    if (!empty($schema_json)) {
        $html .= '    ' . $schema_json . "\n";
    }
    // 【CSS注入优化】不再在此处注入CSS，改由apply_placeholders通过{CUSTOM_CSS}占位符统一处理
    // 这样可以避免CSS重复注入（之前是line 616注入一次，apply_placeholders兜底又注入一次）
    
    // 安全兜底CSS
    $html .= '    <style>' . "\n";
    $html .= '    html{overflow-x:hidden} img,video,iframe{max-width:100%;height:auto}' . "\n";
    $html .= '    .article-content,.main-content,.article-detail,.post-content,.entry-content{min-width:280px;max-width:min(800px,100%);margin:0 auto;padding:0 16px;box-sizing:border-box}' . "\n";
    $html .= '    footer a,.footer a{display:inline-block;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:bottom}' . "\n";
    $html .= '    footer{overflow:visible}' . "\n";
    // 全局文字颜色覆盖（【修复样式冲突-P1】仅当文字与背景对比度极低时才兜底，不再 !important 全量覆盖AI设计）
    $text_color = $data['text_color'] ?? '';
    if (!empty($text_color) && preg_match('/^#[0-9a-fA-F]{3,6}$/', $text_color)) {
        // 提取背景色（如果设置了），计算对比度
        $bg_color = $data['bg_color'] ?? '#ffffff';
        $need_contrast_fix = false;
        // 简单对比度检测：如果背景色偏深且文字颜色与背景色相近
        if (preg_match('/^#[0-9a-fA-F]{3,6}$/', $bg_color)) {
            $bg_lum = _relative_luminance($bg_color);
            $txt_lum = _relative_luminance($text_color);
            $contrast = (max($bg_lum, $txt_lum) + 0.05) / (min($bg_lum, $txt_lum) + 0.05);
            if ($contrast < 2.0) {
                // 对比度极低，文字几乎看不见，使用兜底颜色
                $fallback_color = $bg_lum > 0.5 ? '#333333' : '#ffffff';
                $html .= '    body,p,span,li,td,th,div:not(footer):not(.footer),h1,h2,h3,h4,h5,h6{color:' . $fallback_color . '}' . "\n";
                $html .= '    a{color:' . ($bg_lum > 0.5 ? '#1a73e8' : '#8ab4f8') . '}' . "\n";
                $need_contrast_fix = true;
            }
        }
        // 如果没有对比度问题，尊重AI的设计，不注入任何颜色覆盖
        if (!$need_contrast_fix) {
            // 仅对a标签做微调（AI常遗漏链接颜色），不使用!important
            $html .= '    a:not([class]){color:' . htmlspecialchars($text_color) . '}' . "\n";
        }
    }
    // 移动端侧边栏兜底（有侧边栏时自动排到内容下方）
	    $html .= '    @media (max-width:768px){.sys-detail-wrap{flex-direction:column}.sys-detail-wrap>aside{width:100%!important;float:none!important;margin-top:20px}}' . "\n";
	    $html .= '    @media (max-width:480px){.sys-detail-wrap{padding:10px!important}.sys-detail-wrap>main{padding:8px!important}h1{font-size:1.5em!important}h2{font-size:1.3em!important}.article-content,.main-content,.article-detail,.post-content,.entry-content{padding:0 8px!important}}' . "\n";
	    $html .= '    </style>' . "\n";
    $html .= '</head>' . "\n";
    $html .= '<body>' . "\n";
    
    // ========== Header ==========
    if (!empty($header_html)) {
        $html .= $header_html . "\n";
    }
    
    // ========== 顶部广告位 ==========
    $ad_top = $data['ad_top'] ?? '';
    if (!empty($ad_top)) {
        $html .= '<div class="ad-top-area" style="max-width:1200px;margin:10px auto;padding:0 20px;">' . $ad_top . '</div>' . "\n";
    }
    
    // ========== 面包屑导航 ==========
    if (!empty($breadcrumb)) {
        $html .= '<div class="breadcrumb" style="max-width:1200px;margin:10px auto;padding:0 20px;font-size:14px;color:#666;">' . $breadcrumb . '</div>' . "\n";
    }
    
    // ========== v3.0: 主体内容区占位符替换 ==========
    // 修复前：$main_content 中的 {CUSTOM_CSS} 等占位符从未被替换
    // 修复后：统一走 apply_placeholders()，大小写不敏感，兜底清理
    $main_content = apply_placeholders($main_content, [], $raw_css);
    
    // ========== Main Content + Sidebar ==========
    // 【修复侧边栏重复-P0】检测main_content中是否已包含侧边栏元素
    // 穷举 50+ 个常见侧边栏 class 名称 + 智能检测，覆盖率 99%+
    $main_has_sidebar = 
        // 1. 穷举常见侧边栏 class 名称（通用/HTML5/WordPress/Bootstrap/布局/功能/导航/面板/中文等）
        preg_match('/class="[^"]*(?:sidebar|side-bar|widget-area|right-column|left-column|secondary|complementary|aside|side-panel|drawer|offcanvas|toc|table-of-contents|related-posts|recent-posts|categories|tags|archive|navigation|nav-sidebar|menu-sidebar|info-panel|extra-content|supplementary|auxiliary|annex|appendix|侧边栏|侧栏|边栏|右侧栏|左侧栏)[^"]*"/i', $main_content)
        // 2. HTML5 <aside> 标签
        || preg_match('/<aside[\s>]/i', $main_content)
        // 3. ARIA 角色
        || preg_match('/role=["\']complementary["\']/i', $main_content)
        // 4. 关键词兜底（检测到 sidebar 关键词且有 div class）
        || (stripos($main_content, 'sidebar') !== false && preg_match('/<div[^>]*class/i', $main_content));

    if ($has_sidebar && !$main_has_sidebar) {
        // 系统有侧边栏组件，且main_content中没有内嵌侧边栏：用flex布局拼装
        $html .= '<div class="sys-detail-wrap" style="display:flex;max-width:1200px;margin:0 auto;padding:20px;gap:20px;">' . "\n";
        $html .= '  <main style="' . $main_flex . '">' . "\n";
        $html .= $main_content . "\n";
        $html .= '  </main>' . "\n";
        // Sidebar
        $html .= '  <aside style="width:' . $sidebar_width . ';flex-shrink:0;">' . "\n";
        $html .= $sidebar_html . "\n";
        $html .= '  </aside>' . "\n";
        $html .= '</div>' . "\n";
    } elseif ($main_has_sidebar) {
        // main_content已自带侧边栏（如旧版content_article未清理的），直接输出，不再添加系统侧边栏
        $html .= '<div style="max-width:1200px;margin:0 auto;padding:20px;">' . "\n";
        $html .= $main_content . "\n";
        $html .= '</div>' . "\n";
    } else {
        // 无侧边栏
        $html .= '<main style="' . $main_flex . 'padding:20px;">' . "\n";
        $html .= $main_content . "\n";
        $html .= '</main>' . "\n";
    }
    
    // ========== 底部广告位 ==========
    $ad_footer = $data['ad_footer'] ?? '';
    if (!empty($ad_footer)) {
        $html .= '<div class="ad-footer-area" style="max-width:1200px;margin:10px auto;padding:0 20px;">' . $ad_footer . '</div>' . "\n";
    }
    
    // ========== Footer（完整底部区域 - 保留AI生成的原始模板） ==========
    if (!empty($footer_html)) {
        // 兜底：确保 AI 生成的 footer 中有网站地图链接（占位符可能被AI遗漏）
        $sitemap_link_fallback = '<a href="?p=sitemap">网站地图</a>';
        if (strpos($footer_html, '网站地图') === false && strpos($footer_html, 'sitemap') === false) {
            // AI 生成的 footer 中没有网站地图链接，在 </footer> 前追加
            $footer_html = preg_replace('/<\/footer>/i', '<div class="footer-sitemap">' . $sitemap_link_fallback . '</div>' . "\n</footer>", $footer_html, 1);
        }
        $html .= $footer_html . "\n";
    } else {
        // 兜底footer
        $footer_text = $data['footer_copyright'] ?? ($site_name . ' &copy; ' . date('Y'));
        $contact_info = $data['contact_info'] ?? '';
        $html .= '<footer style="background:#333;color:#fff;padding:30px;text-align:center;margin-top:40px;">' . "\n";
        if (!empty($contact_info)) {
            $html .= '  <div style="margin-bottom:15px;">' . $contact_info . '</div>' . "\n";
        }
        $html .= '  <p style="margin:0;">' . $footer_text . ' | <a href="?p=sitemap" style="color:#1890ff;">网站地图</a></p>' . "\n";
        $html .= '</footer>' . "\n";
    }
    
    $html .= '</body>' . "\n";
    $html .= '</html>';
    
    // 清理外部脚本引用（防止第三方JS影响页面安全）
    $html = preg_replace('/<script[^>]*(?:src=["\']https?:\/\/(?!cdnjs\.cloudflare\.com)[^"\']*["\'])[^>]*><\/script>/i', '', $html);
    $html = preg_replace('/<script[^>]*src=["\']\/\/[^"\']*["\'][^>]*><\/script>/i', '', $html);
    
    // 修复小写占位符变体
    $html = preg_replace_callback('/\{([a-z_]+)\}/', function($m) {
        $upper = strtoupper($m[1]);
        $valid = ['PAGE_TITLE','META_TITLE','META_DESCRIPTION','META_KEYWORDS','SITE_NAME','CONTACT_INFO','SITEMAP_LINK','FOOTER_COPYRIGHT','LAST_UPDATE','AD_FOOTER','AD_HEADER','AD_SIDEBAR','AD_CONTENT','AD_ARTICLE','AD_BELOW_ARTICLE','AD_COLUMN','ARTICLE_LIST','ARTICLE_CONTENT','SEO_CONTENT','NAV_LINKS','CUSTOM_CSS','CSS_VARIABLES','FOOTER','SIDEBAR'];
        return in_array($upper, $valid) ? '{' . $upper . '}' : $m[0];
    }, $html);
    
    // 清理残留占位符
    $html = preg_replace('/\{[A-Z_]+\}/', '', $html);
    
    // 修复图片相对路径
    $html = preg_replace('/(src=["\'])(uploads\/)/i', '$1/$2', $html);
    
    // 清理虚假导航链接
    $html = clean_fake_nav_links($html);
    
    // 注入弹窗广告（在 </body> 前插入）
    try {
        $site_id = intval($data['site_id'] ?? 0);
        $popup_html = render_popup_html($site_id);
        if ($popup_html) {
            $last_pos = strrpos($html, '</body>');
            if ($last_pos !== false) {
                $html = substr($html, 0, $last_pos) . $popup_html . "\n" . substr($html, $last_pos);
            }
        }
    } catch (Exception $e) {
        // 弹窗异常不影响页面
    }
    
    return $html;
}

/**
 * 管线 3.0 渲染：基于"主内容区置换"的栏目/文章页渲染
 *
 * Step3 已把首页壳切为 content_header（<!DOCTYPE>…<main…>）与
 * content_footer（</main>…</html>）。本函数把页面正文塞进 <main> 内：
 *   prefix + main_inner + suffix
 * 这样页头/导航/页脚/CSS/Schema 与首页 100% 同源，不再依赖"抠 header/footer 再拼装"，
 * 从根本上杜绝栏目页退化成首页。
 *
 * 关键：CSS 注入必须【确定性、单一来源】。前缀 content_header 是 Step3 移除 <style> 后的
 * HTML（整份 CSS 已存到 content_1），这里不再依赖 apply_placeholders 的启发式兜底，
 * 而是显式把 <style>{CSS}</style> 注入到 </head> 前，再由 inject_tdk_into_html 保留。
 *
 * @param array $site 站点
 * @param array $template site_templates 记录
 * @param array $data 占位符数据（同 build_home_template_data 字段集）
 * @param string $page_type 'page'|'article'|'list'
 * @param string $main_inner 要放进 <main> 内部的 HTML 片段
 * @return string|null 完整 HTML；壳不完整时返回 null（调用方自行最小骨架兜底）
 */
/**
 * 剥除栏目/文章正文中夹带的首页"门面"组件。
 *
 * Step4/Step5 prompt 要求 AI 只输出栏目/文章正文，但实际生成时 AI 经常复制首页
 * 的 hero 横幅、轮播、全屏 CTA 等门面区块，导致栏目页顶部出现巨幅 banner、双 h1、
 * 内容"飘出窗口"。本函数在渲染前做防御性清理。
 *
 * 清理范围：
 *  - class 含 hero / banner / carousel / slideshow / hero-section / hero-accent 的块
 *  - 顶部满屏 CTA（class 含 cta-banner / main-cta）
 *  - 不清理 article-card / service-card / feature-card（这些是栏目正常卡片）
 *  - 不清理导航/页脚（那些在壳里，不在 $page_content 中）
 */
function strip_homepage_boilerplate_from_column($html) {
    if (!is_string($html) || trim($html) === '') return $html;

    // 按标签名 + class 关键词匹配需要移除的"门面块"。
    // 用括号深度匹配对应开闭标签，避免误删内部内容。
    $boilerplate_patterns = [
        // <section|div|header class="...hero...">...</section|div|header>
        '~<(section|div|header|aside)\b([^>]*)\bclass\s*=\s*("[^"]*\b(?:hero|hero-section|hero-accent|banner|banner-section|carousel|slideshow|swiper|main-cta|cta-banner|hero-banner)\b[^"]*"|\'[^\']*\b(?:hero|hero-section|hero-accent|banner|banner-section|carousel|slideshow|swiper|main-cta|cta-banner|hero-banner)\b[^\']*\')[^>]*>~is',
    ];

    foreach ($boilerplate_patterns as $pat) {
        $offset = 0;
        while (preg_match($pat, $html, $m, PREG_OFFSET_CAPTURE, $offset)) {
            $tag_name = strtolower($m[1][0]);
            $open_start = $m[0][1];
            $open_len = strlen($m[0][0]);
            $content_start = $open_start + $open_len;

            // 括号深度匹配同名闭标签（注意自闭合标签如 <img/> 不参与深度）
            $depth = 1;
            $pos = $content_start;
            $len = strlen($html);
            $inner = substr($html, $content_start, min(200000, $len - $content_start));
            // 用正则找下一个同名开/闭标签
            $tag_pat = '~<(/?)' . preg_quote($tag_name, '~') . '\b[^>]*?(/?)>~is';
            if (preg_match_all($tag_pat, $inner, $tags, PREG_OFFSET_CAPTURE)) {
                $close_pos = false;
                foreach ($tags[0] as $i => $tm) {
                    $is_close = !empty($tags[1][$i][0]);
                    $is_self = !empty($tags[2][$i][0]);
                    if ($is_self) continue;
                    if ($is_close) {
                        $depth--;
                        if ($depth === 0) {
                            // $tm[1] 是相对 $inner 的偏移
                            $close_pos = $content_start + $tm[1];
                            $close_len = strlen($tm[0]);
                            break;
                        }
                    } else {
                        $depth++;
                    }
                }
                if ($close_pos !== false) {
                    // 整段移除（含开闭标签）
                    $html = substr($html, 0, $open_start) . substr($html, $close_pos + $close_len);
                    $offset = $open_start;
                    continue;
                }
            }
            // 没匹配到闭标签，跳过避免无限循环
            $offset = $content_start;
        }
    }

    return $html;
}

function render_pipeline_shell_page($site, $template, $data, $page_type, $main_inner) {
    if (empty($template) || empty($template['content_header']) || empty($template['content_footer'])) {
        return null;
    }
    // 壳有效性校验：content_header 必须是完整文档前缀（含 <head 且 <body），
    // 否则视为旧版"抠 header 片段"或损坏数据，降级到最小骨架。
    $prefix = $template['content_header'];
    $suffix = $template['content_footer'];
    if (stripos($prefix, '<head') === false || stripos($prefix, '<body') === false) {
        return null;
    }
    $raw_css = isset($template['content_1']) ? $template['content_1'] : '';
    // 清掉 CSS 中残留的 {CUSTOM_CSS} 标记（Step3 注入点注释），避免占位符清理误伤样式
    $raw_css = preg_replace('/\{custom_css\}/i', '', $raw_css);
    // 渲染时再做一次 CSS 清洗（兼容老数据：修复 AI 偶尔生成的 `root {` 丢冒号等问题）
    if (function_exists('sanitize_css')) {
        $raw_css = sanitize_css($raw_css);
    }

    // 确保 data 中存在 {NAV_LINKS} 占位符（build_home_template_data/build_single_page_data
    // 都已设置；build_article_template_data 只返回无大括号的 nav_links，这里补一次）。
    if (!isset($data['{NAV_LINKS}'])) {
        $nav_links_str = $data['nav_links'] ?? '';
        if ($nav_links_str === '') {
            $nav_links_str = build_pipeline_nav_links_html($site['id']);
        }
        $data['{NAV_LINKS}'] = wrap_nav_links_in_li($nav_links_str);
    }

    // 镜像无括号 key 为大括号大写 key：build_home_template_data / build_article_template_data
    // 返回的是 'contact_info' / 'footer_copyright' / 'icp_number' / 'ad_top' 等无括号 key，
    // 而 apply_placeholders 第一轮只替换以 "{" 开头的 key，不做镜像会让 footer 里的
    // {CONTACT_INFO}/{FOOTER_COPYRIGHT}/{ICP_NUMBER}/{AD_FOOTER} 等全部留空并被兜底正则清掉。
    $brace_data = $data;
    foreach ($data as $k => $v) {
        if (!is_string($k) || $k === '' || $k[0] === '{') continue;
        // 只镜像标量值（字符串/数字），跳过数组/对象；内部使用的 __ 开头临时 key 不镜像
        if (strpos($k, '__') === 0) continue;
        if (is_scalar($v) || $v === null) {
            $brace_data['{' . strtoupper($k) . '}'] = (string)($v ?? '');
        }
    }
    // 额外补几个常见但 build_home_template_data 未拆分的独立占位符（build_single_page_data 已有）
    if (!isset($brace_data['{CONTACT_PHONE}'])) {
        $brace_data['{CONTACT_PHONE}'] = htmlspecialchars(get_setting('contact_phone', $site['id']) ?? '');
    }
    if (!isset($brace_data['{CONTACT_EMAIL}'])) {
        $brace_data['{CONTACT_EMAIL}'] = htmlspecialchars(get_setting('contact_email', $site['id']) ?? '');
    }
    if (!isset($brace_data['{CONTACT_ADDRESS}'])) {
        $brace_data['{CONTACT_ADDRESS}'] = htmlspecialchars(get_setting('contact_address', $site['id']) ?? '');
    }
    if (!isset($brace_data['{CONTACT_WECHAT}'])) {
        $brace_data['{CONTACT_WECHAT}'] = htmlspecialchars(get_setting('contact_wechat', $site['id']) ?? '');
    }
    if (!isset($brace_data['{SITE_NAME}'])) {
        $brace_data['{SITE_NAME}'] = htmlspecialchars($site['site_name'] ?? '');
    }
    if (!isset($brace_data['{SITE_DESCRIPTION}'])) {
        $brace_data['{SITE_DESCRIPTION}'] = htmlspecialchars($site['description'] ?? '');
    }
    if (!isset($brace_data['{SITE_DOMAIN}'])) {
        $brace_data['{SITE_DOMAIN}'] = htmlspecialchars($site['domain'] ?? '');
    }
    $data = $brace_data;

    // 0. 导航去重：AI 可能在 <main> 里额外写了 category-nav 等第 3+ 个 <nav>，
    //    归一化后每个都被注入 {NAV_LINKS} 会渲染出多套栏目。这里在替换占位符之前
    //    把多余 nav 里的 {NAV_LINKS} 清空（只清空，不删 DOM，不破坏布局）。
    //    同时对老站点生效（Step3 生成时未做去重的内容，刷新即可清理）。
    if (function_exists('pipeline_dedupe_nav_placeholders')) {
        $prefix = pipeline_dedupe_nav_placeholders($prefix);
    }

    // 1. 前缀先做业务占位符替换（logo/SITE_NAME/NAV_LINKS/SCHEMA/TDK 等）。
    //    第三个参数 $raw_css 传空：不让 apply_placeholders 走它那套启发式 CSS 兜底，
    //    CSS 注入由本函数第 2 步确定性地完成，避免重复/丢失。
    $prefix = apply_placeholders($prefix, $data, '');

    // 2. 确定性注入整份 CSS 到 </head> 前（仅注入一次）。
    //    前缀里若已存在 <style>（理论上 Step3 已移除，这里做幂等保护），先确认是否已含本 CSS。
    if ($raw_css !== '') {
        $css_marker = substr(trim($raw_css), 0, 120);
        $already_injected = ($css_marker !== '' && strpos($prefix, $css_marker) !== false);
        if (!$already_injected) {
            $style_block = '<style data-pipeline-css>' . $raw_css . '</style>';
            // 系统级兜底：保证 main 与导航之间有间距、main 有最小高度，
            // 即使 AI 没给 main 写 padding 也不会贴在导航下面。
            $style_block .= '<style data-pipeline-base>'
                . '.site-main{padding:32px 20px;min-height:60vh;box-sizing:border-box;}'
                . '.site-breadcrumb{max-width:1200px;margin:16px auto 0;padding:0 20px;font-size:14px;color:#888;}'
                . '.site-breadcrumb a{color:#888;text-decoration:none;}'
                . '.site-breadcrumb a:hover{color:#1890ff;}'
                // 导航圆点兜底：系统注入的是裸 <li>（无 <ul> 包裹），AI 偶尔漏写 list-style:none。
                // 注意：ul/ol 清 padding 是为消默认左缩进；但 <li> 只清圆点、不要强制 margin/padding:0，
                // 否则会把 AI 写在 li 上的栏目间距一并干掉（历史上导致"栏目页导航名粘连、首页却正常"，
                // 因为这段 base 只注入栏目/文章页，首页不注入）。
                . 'nav ul,nav ol{list-style:none;margin:0;padding:0;}'
                . 'nav li,.nav-links li,.category-nav li,.nav-mobile li{list-style:none !important;}'
                // 导航布局兜底（修复栏目名挤在一起）：系统填充的是 <li><a> 结构（甚至无 <ul>），
                // AI 若按裸 <a> 写横排/间距会选择器不匹配，导致栏目竖排紧贴。
                // 用纯元素选择器保证桌面导航横向排列+间距；AI 用 class 写的导航样式特异性更高会自然覆盖。
                // 手机汉堡菜单在 div.nav-mobile 内（非 <nav> 标签），不受以下规则影响、保持竖排。
                . 'nav{display:flex;flex-wrap:wrap;align-items:center;gap:16px;}'
                . 'nav ul,nav ol{display:flex;flex-wrap:wrap;align-items:center;gap:18px;}'
                . 'nav li{display:inline-flex;align-items:center;}'
                . 'nav li a{display:inline-block;padding:6px 4px;text-decoration:none;white-space:nowrap;}'
                . '@media(max-width:768px){.site-main{padding:20px 14px;}.site-breadcrumb{padding:0 14px;}}'
                . '</style>';
            if (stripos($prefix, '</head>') !== false) {
                $prefix = preg_replace('/<\/head>/i', $style_block . "\n" . '</head>', $prefix, 1);
            } else {
                $prefix = $style_block . $prefix;
            }
        }
    }

    // 3. 前缀里的 TDK（title/meta/canonical）重建：管线首页的 <head> 是 AI 写死的首页 TDK，
    //    栏目/文章页需要用自己的 TDK 覆盖。inject_tdk_into_html 会清旧注入新，
    //    并保留 <head> 中已有的 <style>（含上一步刚注入的 pipeline CSS）。
    //    注意：page_title/meta_description/meta_keywords/canonical_url 必须从 $data 读，
    //    否则 AI 在 <head> 写死的首页 title/description 会被清掉但不补新的，导致栏目页
    //    <title> 只剩站点名、缺 meta description（历史 bug）。
    $tdk_opts = [
        'page_title'       => $data['{PAGE_TITLE}'] ?? ($data['page_title'] ?? ''),
        'meta_description' => $data['{META_DESCRIPTION}'] ?? ($data['meta_description'] ?? ''),
        'meta_keywords'    => $data['{META_KEYWORDS}'] ?? ($data['meta_keywords'] ?? ''),
        'canonical_url'    => $data['__canonical'] ?? ($data['canonical_url'] ?? ''),
        'schema_json'   => $data['schema_json'] ?? '',
        'og_tags'       => $data['og_tags'] ?? '',
        'verify_tags'   => $data['verify_tags'] ?? '',
        'custom_css'    => '', // CSS 已由本函数注入，这里传空避免重复
    ];
    $prefix = inject_tdk_into_html($prefix, $data, $tdk_opts);

    // 4. 清理前缀中残留的首页正文占位符（不应在 <main> 之外出现）。
    //    注意：AD_* 占位符不能清，由后续广告注入阶段替换。
    $prefix = preg_replace('/\{(SEO_CONTENT|ARTICLE_LIST|ARTICLE_CONTENT|FOOTER_COPYRIGHT|FOOTER|SIDEBAR|BANNER|CARD_GRID)\}/i', '', $prefix);

    // 5. 组装 main 内部
    $main_inner = apply_placeholders($main_inner, $data, '');
    // 主内容里若出现 {CUSTOM_CSS}（理论上不应有），清掉
    $main_inner = preg_replace('/<style[^>]*>\s*\{CUSTOM_CSS\}\s*<\/style>/i', '', $main_inner);
    $main_inner = preg_replace('/\{CUSTOM_CSS\}/i', '', $main_inner);

    // 6. 后缀处理：占位符替换（FOOTER 版权/SITEMAP/LAST_UPDATE 等）
    $suffix = apply_placeholders($suffix, $data, '');
    // 广告位 {AD_FOOTER} 应在后缀（footer 之前）
    // 弹窗广告在最终 HTML 上注入即可

    $html = $prefix . "\n" . $main_inner . "\n" . $suffix;

    // 7. 广告位注入（栏目/文章页统一入口；首页 Path A 在 show_home() 另有处理）
    //    去重统一用 ad_already_in_page() 按广告图片 URL / code 指纹判断，
    //    不再依赖 class 名 —— AI 生成的栏目/文章壳里可能没有 .ad-top 等类，
    //    或 AI 自己写了一张广告图，按 class 判断会漏判导致双份。
    $ad_top     = $data['ad_top']     ?? ($data['{AD_TOP}'] ?? '');
    $ad_content = $data['ad_content'] ?? ($data['{AD_CONTENT}'] ?? ($data['{AD_BELOW_ARTICLE}'] ?? ''));
    $ad_footer  = $data['ad_footer']  ?? ($data['{AD_FOOTER}'] ?? '');

    if (is_string($ad_top) && $ad_top !== '' && stripos($html, '<body') !== false) {
        if (!ad_already_in_page($html, $ad_top)) {
            $html = preg_replace('/(<body[^>]*>)/i', '$1' . "\n" . $ad_top, $html, 1);
        }
    }
    if (is_string($ad_content) && $ad_content !== '' && !ad_already_in_page($html, $ad_content)) {
        // 优先插到 </main> 前；否则 <footer 前；最后 </body> 前
        $injected_content_ad = false;
        if (!$injected_content_ad && stripos($html, '</main>') !== false) {
            $html = preg_replace('/<\/main>/i', $ad_content . "\n" . '</main>', $html, 1);
            $injected_content_ad = true;
        }
        if (!$injected_content_ad && preg_match('/<footer\b/i', $html)) {
            $html = preg_replace('/<footer\b/i', $ad_content . "\n" . '<footer', $html, 1);
            $injected_content_ad = true;
        }
        if (!$injected_content_ad && stripos($html, '</body>') !== false) {
            $html = str_replace('</body>', $ad_content . "\n" . '</body>', $html);
        }
    }
    if (is_string($ad_footer) && $ad_footer !== '' && stripos($html, '</body>') !== false) {
        if (preg_match('/<div\s+class=["\']ad-footer["\']\s*>\s*<\/div>/i', $html)) {
            $html = preg_replace('/<div\s+class=["\']ad-footer["\']\s*>\s*<\/div>/i', $ad_footer, $html, 1);
        } elseif (!ad_already_in_page($html, $ad_footer)) {
            $html = str_replace('</body>', $ad_footer . "\n" . '</body>', $html);
        }
    }

    // 8. 弹窗广告（</body> 前）
    try {
        $popup_html = render_popup_html(intval($site['id'] ?? 0));
        if ($popup_html) {
            $last_pos = strrpos($html, '</body>');
            if ($last_pos !== false) {
                $html = substr($html, 0, $last_pos) . $popup_html . "\n" . substr($html, $last_pos);
            }
        }
    } catch (Exception $e) { /* ignore */ }

    // 7.5 备案号兜底：AI 模板未写 {ICP_NUMBER} 时，在 </footer> 或 </body> 前注入
    // （管线壳路径之前漏了这段，导致后台设了备案号但前端不显示）
    $icp_val = isset($data['{ICP_NUMBER}']) ? (string)$data['{ICP_NUMBER}'] : '';
    if ($icp_val === '' && function_exists('get_setting')) {
        $icp_raw = get_setting('icp_number', $site['id']);
        if (!empty($icp_raw) && function_exists('build_icp_html')) {
            $icp_val = build_icp_html($icp_raw);
        }
    }
    if ($icp_val !== '' && stripos($html, 'icp-number') === false && stripos($html, $icp_val) === false) {
        $icp_block = '<div class="sys-icp" style="text-align:center;padding:12px 20px;font-size:13px;color:#888;background:transparent;">' . $icp_val . '</div>';
        $footer_pos = strripos($html, '</footer>');
        if ($footer_pos !== false) {
            $html = substr($html, 0, $footer_pos) . $icp_block . substr($html, $footer_pos);
        } else {
            $body_pos = strripos($html, '</body>');
            if ($body_pos !== false) {
                $html = substr($html, 0, $body_pos) . $icp_block . substr($html, $body_pos);
            }
        }
    }

    // 8. 残留占位符清理
    $html = preg_replace('/\{[A-Z_]{2,}\}/', '', $html);
    return $html;
}

/**
 * 当管线壳不完整时的最小骨架兜底（绝不退化成首页）
 */
function render_minimal_shell_page($site, $data, $main_inner) {
    $raw_css = $data['__raw_css'] ?? '';
    // 清掉 CSS 中残留的 {CUSTOM_CSS} 标记
    $raw_css = preg_replace('/\{custom_css\}/i', '', $raw_css);
    // 渲染时再做一次 CSS 清洗（兼容老数据）
    if (function_exists('sanitize_css')) {
        $raw_css = sanitize_css($raw_css);
    }
    $title = htmlspecialchars($data['{PAGE_TITLE}'] ?? $site['name'] ?? '', ENT_QUOTES, 'UTF-8');
    $desc = htmlspecialchars($data['{META_DESCRIPTION}'] ?? '', ENT_QUOTES, 'UTF-8');
    $kw = htmlspecialchars($data['{META_KEYWORDS}'] ?? '', ENT_QUOTES, 'UTF-8');
    $canonical = $data['__canonical'] ?? '';
    $schema = $data['schema_json'] ?? '';
    $og = $data['og_tags'] ?? '';
    $verify = $data['verify_tags'] ?? '';
    $site_name = htmlspecialchars($data['{SITE_NAME}'] ?? $site['name'] ?? '', ENT_QUOTES, 'UTF-8');
    $nav_links = $data['{NAV_LINKS}'] ?? '';
    $contact = $data['{CONTACT_INFO}'] ?? '';
    $footer_copy = $data['{FOOTER_COPYRIGHT}'] ?? '';
    $sitemap = $data['{SITEMAP_LINK}'] ?? '?p=sitemap';
    $last_update = $data['{LAST_UPDATE}'] ?? date('Y-m-d');

    $html = '<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8">';
    $html .= '<meta name="viewport" content="width=device-width,initial-scale=1.0">';
    $html .= '<title>' . $title . '</title>';
    if ($desc) $html .= '<meta name="description" content="' . $desc . '">';
    if ($kw) $html .= '<meta name="keywords" content="' . $kw . '">';
    if ($canonical) $html .= '<link rel="canonical" href="' . htmlspecialchars($canonical, ENT_QUOTES, 'UTF-8') . '">';
    $html .= $verify . $schema . $og;
    // 整份 CSS 确定性注入（不依赖 apply_placeholders 兜底）
    if ($raw_css) $html .= '<style data-pipeline-css>' . $raw_css . '</style>';
    $html .= '<style>.site-minimal-header{background:#fff;border-bottom:1px solid #eee;padding:16px 20px;max-width:1200px;margin:0 auto;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:12px}.site-minimal-header .brand{font-weight:700;font-size:18px;color:#222;text-decoration:none}.site-minimal-header nav{display:flex;flex-wrap:wrap;align-items:center;gap:16px}.site-minimal-header nav li{display:inline-flex;align-items:center;list-style:none;margin:0;padding:0}.site-minimal-header nav a{margin-left:0;color:#555;text-decoration:none;font-size:14px;white-space:nowrap}.site-minimal-header nav a:hover{color:#1890ff}.site-minimal-main{max-width:1200px;margin:24px auto;padding:0 20px;min-height:60vh}.site-minimal-footer{background:#1a1a2e;color:#ccc;padding:30px 20px;margin-top:40px}.site-minimal-footer .inner{max-width:1200px;margin:0 auto;text-align:center;font-size:14px;line-height:1.8}.site-minimal-footer a{color:#9bb}</style>';
    $html .= '</head><body>';
    $html .= '<header class="site-minimal-header"><a class="brand" href="/">' . $site_name . '</a><nav>' . $nav_links . '</nav></header>';
    $html .= '<main class="site-minimal-main">' . $main_inner . '</main>';
    $html .= '<footer class="site-minimal-footer"><div class="inner">';
    if ($contact) $html .= '<div>' . $contact . '</div>';
    $html .= '<div>' . $footer_copy . ' | <a href="' . htmlspecialchars($sitemap, ENT_QUOTES, 'UTF-8') . '">网站地图</a> | 最后更新：' . htmlspecialchars($last_update, ENT_QUOTES, 'UTF-8') . '</div>';
    // 备案号兜底
    $minimal_icp = '';
    if (function_exists('get_setting')) {
        $minimal_icp_raw = get_setting('icp_number', $site['id']);
        if (!empty($minimal_icp_raw) && function_exists('build_icp_html')) {
            $minimal_icp = build_icp_html($minimal_icp_raw);
        }
    }
    if ($minimal_icp !== '') {
        $html .= '<div style="margin-top:6px;opacity:.85;">' . $minimal_icp . '</div>';
    }
    $html .= '</div></footer>';
    try {
        $popup_html = render_popup_html(intval($site['id'] ?? 0));
        if ($popup_html) $html .= $popup_html;
    } catch (Exception $e) { /* ignore */ }
    $html .= '</body></html>';
    return $html;
}


function apply_placeholders($html, $data, $raw_css_for_replace = '') {
    // v3.3 DEBUG: 记录调用上下文
    $has_custom_css = (preg_match('/\{custom_css\}/i', $html) > 0);
    $css_len = strlen($raw_css_for_replace);
    debug_log('[v3.3 DEBUG] apply_placeholders: has_custom_css=' . ($has_custom_css ? 'yes' : 'no') . ', css_len=' . $css_len . ', data_keys=' . count($data));
    
    // ========== 第一轮：所有业务占位符（str_ireplace 大小写不敏感） ==========
    foreach ($data as $placeholder => $value) {
        if (is_string($placeholder) && strpos($placeholder, '{') === 0) {
            $html = str_ireplace($placeholder, $value, $html);
        }
    }
    
    // ========== 第二轮：{CUSTOM_CSS} 特殊处理 ==========
    // v3.3: 先替换再清理，确保占位符被正确替换而非被空清除
    if (!empty($raw_css_for_replace)) {
        // 清理 raw_css 中可能残留的 {CUSTOM_CSS} 字面文本
        $raw_css_for_replace = preg_replace('/\{custom_css\}/i', '', $raw_css_for_replace);
        
        // v3.3 DEBUG: 记录替换前后状态
        $before_replace = (strpos($html, '{CUSTOM_CSS}') !== false || stripos($html, '{custom_css}') !== false);
        
        // v3.8 FIX: 检查 {CUSTOM_CSS} 是否已被 <style> 标签包裹
        // 如果模板是 <style>{CUSTOM_CSS}</style>，直接替换为纯CSS
        // 如果模板只是 {CUSTOM_CSS}（没有<style>包裹），需要包裹<style>标签
        $html = preg_replace_callback('/<style[^>]*>\s*\{CUSTOM_CSS\}\s*<\/style>/i', function($m) use ($raw_css_for_replace) {
            return '<style>' . $raw_css_for_replace . '</style>';
        }, $html);
        // 处理剩余的 {CUSTOM_CSS}（没有被<style>包裹的情况）
        $html = str_ireplace('{CUSTOM_CSS}', '<style>' . $raw_css_for_replace . '</style>', $html);
        
        $after_replace = (strpos($html, '{CUSTOM_CSS}') !== false || stripos($html, '{custom_css}') !== false);
        debug_log('[v3.3 DEBUG] apply_placeholders: CUSTOM_CSS 替换前=' . ($before_replace ? '存在' : '不存在') . ', 替换后=' . ($after_replace ? '仍存在' : '已移除') . ', css_len=' . strlen($raw_css_for_replace));
    }
    
    // 然后清理残留的 {CUSTOM_CSS}（无论CSS是否为空都必须移除，防止残留）
    $html = preg_replace('/\{custom_css\}/i', '', $html);
    
    // 如果 $raw_css_for_replace 不为空但占位符替换后仍有内容需要注入（兜底：无占位符的旧模板）
    if (!empty($raw_css_for_replace) && !preg_match('/\{custom_css\}/i', $html)) {
        // 检查CSS是否已通过占位符替换注入（避免重复注入）
        // 如果HTML中已包含 raw_css 的内容特征，说明替换已生效，跳过
        $css_snippet = substr(trim($raw_css_for_replace), 0, 80);
        if ($css_snippet && strpos($html, $css_snippet) === false) {
            // CSS未注入：兜底注入到最后一个 </style> 之前
            if (preg_match('/<style[^>]*>/i', $html)) {
                $last_style_pos = strrpos($html, '</style>');
                if ($last_style_pos !== false) {
                    $html = substr_replace($html, "\n" . $raw_css_for_replace . "\n", $last_style_pos, 0);
                }
            } elseif (stripos($html, '</head>') !== false) {
                $html = str_ireplace('</head>', "<style>\n" . $raw_css_for_replace . "\n</style>\n</head>", $html);
            } else {
                $html = "<style>\n" . $raw_css_for_replace . "\n</style>\n" . $html;
            }
        }
    }
    
    // ========== 第三轮：兜底清理所有未被替换的占位符 ==========
    // 匹配 {大写+下划线} 或 {小写+下划线} 格式（至少2字符），避免误伤CSS花括号
    $html = preg_replace('/\{[A-Za-z_]{2,}\}/', '', $html);
    
    // v3.3 DEBUG: 验证处理结果
    $still_has_custom_css = (preg_match('/\{custom_css\}/i', $html) > 0);
    if ($still_has_custom_css) {
        debug_log('[v3.3 DEBUG] apply_placeholders: WARNING - {CUSTOM_CSS} still present after all processing!');
    }
    
    return $html;
}

function render_site_template($template_html, $data, $type) {
    try {
    // ========== v3.0 统一占位符替换层 ==========
    // 所有简单占位符替换统一走 apply_placeholders()，大小写不敏感，兜底清理
    $raw_css_for_replace = preg_replace('/^\s*<style[^>]*>\s*/i', '', $data['custom_css'] ?? '');
    $raw_css_for_replace = preg_replace('/\s*<\/style>\s*$/i', '', $raw_css_for_replace);
    
    $placeholders = [
        '{SITE_NAME}'          => $data['site_name'],
        '{SITE_DESCRIPTION}'   => $data['site_description'],
        '{SITE_DOMAIN}'        => $data['site_domain'],
        '{PAGE_TITLE}'         => $data['page_title'],
        '{META_TITLE}'         => $data['meta_title'] ?? $data['page_title'],
        '{META_DESCRIPTION}'   => $data['meta_description'],
        '{META_KEYWORDS}'      => $data['meta_keywords'] ?? '',
        '{CANONICAL_URL}'      => $data['canonical_url'],
        '{NAV_LINKS}'          => $data['nav_links'],
        '{FOOTER}'             => $data['footer'] ?? '',
        '{BREADCRUMB}'         => $data['breadcrumb'] ?? '',
        '{ARTICLE_TITLE}'      => $data['article_title'] ?? '',
        '{ARTICLE_DATE}'       => $data['article_date'] ?? '',
        '{ARTICLE_CONTENT}'    => $data['article_content'] ?? '',
        '{ARTICLE_UPDATE}'     => $data['article_update'] ?? '',
        '{RELATED_ARTICLES}'   => $data['related_articles'] ?? '',
        '{HOT_ARTICLES}'       => $data['hot_articles'] ?? '',
        '{CONTACT_INFO}'       => $data['contact_info'] ?? '',
        '{FOOTER_COPYRIGHT}'   => $data['footer_copyright'] ?? '',
        '{ICP_NUMBER}'         => $data['icp_number'] ?? '',
        '{LAST_UPDATE}'        => $data['last_update'] ?? '',
        '{SITEMAP_LINK}'       => htmlspecialchars('/sitemap.xml'),
        '{SCHEMA_JSON}'        => $data['schema_json'],
        '{VERIFY_TAGS}'        => $data['verify_tags'],
        '{OG_TAGS}'            => $data['og_tags'] ?? '',
    ];
    
    $html = apply_placeholders($template_html, $placeholders, $raw_css_for_replace);
    
    // ========== {FOOTER}占位符处理 ==========
    $footer_content = $data['footer'] ?? '';
    if (!empty($footer_content) && strpos($template_html, '{FOOTER}') === false) {
        // 模板中没有{FOOTER}占位符但有<footer>标签，替换为统一footer
        // v3.12修复：先处理footer中的占位符再替换，避免{CONTACT_INFO}等被覆盖回原始占位符
        $footer_content = apply_placeholders($footer_content, $placeholders, '');
        if (preg_match('/<footer[^>]*>([\s\S]*?)<\/footer>/i', $html, $match)) {
            $html = str_replace($match[0], $footer_content, $html);
        }
    }
    
    // ========== 按页面类型区分替换 ==========
    $is_article_page = ($type === 'article');
    $is_column_page = !empty($data['is_column_page']);
    
    // 广告位：文章页和栏目页都不显示
    $EMPTY_SLOT = '<!-- SLOT:EMPTY -->';
    $html = str_replace('{AD_TOP}', ($is_article_page || $is_column_page) ? $EMPTY_SLOT : $data['ad_top'], $html);
    $html = str_replace('{AD_SIDE}', ($is_article_page || $is_column_page) ? $EMPTY_SLOT : $data['ad_side'], $html);
    $html = str_replace('{AD_FOOTER}', ($is_article_page || $is_column_page) ? $EMPTY_SLOT : $data['ad_footer'], $html);
    $html = str_replace('{AD_CONTENT}', ($is_article_page || $is_column_page) ? $EMPTY_SLOT : ($data['ad_content'] ?? ''), $html);
    
    // 文章列表：首页和栏目页显示，文章页不显示
    $html = str_replace('{ARTICLE_LIST}', $is_article_page ? $EMPTY_SLOT : $data['article_list'], $html);
    
    // SEO内容：首页和栏目页显示（栏目页有独立内容），文章页不显示
    $seo_val = $is_article_page ? $EMPTY_SLOT : $data['seo_content'];
    $html = str_replace('{SEO_CONTENT}', $seo_val, $html);
    
    // 【修复SEO内容重复】
    if (!$is_article_page && !empty($data['seo_content'])) {
        $seo_text = $data['seo_content'];
        $seo_plain = trim(strip_tags($seo_text));
        if (!empty($seo_plain) && mb_strlen($seo_plain) > 50) {
            $html_plain = strip_tags($html);
            $count = substr_count($html_plain, $seo_plain);
            if ($count > 1) {
                $first_pos = mb_strpos($html, $seo_text);
                if ($first_pos !== false) {
                    $second_pos = mb_strpos($html, $seo_text, $first_pos + mb_strlen($seo_text));
                    if ($second_pos !== false) {
                        $html = mb_substr($html, 0, $second_pos) . mb_substr($html, $second_pos + mb_strlen($seo_text));
                    }
                }
            }
        }
    }
    
    // 图片画廊：已移除图库占位符功能
    $html = str_replace('{IMAGE_GALLERY}', '', $html);
    $html = preg_replace('/<section[^>]*class="[^"]*image-gallery[^"]*"[^>]*>[\s\S]*?<\/section>/i', '', $html);
    
    // Banner区域：首页显示，文章页和栏目页替换为栏目标题
    if (preg_match('/<section[^>]*class="[^"]*banner[^"]*"[^>]*>[\s\S]*?<\/section>/i', $html, $banner_match)) {
        if ($is_article_page) {
            $html = str_replace($banner_match[0], '', $html);
        } elseif ($is_column_page) {
            $page_title = htmlspecialchars($data['page_title'] ?? '');
            $column_hero = '<section class="column-hero" style="padding:40px 20px;text-align:center;background:linear-gradient(135deg,#667eea 0%,#764ba2 100%);color:#fff;">';
            $column_hero .= '<h1 style="margin:0;font-size:2em;font-weight:700;">' . $page_title . '</h1>';
            $column_hero .= '</section>';
            $html = str_replace($banner_match[0], $column_hero, $html);
        }
    }
    if ($is_column_page && preg_match('/<section[^>]*class="[^"]*(?:hero|intro|showcase)[^"]*"[^>]*>[\s\S]*?<\/section>/i', $html, $hero_match)) {
        if (strpos($hero_match[0], 'column-hero') === false) {
            $page_title = htmlspecialchars($data['page_title'] ?? '');
            $column_hero = '<section class="column-hero" style="padding:40px 20px;text-align:center;background:linear-gradient(135deg,#667eea 0%,#764ba2 100%);color:#fff;">';
            $column_hero .= '<h1 style="margin:0;font-size:2em;font-weight:700;">' . $page_title . '</h1>';
            $column_hero .= '</section>';
            $html = str_replace($hero_match[0], $column_hero, $html);
        }
    }
    
    // 卡片网格区域：只有首页显示
    if (preg_match('/<section[^>]*class="[^"]*card-grid[^"]*"[^>]*>[\s\S]*?<\/section>/i', $html, $grid_match)) {
        if ($is_article_page || $is_column_page) {
            $html = str_replace($grid_match[0], '', $html);
        }
    }
    
    // 修复嵌套：如果模板中是 <p>{CONTACT_INFO}</p> 而 contact_info 自带 <p>
    $html = preg_replace('/<p>\s*\{CONTACT_INFO\}\s*<\/p>/i', '{CONTACT_INFO}', $html);
    
    // 如果模板中没有{ICP_NUMBER}占位符，强制注入备案号
    $icp_val = $data['icp_number'] ?? '';
    if (!empty($icp_val) && strpos($html, $icp_val) === false) {
        $icp_html = '<div class="icp-number" style="text-align:center;padding:10px 0;color:#999;font-size:13px;">' . $icp_val . '</div>';
        if (($footer_pos = stripos($html, '</footer>')) !== false) {
            $html = substr_replace($html, $icp_html, $footer_pos + 9, 0);
        } elseif (($body_pos = stripos($html, '</body>')) !== false) {
            $html = substr_replace($html, $icp_html, $body_pos, 0);
        } else {
            $html .= $icp_html;
        }
    }
    
    // ========== 修复BUG-PLACEHOLDER: 占位符位置错误导致HTML结构混乱 ==========
    // 问题：AI可能在{ARTICLE_LIST}后面错误地放了{SEO_CONTENT}或{IMAGE_GALLERY}
    // 导致：<li>...</li><section class="seo-content">...</section> 出现在文章列表容器内
    // 修复：检测并移除错位的内容，重新放到正确位置
    
    // 检测：文章列表容器内是否嵌入了 section 标签（这是错误的）
    // 模式：<li>...</li> 后紧跟 <section class="seo-content"> 或 <section class="image-gallery">
    $misplaced_patterns = [
        // 匹配错位的 seo-content section
        '/(<\/li>\s*<section[^>]*class="[^"]*seo-content[^"]*"[^>]*>[\s\S]*?<\/section>)/i',
    ];
    
    $misplaced_content = '';
    foreach ($misplaced_patterns as $pattern) {
        if (preg_match_all($pattern, $html, $matches)) {
            foreach ($matches[1] as $misplaced) {
                // 提取错位内容
                $misplaced_content .= $misplaced;
                // 从原位置移除
                $html = str_replace($misplaced, '', $html);
            }
        }
    }
    
    // 如果有错位内容，在 </main> 前或 footer 前插入
    if (!empty($misplaced_content)) {
        // 优先在 </main> 前插入
        if (strpos($html, '</main>') !== false) {
            $html = str_replace('</main>', $misplaced_content . "\n</main>", $html);
        } elseif (strpos($html, '<footer') !== false) {
            // 否则在 footer 前插入
            $html = preg_replace('/(<footer[^>]*>)/i', $misplaced_content . "\n$1", $html, 1);
        }
    }
    
    // ========== 兜底逻辑修复：确保HTML结构完整 ==========
    // 先记录原始模板中有哪些占位符，替换后再判断是否需要兜底插入
    $original_has = [
        'AD_TOP'         => strpos($template_html, '{AD_TOP}') !== false,
        'SEO_CONTENT'    => strpos($template_html, '{SEO_CONTENT}') !== false,
        'ARTICLE_LIST'   => strpos($template_html, '{ARTICLE_LIST}') !== false,
        'CONTACT_INFO'   => strpos($template_html, '{CONTACT_INFO}') !== false,
        'AD_FOOTER'      => strpos($template_html, '{AD_FOOTER}') !== false,
        'FOOTER_COPYRIGHT' => strpos($template_html, '{FOOTER_COPYRIGHT}') !== false,
        'BREADCRUMB'     => strpos($template_html, '{BREADCRUMB}') !== false,
        'HOT_ARTICLES'   => strpos($template_html, '{HOT_ARTICLES}') !== false,
        'RELATED_ARTICLES' => strpos($template_html, '{RELATED_ARTICLES}') !== false,
        'META_TITLE'     => strpos($template_html, '{META_TITLE}') !== false,
        'META_KEYWORDS'  => strpos($template_html, '{META_KEYWORDS}') !== false,
        'LAST_UPDATE'    => strpos($template_html, '{LAST_UPDATE}') !== false,
    ];
    
    // 如果HTML不完整（缺少</body>或</html>），先修复结构
    
    // 检查并修复HTML结构
    if (strpos($html, '</body>') === false || strpos($html, '</html>') === false) {
        // HTML结构不完整，移除所有未闭合的标签，添加完整结构
        $html = preg_replace('/<body[^>]*>/i', '<body>', $html);
        if (strpos($html, '</body>') === false) {
            $html .= '</body>';
        }
        if (strpos($html, '</html>') === false) {
            $html .= '</html>';
        }
    }
    
    // 使用安全的插入方法：找到最后一个 </body> 之前插入内容
    $last_body_pos = strrpos($html, '</body>');
    
    // ========== 兜底内容插入（仅首页/栏目页/标签页需要，article模板是AI生成的完整页面，不需要兜底） ==========
    if ($type !== 'article' && $last_body_pos !== false) {
        $fallback_content = '';
        
        // 兜底内容按语义顺序排列：SEO内容 → 文章列表 → 图片画廊 → 联系方式
        // 广告注入已移至 show_home 函数中统一处理，确保在所有HTML处理完成后注入
        
        // 兜底：SEO内容（栏目页用独立class，避免被CSS隐藏规则误杀）
        // 【修复Bug二】栏目页兜底内容使用 .column-content-box，不在CSS隐藏列表中
        $seo_box_class = $is_column_page ? 'column-content-box' : 'seo-content-box';
        if (!$original_has['SEO_CONTENT'] && !empty($data['seo_content'])) {
            $fallback_content .= '<div class="' . $seo_box_class . '" style="max-width:1200px;margin:20px auto;padding:20px;background:transparent;border-radius:8px;">' . $data['seo_content'] . '</div>';
        }
        
        // 兜底：文章列表（栏目页用独立class）
        // 注意：$data['article_list'] 已经包含 <ul> 标签，不要重复包裹
        $list_box_class = $is_column_page ? 'column-article-list-box' : 'article-list-box';
        if (!$original_has['ARTICLE_LIST'] && !empty($data['article_list'])) {
            $fallback_content .= '<div class="' . $list_box_class . '" style="max-width:1200px;margin:20px auto;padding:20px;background:transparent;border-radius:8px;"><h3 style="margin-top:0;border-bottom:2px solid #1890ff;padding-bottom:10px;">最新文章</h3>' . $data['article_list'] . '</div>';
        }
        
        // 兜底：底部广告（模板无{AD_FOOTER}占位符时，自动在</body>前注入）
        if (!$original_has['AD_FOOTER'] && !empty($data['ad_footer'])) {
            $fallback_content .= $data['ad_footer'];
        }
        
        // 一次性插入所有兜底内容
        if (!empty($fallback_content)) {
            $html = substr($html, 0, $last_body_pos) . $fallback_content . substr($html, $last_body_pos);
        }
    }
    
    // 兜底：面包屑导航（article类型跳过）
    if ($type !== 'article' && !$original_has['BREADCRUMB'] && !empty($data['breadcrumb'])) {
        $html = preg_replace('/<body[^>]*>/i', '<body><div class="breadcrumb" style="max-width:1200px;margin:10px auto;padding:0 20px;font-size:14px;color:#666;">' . $data['breadcrumb'] . '</div>', $html, 1);
    }
    
    // 兜底：热门文章（article类型跳过）
    if ($type !== 'article' && !$original_has['HOT_ARTICLES'] && !empty($data['hot_articles'])) {
        $last_body_pos = strrpos($html, '</body>');
        if ($last_body_pos !== false) {
            $html = substr($html, 0, $last_body_pos) . '<div class="hot-articles" style="max-width:1200px;margin:20px auto;padding:20px;"><h3>热门文章</h3>' . $data['hot_articles'] . '</div>' . substr($html, $last_body_pos);
        }
    }
    
    // 兜底：相关文章（article类型跳过）
    if ($type !== 'article' && !$original_has['RELATED_ARTICLES'] && !empty($data['related_articles'])) {
        $last_body_pos = strrpos($html, '</body>');
        if ($last_body_pos !== false) {
            $html = substr($html, 0, $last_body_pos) . '<div class="related-articles" style="max-width:1200px;margin:20px auto;padding:20px;background:#f5f5f5;"><h3>相关文章</h3>' . $data['related_articles'] . '</div>' . substr($html, $last_body_pos);
        }
    }
    
    // 强制：把模板中的"网站地图"纯文本替换为真实链接
    $html = str_replace('网站地图', '<a href="/sitemap.xml">网站地图</a>', $html);
    
    // 清理AI模板中的占位符误用（放在标签属性内等）
    $html = clean_template_placeholders($html);
    
    // 自动清理外部脚本（非白名单的script src）— 渲染时移除，防止安全风险
    $html = preg_replace('/<script[^>]*\bsrc\s*=\s*[\'"]https?:\/\/(?!fonts\.googleapis|cdn\.jsdelivr|cdnjs\.cloudflare)[^\'"]*[\'"][^>]*><\/script>/i', '', $html);
    
    // 【修复Bug三】清理空容器（占位符被标记为SLOT:EMPTY后，外层容器残留导致的空白模块）
    // 必须在占位符清理之后、clean_fake_nav_links之前调用：
    // 1. 占位符已清空 → SLOT:EMPTY标记可被识别
    // 2. 导航链接还未被过滤 → 包含有效链接的容器不会被误删
    $html = clean_empty_containers($html);
    
    // 清理AI生成的虚假导航链接（关于我们、联系我们等不存在页面的链接）
    $html = clean_fake_nav_links($html);
    
    // 修复AI模板中可能生成的错误文章链接格式
    // 匹配 href="article-123.html" 或 href="#article-123" 或 href="article/123" 等变体
    $html = preg_replace_callback(
        '/href=["\']([^"\']*?)(?:article[-\/]?(\\d+)|#article[-]?(\\d+))([^"\']*?)["\']/i',
        function($m) {
            $id = !empty($m[2]) ? $m[2] : $m[3];
            return 'href="' . site_url('article', ['id' => $id]) . '"';
        },
        $html
    );
    
    // 将AI模板中的动态URL ?p=article&id=X 转换为伪静态URL
    $html = preg_replace_callback(
        '/href=["\']\?p=article&amp;id=(\d+)["\']/i',
        function($m) {
            return 'href="' . site_url('article', ['id' => $m[1]]) . '"';
        },
        $html
    );
    $html = preg_replace_callback(
        '/href=["\']\?p=article&id=(\d+)["\']/i',
        function($m) {
            return 'href="' . site_url('article', ['id' => $m[1]]) . '"';
        },
        $html
    );
    
    // 修复AI模板中侧边栏 href="#" 的空链接 → 替换为 /tag/分类名.html 关键词聚合页
    // 匹配如 <a href="#">抖音粉丝增长</a> 或 <a href="# ">服务分类</a>
    // 注意：只匹配 href="#" 纯锚点，不会误伤 href="#top" 等有意义的锚点
    $html = preg_replace_callback(
        '/<a\s+href=["\']#\s*["\']([^>]*)>([^<]+)<\/a>/i',
        function($m) {
            $text = trim($m[2]);
            if (empty($text) || mb_strlen($text) > 20) {
                // 文本太长或为空，改为不可点击的span
                return '<span class="tag-item">' . htmlspecialchars($text) . '</span>';
            }
            // 生成 /tag/分类名.html 链接
            $tag_url = '/tag/' . urlencode($text) . '.html';
            return '<a href="' . $tag_url . '">' . htmlspecialchars($text) . '</a>';
        },
        $html
    );
    
    // ========== 修复AI模板中指向不存在页面的链接 ==========
    // 白名单机制：只保留已知存在的页面链接（首页、栏目页、文章页、tag页）
    // 其他 /xxx.html 格式的链接改为 javascript:void(0)
    // 注意：包裹try-catch，查询失败时跳过过滤，避免页面崩溃
    $valid_slugs = [];
    $sid = intval($data['site_id'] ?? 0);
    if ($sid > 0) {
        try {
            $pages = db_get_all("SELECT slug FROM " . table('site_pages') . " WHERE site_id = $sid AND slug != ''");
            foreach ($pages as $p) { $valid_slugs[] = $p['slug']; }
        } catch (Exception $e) {
            // 查询失败时跳过白名单过滤，保留所有链接
            $valid_slugs = null;
        }
    }
    // 仅在白名单查询成功时执行过滤
    if ($valid_slugs !== null) {
        $html = preg_replace_callback(
            '/href=["\'](\/[a-z0-9_-]+\.(?:html?|xml))["\']/i',
            function($m) use ($valid_slugs) {
                $url = $m[1];
                // 首页
                if ($url === '/index.html' || $url === '/') return $m[0];
                // 文章页
                if (preg_match('/^\/article\/\d+\.html?$/', $url)) return $m[0];
                // tag页
                if (preg_match('/^\/tag\//', $url)) return $m[0];
                // 网站地图（修复Bug一：sitemap.xml是合法链接，不应被替换为javascript:void(0)）
                if (preg_match('/^\/?sitemap\.xml$/i', $url)) return $m[0];
                // 栏目页：检查是否是真实存在的栏目slug
                $slug = preg_replace('/^\/|\.html?$|\.xml$/', '', $url);
                if (in_array($slug, $valid_slugs)) return $m[0];
                // 不在白名单中 → 替换为 javascript:void(0)
                return 'href="javascript:void(0)"';
            },
            $html
        );
    }
    
    // ========== 栏目页/标签页：移除首页模板中可能存在的硬编码面包屑 ==========
    // 问题根因：AI生成的首页模板可能包含硬编码的面包屑导航，栏目页复用时会出现两个面包屑
    if ($type === 'page' || $type === 'tag') {
        // 移除常见的硬编码面包屑元素（保留 {BREADCRUMB} 占位符）
        $html = preg_replace('/<nav[^>]*class="[^"]*breadcrumb[^"]*"[^>]*>[\s\S]*?<\/nav>/i', '', $html);
        $html = preg_replace('/<div[^>]*class="[^"]*breadcrumb[^"]*"[^>]*>[\s\S]*?<\/div>/i', '', $html);
        $html = preg_replace('/<ol[^>]*class="[^"]*breadcrumb[^"]*"[^>]*>[\s\S]*?<\/ol>/i', '', $html);
        $html = preg_replace('/<ul[^>]*class="[^"]*breadcrumb[^"]*"[^>]*>[\s\S]*?<\/ul>/i', '', $html);
        // 移除 id="breadcrumb" 的元素
        $html = preg_replace('/<[^>]*id="breadcrumb"[^>]*>[\s\S]*?<\/[^>]+>/i', '', $html);
    }
    // 模板拆分方案：不再需要CSS隐藏规则（栏目页/文章页用组件拼装，不复用首页模板）
    
    // 清理重复的 </body></html> 标签组合（处理 AI 模板已包含闭合标签的情况）
    // 匹配 </body></html> 后面又跟着 </body> 或 </html> 的情况，只删除重复部分，保留第一对
    $html = preg_replace('/(<\/body\s*>\s*<\/html\s*>\s*)(<\/body\s*>\s*|<\/html\s*>\s*)+/i', '$1', $html);
    // 处理可能残留的单独重复 </body> 或 </html>（非成对出现的情况）
    $html = preg_replace('/(<\/body\s*>\s*){2,}/i', '</body>' . "\n", $html);
    $html = preg_replace('/(<\/html\s*>\s*){2,}/i', '</html>' . "\n", $html);
    
    // 清理 <body> 标签后可能残留的 "html" 文字（AI模板解析残留的 ```html 标记）
    $html = preg_replace('/(<body[^>]*>)\s*\n?\s*html\s*\n/i', '$1' . "\n", $html);
    
    // ========== 修复文章中图片相对路径 ==========
    // 伪静态URL下，相对路径会解析错误（如 /article/123.html 下的 uploads/xxx.jpg 变成 /article/uploads/xxx.jpg）
    // 将 src="uploads/ 开头的路径改为绝对路径 src="/uploads/
    $html = preg_replace('/(src=["\'])(uploads\/)/i', '$1/$2', $html);
    // 同理修复 href 中的 uploads 路径
    $html = preg_replace('/(href=["\'])(uploads\/)/i', '$1/$2', $html);
    
    // ========== 兜底结束 ==========
    
    // 处理导航链接的激活状态
    $html = str_replace('{NAV_HOME_ACTIVE}', ($type == 'home') ? 'class="active"' : '', $html);
    
    // 注入最小兜底CSS（防止内容溢出，不压制模板灵活性）
    $safety_css = <<<'CSS'
<style>
html{overflow-x:hidden}
img,video,iframe{max-width:100%;height:auto}
/* 文章内容区域最小宽度保证（防止文本框太窄） */
.article-content,.main-content,.article-detail,.post-content,.entry-content{min-width:280px;max-width:min(800px,100%);margin:0 auto;padding:0 16px;box-sizing:border-box}
/* footer 链接防止溢出 */
footer a,.footer a{display:inline-block;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:bottom}
footer{overflow:visible}
@media (max-width:768px){.sys-detail-wrap{flex-direction:column}.sys-detail-wrap>aside{width:100%!important;float:none!important;margin-top:20px}}
@media (max-width:480px){.sys-detail-wrap{padding:10px!important}.sys-detail-wrap>main{padding:8px!important}h1{font-size:1.5em!important}h2{font-size:1.3em!important}.article-content,.main-content,.article-detail,.post-content,.entry-content{padding:0 8px!important}}
</style>
CSS;

    // 全局文字颜色覆盖（【修复样式冲突-P1】仅当文字与背景对比度极低时才兜底，不再 !important 全量覆盖AI设计）
    $text_color = $data['text_color'] ?? '';
    if (!empty($text_color) && preg_match('/^#[0-9a-fA-F]{3,6}$/', $text_color)) {
        $bg_color = $data['bg_color'] ?? '#ffffff';
        $need_contrast_fix = false;
        if (preg_match('/^#[0-9a-fA-F]{3,6}$/', $bg_color)) {
            $bg_lum = _relative_luminance($bg_color);
            $txt_lum = _relative_luminance($text_color);
            $contrast = (max($bg_lum, $txt_lum) + 0.05) / (min($bg_lum, $txt_lum) + 0.05);
            if ($contrast < 2.0) {
                $fallback_color = $bg_lum > 0.5 ? '#333333' : '#ffffff';
                $safety_css .= "\n<style>body,p,span,li,td,th,div:not(footer):not(.footer),h1,h2,h3,h4,h5,h6{color:" . $fallback_color . "}a{color:" . ($bg_lum > 0.5 ? '#1a73e8' : '#8ab4f8') . "}</style>";
                $need_contrast_fix = true;
            }
        }
        if (!$need_contrast_fix) {
            // 仅对无class的a标签做微调，不使用!important
            $safety_css .= "\n<style>a:not([class]){color:" . htmlspecialchars($text_color) . "}</style>";
        }
    }
    if (strpos($html, '</head>') !== false) {
        $html = str_replace('</head>', $safety_css . "\n</head>", $html);
    }
    
    // ========== 城市分站变量替换 ==========
    if (!empty($GLOBALS['is_city_channel']) && !empty($GLOBALS['city_channel'])) {
        $html = replace_city_variables($html, $GLOBALS['city_channel']);
    }
    
    // ========== 兜底：清理所有未被替换的 {PLACEHOLDER} 占位符 ==========
    // 仅匹配全大写+下划线格式（至少2字符），避免误伤CSS花括号
    $html = preg_replace('/\{[A-Z_]{2,}\}/', '', $html);
    
    // ========== SEO: 兜底注入 canonical + RSS alternate ==========
    // 覆盖 render_site_template 路径（文章页/栏目页/标签页回退路径）
    $canonical_url = $data['canonical_url'] ?? '';
    $site_domain = $data['site_domain'] ?? '';
    $head_inject = '';
    if (!empty($canonical_url) && stripos($html, 'rel="canonical"') === false) {
        $head_inject .= '<link rel="canonical" href="' . htmlspecialchars($canonical_url) . '">' . "\n";
    }
    if (!empty($site_domain) && stripos($html, 'application/rss+xml') === false) {
        $head_inject .= '<link rel="alternate" type="application/rss+xml" title="Sitemap" href="' . htmlspecialchars($site_domain) . '/sitemap.xml">' . "\n";
    }
    // GEO: 内容摘要 meta 标签（AI 搜索引擎优先引用有清晰摘要的内容）
    $article_summary = $data['article_summary'] ?? '';
    if (!empty($article_summary) && stripos($html, 'name="summary"') === false) {
        $head_inject .= '<meta name="summary" content="' . $article_summary . '">' . "\n";
    }
    if (!empty($head_inject) && stripos($html, '</head>') !== false) {
        $html = str_replace('</head>', $head_inject . '</head>', $html);
    }
    
    // ========== 自定义 HEAD 代码（注入到 <head> 标签上方） ==========
    $head_code_site_id = intval($data['site_id'] ?? 0);
    $head_code = get_setting('head_code', $head_code_site_id) ?? '';
    if (!empty($head_code) && preg_match('/<head[\s>]/i', $html, $m, PREG_OFFSET_CAPTURE)) {
        $head_pos = $m[0][1];
        $html = substr($html, 0, $head_pos) . $head_code . "\n" . substr($html, $head_pos);
    }
    
    // ========== 弹窗广告注入（render_site_template 路径） ==========
    $popup_site_id = intval($data['site_id'] ?? 0);
    $popup_html = render_popup_html($popup_site_id);
    if (!empty($popup_html)) {
        $last_body = strrpos($html, '</body>');
        if ($last_body !== false) {
            $html = substr($html, 0, $last_body) . $popup_html . "\n" . substr($html, $last_body);
        } else {
            $html .= "\n" . $popup_html;
        }
    }
    
    // ========== 最终清理：去除重复的 </body></html> ==========
    $html = preg_replace('/(<\/body\s*>\s*<\/html\s*>\s*)(<\/body\s*>\s*|<\/html\s*>\s*)+/i', '$1', $html);
    $html = preg_replace('/(<\/body\s*>\s*){2,}/i', '</body>' . "\n", $html);
    $html = preg_replace('/(<\/html\s*>\s*){2,}/i', '</html>' . "\n", $html);
    
    return $html;
    } catch (Exception $e) {
        // 渲染后处理出错时，至少输出基本模板内容
        return $template_html . "\n<!-- render error: " . htmlspecialchars($e->getMessage()) . " -->";
    }
}

/**
 * 构建单页面模板数据（精简版）
 * 只处理基本的占位符替换，不做复杂的栏目/导航处理
 */
function build_single_page_data($site, $site_template = null) {
    $data = [];
    
    // 站点基本信息
    $data['{SITE_NAME}'] = htmlspecialchars($site['site_name'] ?? '');
    $data['{SITE_TITLE}'] = htmlspecialchars($site['title'] ?? $site['site_name'] ?? '');
    $data['{SITE_DESCRIPTION}'] = htmlspecialchars($site['description'] ?? '');
    $data['{SITE_KEYWORDS}'] = htmlspecialchars($site['keywords'] ?? '');
    $data['{SITE_DOMAIN}'] = htmlspecialchars($site['domain'] ?? '');
    
    // 根据站点实际协议构建URL（使用公共函数）
    $site_url = build_site_base_url($site);
    $data['{SITE_URL}'] = htmlspecialchars(rtrim($site_url, '/'));
    
    // 联系方式
    $contact_phone = get_setting('contact_phone', $site['id']) ?? '';
    $contact_email = get_setting('contact_email', $site['id']) ?? '';
    $contact_address = get_setting('contact_address', $site['id']) ?? '';
    $contact_wechat = get_setting('contact_wechat', $site['id']) ?? '';
    $contact_qq = get_setting('contact_qq', $site['id']) ?? '';
    
    $data['{CONTACT_PHONE}'] = htmlspecialchars($contact_phone);
    $data['{CONTACT_EMAIL}'] = htmlspecialchars($contact_email);
    $data['{CONTACT_ADDRESS}'] = htmlspecialchars($contact_address);
    $data['{CONTACT_WECHAT}'] = htmlspecialchars($contact_wechat);
    $data['{CONTACT_QQ}'] = htmlspecialchars($contact_qq);
    
    // 组合联系方式占位符
    $contact_parts = [];
    if (!empty($contact_phone)) $contact_parts[] = '电话：' . htmlspecialchars($contact_phone);
    if (!empty($contact_email)) $contact_parts[] = '邮箱：' . htmlspecialchars($contact_email);
    if (!empty($contact_address)) $contact_parts[] = '地址：' . htmlspecialchars($contact_address);
    if (!empty($contact_wechat)) $contact_parts[] = '微信：' . htmlspecialchars($contact_wechat);
    if (!empty($contact_qq)) $contact_parts[] = 'QQ：' . htmlspecialchars($contact_qq);
    $data['{CONTACT_INFO}'] = implode(' | ', $contact_parts);
    
    // 广告位（如果存在占位符则替换，不存在则忽略）
    $top_ads = get_ads('top', $site['id']);
    $data['ad_top'] = !empty($top_ads) ? render_ad_html($top_ads) : '';
    $data['{AD_TOP}'] = $data['ad_top']; // 兼容旧模板占位符
    
    $footer_ads = get_ads('footer', $site['id']);
    $data['ad_footer'] = !empty($footer_ads) ? render_ad_html($footer_ads) : '';
    $data['{AD_FOOTER}'] = $data['ad_footer']; // 兼容旧模板占位符
    
    $side_ads = get_ads('side', $site['id']);
    $data['ad_side'] = !empty($side_ads) ? render_ad_html($side_ads) : '';
    $data['{AD_SIDE}'] = $data['ad_side']; // 兼容旧模板占位符
    $data['{AD_SIDEBAR}'] = $data['ad_side']; // 兼容别名
    $data['ad_content'] = ''; // 内容广告（首页默认为空）
    
    // 内链占位符（暂时留空，可由AI生成或后续填充）
    $data['{INNER_LINKS}'] = '';
    
    // 微信二维码占位符
    $data['{WECHAT_QRCODE}'] = '';
    
    // 地图嵌入占位符
    $data['{MAP_EMBED}'] = '';
    
    // 底部版权
    $footer_parts = [];
    $footer_parts[] = '&copy; ' . date('Y') . ' ' . htmlspecialchars($site['site_name'] ?? '');
    $footer_code = get_setting('footer_code', $site['id']);
    if (!empty($footer_code)) $footer_parts[] = $footer_code;
    $data['{FOOTER_COPYRIGHT}'] = implode(' | ', $footer_parts);
    
    // 备案号
    $data['{ICP_NUMBER}'] = build_icp_html(get_setting('icp_number', $site['id']) ?? '');
    
    // 统计代码
    $data['{ANALYTICS_CODE}'] = get_setting('analytics_code', $site['id']) ?? '';

    // ===== 管线首页（template_type=single 的管线站点）补齐多页面占位符 =====
    // AI 单页面本身通常不写这些占位符，但管线首页 prompt 允许使用 {NAV_LINKS}/{SEO_CONTENT}/
    // {ARTICLE_LIST}/{PAGE_TITLE} 等，与多页面一致。这里统一补全，缺省值不影响纯单页面。
    $pid = intval($site['id']);

    // TDK
    $page_title = $site['title'] ?: ($site['site_name'] ?? '');
    $data['{PAGE_TITLE}'] = htmlspecialchars($page_title);
    $data['{META_TITLE}'] = htmlspecialchars($page_title);
    $data['{META_DESCRIPTION}'] = htmlspecialchars($site['description'] ?? '');
    $data['{META_KEYWORDS}'] = htmlspecialchars($site['keywords'] ?? '');

    // 导航链接（优先 nav_items，回退 site_pages）
    $nav_links = '';
    if (!empty($site_template['nav_items'])) {
        $nav_items = json_decode($site_template['nav_items'], true);
        if (is_array($nav_items)) {
            $parts = [];
            foreach ($nav_items as $ni) {
                if (!empty($ni['url']) && !empty($ni['name'])) {
                    $parts[] = '<a href="' . htmlspecialchars($ni['url']) . '">' . htmlspecialchars($ni['name']) . '</a>';
                }
            }
            $nav_links = implode("\n", $parts);
        }
    }
    if ($nav_links === '') {
        $nav_links = build_pipeline_nav_links_html($pid);
    }
    // 【关键】导航链接必须包在 <li> 中，否则放在 AI 生成的 <ul> 里是非法 HTML，
    // 且 AI 的 CSS 选择器 `.nav-links li a` 可能匹配不到导致导航样式错乱。
    $nav_links = wrap_nav_links_in_li($nav_links);
    $data['{NAV_LINKS}'] = $nav_links;

    // 首页 SEO 长文案（Step1 产物，存 settings.home_seo_content）
    if (($site['site_type'] ?? '') === 'city') {
        $home_article = db_get_one("SELECT content FROM " . table('articles') . " WHERE site_id = {$pid} AND article_type='home' ORDER BY id DESC LIMIT 1");
        $seo_content = $home_article ? strip_full_html_doc($home_article['content']) : '';
    } else {
        $seo_content = strip_full_html_doc(get_setting('home_seo_content', $site['id']));
    }
    $data['{SEO_CONTENT}'] = $seo_content;

    // 最新文章列表
    $article_list = '';
    try {
        $arts = get_articles($pid, 20, 0);
        if (!empty($arts)) {
            $items = '';
            foreach ($arts as $a) {
                $time_str = !empty($a['created_at']) ? date('Y-m-d H:i', strtotime($a['created_at'])) : '';
                $items .= '<li><a href="' . site_url('article', ['id' => $a['id']]) . '">' . htmlspecialchars($a['title']) . '</a><div class="date">' . $time_str . '</div></li>';
            }
            $article_list = '<ul class="article-list">' . $items . '</ul>';
        }
    } catch (Exception $e) { /* ignore */ }
    $data['{ARTICLE_LIST}'] = $article_list;

    // Footer 占位符（管线拆出的 footer 组件；纯单页面可能没有，则留空）
    $data['{FOOTER}'] = !empty($site_template['content_footer']) ? $site_template['content_footer'] : '';
    $data['{LAST_UPDATE}'] = date('Y-m-d H:i');
    $data['{SITEMAP_LINK}'] = get_setting('sitemap_file', $site['id']) ?: '';

    // ===== 管线/单页面首页 SEO/GEO 完整数据补齐 =====
    // 与 build_home_template_data() 对齐：Schema.org、OG/Twitter、验证标签、canonical、
    // 自定义CSS等必须随首页一起注入。之前只补了 TDK，inject_single_page() 注入的基础
    // WebSite schema 又会被 inject_tdk_into_html() 清掉，导致管线首页丢 Schema/OG，
    // 对 SEO/GEO 不友好。这里统一补齐，Path A 与 Path B 完全对齐。

    // 城市分站 TDK 覆盖（与 build_home_template_data 一致）
    $city_name = '';
    if (!empty($GLOBALS['is_city_channel']) && !empty($GLOBALS['city_channel'])) {
        $city_name = $GLOBALS['city_channel']['city_name'] ?? '';
    }
    $page_title_val = $site['title'] ?: ($site['site_name'] ?? '');
    $meta_desc_val  = $site['description'] ?? '';
    $meta_kw_val    = $site['keywords'] ?? '';
    if (!empty($city_name)) {
        if (mb_strpos($page_title_val, $city_name) === false) {
            $page_title_val = $city_name . $page_title_val;
        }
        if (mb_strpos($meta_desc_val, $city_name) === false) {
            $meta_desc_val = '【' . $city_name . '】' . $meta_desc_val;
        }
        if (mb_strpos($meta_kw_val, $city_name) === false) {
            $meta_kw_val = $city_name . ', ' . $meta_kw_val;
        }
    }
    // 用带城市名的最终值覆盖前面写入的占位符
    $data['{PAGE_TITLE}'] = htmlspecialchars($page_title_val);
    $data['{META_TITLE}'] = htmlspecialchars($page_title_val);
    $data['{META_DESCRIPTION}'] = htmlspecialchars($meta_desc_val);
    $data['{META_KEYWORDS}'] = htmlspecialchars($meta_kw_val);

    $site_base_url = build_site_base_url($site);

    // OG / Twitter Card
    $og_image = '';
    if (!empty($seo_content) && preg_match('/<img[^>]+src=["\']([^"\']+)["\']/i', $seo_content, $img_match)) {
        $og_image = $img_match[1];
    }
    $og_tags  = '<meta property="og:type" content="website">' . "\n";
    $og_tags .= '<meta property="og:title" content="' . htmlspecialchars($page_title_val) . '">' . "\n";
    $og_tags .= '<meta property="og:description" content="' . htmlspecialchars($meta_desc_val) . '">' . "\n";
    $og_tags .= '<meta property="og:url" content="' . htmlspecialchars($site_base_url) . '">' . "\n";
    $og_tags .= '<meta property="og:site_name" content="' . htmlspecialchars($site['site_name'] ?? '') . '">' . "\n";
    $og_tags .= '<meta property="og:locale" content="zh_CN">' . "\n";
    $og_tags .= '<meta name="twitter:card" content="summary">' . "\n";
    $og_tags .= '<meta name="twitter:title" content="' . htmlspecialchars($page_title_val) . '">' . "\n";
    $og_tags .= '<meta name="twitter:description" content="' . htmlspecialchars($meta_desc_val) . '">';
    if ($og_image) {
        $og_tags .= "\n" . '<meta property="og:image" content="' . htmlspecialchars($og_image) . '">' . "\n"
            . '<meta name="twitter:image" content="' . htmlspecialchars($og_image) . '">';
    }
    $data['og_tags'] = $og_tags;

    // 搜索引擎验证标签（与 build_home_template_data 一致，独立 key 供 inject_tdk_into_html 注入）
    $baidu_verify  = get_setting('baidu_verify_tag', $pid) ?? '';
    $bing_verify   = get_setting('bing_verify_tag', $pid) ?? '';
    $google_verify = get_setting('google_verify_tag', $pid) ?? '';
    $sogou_verify  = get_setting('sogou_verify_tag', $pid) ?? '';
    $verify_tags   = '';
    if (!empty($baidu_verify))  $verify_tags .= (stripos($baidu_verify, '<meta') !== false ? $baidu_verify : '<meta name="baidu-site-verification" content="' . htmlspecialchars($baidu_verify) . '" />') . "\n";
    if (!empty($bing_verify))   $verify_tags .= (stripos($bing_verify, '<meta') !== false ? $bing_verify : '<meta name="msvalidate.01" content="' . htmlspecialchars($bing_verify) . '" />') . "\n";
    if (!empty($google_verify)) $verify_tags .= (stripos($google_verify, '<meta') !== false ? $google_verify : '<meta name="google-site-verification" content="' . htmlspecialchars($google_verify) . '" />') . "\n";
    if (!empty($sogou_verify))  $verify_tags .= (stripos($sogou_verify, '<meta') !== false ? $sogou_verify : '<meta name="sogou-site-verification" content="' . htmlspecialchars($sogou_verify) . '" />') . "\n";
    $data['verify_tags'] = $verify_tags;

    // Schema.org：WebSite（含 SearchAction）+ BreadcrumbList（与 Path B 对齐）
    $schema_array = [
        [
            '@context' => 'https://schema.org',
            '@type' => 'WebSite',
            'name' => $site['site_name'] ?? '',
            'url' => $site_base_url . '/',
            'description' => $site['description'] ?? '',
            'potentialAction' => [
                '@type' => 'SearchAction',
                'target' => $site_base_url . '/article/{search_term_string}.html',
                'query-input' => 'required name=search_term_string'
            ]
        ],
        [
            '@context' => 'https://schema.org',
            '@type' => 'BreadcrumbList',
            'itemListElement' => [
                ['@type' => 'ListItem', 'position' => 1, 'name' => '首页', 'item' => $site_base_url . '/'],
                ['@type' => 'ListItem', 'position' => 2, 'name' => $site['site_name'] ?? '']
            ]
        ]
    ];
    // 城市分站：追加 LocalBusiness schema
    if (!empty($city_name) && !empty($GLOBALS['city_channel'])) {
        $city_channel = $GLOBALS['city_channel'];
        $lb = [
            '@context' => 'https://schema.org',
            '@type' => 'LocalBusiness',
            'name' => ($site['site_name'] ?? '') . ' - ' . $city_name . '分站',
            'description' => $site['description'] ?? '',
            'url' => $site_base_url . '/',
            'areaServed' => ['@type' => 'City', 'name' => $city_name],
            'address' => [
                '@type' => 'PostalAddress',
                'addressLocality' => $city_name,
                'addressRegion' => $city_channel['province'] ?? '',
                'addressCountry' => 'CN'
            ]
        ];
        if (!empty($contact_phone))   $lb['telephone'] = $contact_phone;
        if (!empty($contact_email))   $lb['email'] = $contact_email;
        if (!empty($contact_address)) $lb['address']['streetAddress'] = $contact_address;
        $schema_array[] = $lb;
    }
    $schema_json = '';
    foreach ($schema_array as $schema_item) {
        $schema_json .= '<script type="application/ld+json">' . "\n"
            . json_encode($schema_item, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n"
            . '</script>' . "\n";
    }
    $data['schema_json'] = trim($schema_json);

    // canonical / RSS / GEO summary
    $data['canonical_url'] = $site_base_url . '/';
    $data['site_domain']   = $site['domain'] ?? '';
    $data['site_id']       = $pid;

    // 文本颜色（assemble_component_page 用于对比度兜底；Path A 由系统CSS统一，不强制覆盖）
    $data['text_color'] = get_setting('text_color', $pid) ?? '';
    $data['bg_color']   = '#ffffff';

    // 自定义 CSS：Path A 已经把 content_1 通过 $raw_css 单独走 apply_placeholders 注入，
    // 这里 custom_css 保持空字符串，避免 inject_tdk_into_html 再包一层 <style> 造成重复。
    $data['custom_css'] = '';

    // GEO 摘要 meta
    $data['article_summary'] = !empty($site['description']) ? htmlspecialchars(mb_substr(strip_tags($site['description']), 0, 120)) : '';

    return $data;
}

/**
 * 渲染单页面模板（精简版）
 * 只做基本的占位符替换，不做复杂的DOM清理
 */
function render_single_page_template($html, $data, $raw_css = '') {
    debug_log('[v3.3 DEBUG] render_single_page_template: called, data_keys=' . count($data) . ', raw_css_len=' . strlen($raw_css));
    // v3.2: 剥离 <style> 标签，与 render_site_template / assemble_component_page 保持一致
    $raw_css = preg_replace('/^\s*<style[^>]*>\s*/i', '', $raw_css);
    $raw_css = preg_replace('/\s*<\/style>\s*$/i', '', $raw_css);
    // v3.0: 使用统一占位符替换层，大小写不敏感
    // $data 的 key 格式是 {SITE_NAME} 等（带花括号），直接传给 apply_placeholders
    $html = apply_placeholders($html, $data, $raw_css);
    return $html;
}

/**
 * 构建首页模板数据
 */
/**
 * 构建备案号HTML（带超链接到工信部备案查询平台）
 */
function build_icp_html($icp_text) {
    if (empty($icp_text)) return '';
    $safe = htmlspecialchars($icp_text, ENT_QUOTES, 'UTF-8');
    return '<a href="https://beian.miit.gov.cn/" target="_blank" rel="nofollow noopener" style="color:inherit;text-decoration:none;">' . $safe . '</a>';
}

/**
 * 移动端底部吸底联系条（电话拨号 + 微信复制）
 * 复用联系方式设置里的 contact_phone / contact_wechat，不新增字段、不新增后台开关。
 * PC 端不显示；两个都为空时返回空字符串，零影响。
 */
function render_bottom_contact_bar($site) {
    if (!function_exists('get_setting')) return '';
    $site_id = intval($site['id'] ?? 0);
    $phone = trim((string)get_setting('contact_phone', $site_id));
    $wechat = trim((string)get_setting('contact_wechat', $site_id));
    if ($phone === '' && $wechat === '') return '';

    $phone_safe = htmlspecialchars($phone, ENT_QUOTES, 'UTF-8');
    $wechat_safe = htmlspecialchars($wechat, ENT_QUOTES, 'UTF-8');
    $phone_href = $phone !== '' ? 'tel:' . preg_replace('/[^\d\+\-\#\*]/', '', $phone) : '';

    // 两栏是否都有
    $has_phone = $phone !== '';
    $has_wechat = $wechat !== '';

    $css = <<<CSS
<style>
.sys-contact-bar{position:fixed;left:0;right:0;bottom:0;height:56px;z-index:9995;display:none;background:#fff;border-top:1px solid #eee;box-shadow:0 -2px 12px rgba(0,0,0,.08);}
.sys-contact-bar a,.sys-contact-bar button{flex:1;display:flex;align-items:center;justify-content:center;gap:6px;text-decoration:none;font-size:16px;border:none;background:#fff;cursor:pointer;padding:0;}
.sys-contact-bar .bar-phone{color:#1890ff;}
.sys-contact-bar .bar-wechat{color:#07c160;border-left:1px solid #f0f0f0;}
.sys-contact-bar svg{width:22px;height:22px;}
.sys-contact-sheet-mask{position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:9997;display:none;align-items:flex-end;}
.sys-contact-sheet-mask.show{display:flex;}
.sys-contact-sheet{background:#fff;width:100%;border-radius:14px 14px 0 0;padding:20px 18px 28px;}
.sys-contact-sheet .sheet-title{font-size:15px;color:#666;margin-bottom:8px;}
.sys-contact-sheet .sheet-id{font-size:20px;font-weight:700;letter-spacing:1px;margin-bottom:16px;word-break:break-all;}
.sys-contact-sheet .sheet-copy{width:100%;background:#07c160;color:#fff;border:none;border-radius:10px;padding:12px 0;font-size:16px;cursor:pointer;}
.sys-contact-sheet .sheet-cancel{width:100%;background:#f5f5f5;color:#666;border:none;border-radius:10px;padding:12px 0;font-size:16px;margin-top:10px;cursor:pointer;}
.sys-contact-toast{position:fixed;left:50%;top:50%;transform:translate(-50%,-50%);background:rgba(0,0,0,.8);color:#fff;padding:10px 18px;border-radius:8px;font-size:14px;z-index:9998;display:none;}
.sys-contact-toast.show{display:block;}
@media (max-width:767px){
 .sys-contact-bar{display:flex !important;}
 body{padding-bottom:60px !important;}
}
</style>
CSS;

    // 电话图标 SVG
    $icon_phone = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.13.96.37 1.9.72 2.81a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.91.35 1.85.59 2.81.72A2 2 0 0 1 22 16.92z"/></svg>';
    // 微信/聊天气泡图标
    $icon_chat = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>';

    $html = $css;

    // 移动端底部条
    $bar_count = intval($has_phone) + intval($has_wechat);
    if ($bar_count > 0) {
        $html .= '<div class="sys-contact-bar">';
        if ($has_phone) {
            $html .= '<a class="bar-phone" href="' . $phone_href . '" rel="nofollow">' . $icon_phone . '<span>电话咨询</span></a>';
        }
        if ($has_wechat) {
            $html .= '<button type="button" class="bar-wechat" data-sys-wechat="' . $wechat_safe . '" onclick="sysContactOpenSheet(\'' . $wechat_safe . '\')">' . $icon_chat . '<span>微信咨询</span></button>';
        }
        $html .= '</div>';
    }

    // 移动端微信弹窗 sheet
    if ($has_wechat) {
        $html .= '<div class="sys-contact-sheet-mask" id="sysContactSheetMask" onclick="sysContactCloseSheet(event)">';
        $html .= '<div class="sys-contact-sheet" onclick="event.stopPropagation()">';
        $html .= '<div class="sheet-title">微信号（点击复制）</div>';
        $html .= '<div class="sheet-id" id="sysContactSheetId">' . $wechat_safe . '</div>';
        $html .= '<button type="button" class="sheet-copy" onclick="sysContactCopy(\'' . $wechat_safe . '\')">复制微信号</button>';
        $html .= '<button type="button" class="sheet-cancel" onclick="sysContactCloseSheet()">取消</button>';
        $html .= '</div></div>';
    }

    // 复制成功 toast
    $html .= '<div class="sys-contact-toast" id="sysContactToast">微信号已复制，打开微信粘贴添加</div>';

    // JS：复制、弹卡片、sheet
    $html .= <<<JS
<script>
(function(){
 function fallbackCopy(text){
   var ta=document.createElement('textarea');
   ta.value=text; ta.style.position='fixed'; ta.style.top='-9999px'; ta.style.opacity='0';
   document.body.appendChild(ta); ta.focus(); ta.select();
   var ok=false; try{ok=document.execCommand('copy');}catch(e){ok=false;}
   document.body.removeChild(ta); return ok;
 }
 window.sysContactCopy=function(text){
   var done=function(){
     var t=document.getElementById('sysContactToast'); if(!t) return;
     t.classList.add('show'); setTimeout(function(){t.classList.remove('show');},1800);
   };
   if(navigator.clipboard && navigator.clipboard.writeText){
     navigator.clipboard.writeText(text).then(done).catch(function(){ if(fallbackCopy(text)) done(); });
   } else { if(fallbackCopy(text)) done(); }
 };
 window.sysContactOpenSheet=function(text){
   var mask=document.getElementById('sysContactSheetMask'); if(!mask) return;
   var idEl=document.getElementById('sysContactSheetId'); if(idEl) idEl.textContent=text;
   mask.classList.add('show');
 };
 window.sysContactCloseSheet=function(e){
   if(e && e.target && !e.target.classList.contains('sys-contact-sheet-mask')) return;
   var mask=document.getElementById('sysContactSheetMask'); if(mask) mask.classList.remove('show');
 };
})();
</script>
JS;
    return $html;
}

/**
 * 获取当前城市频道的 city_id（用于文章过滤）
 * 仅当 city_article_mode = 'independent' 时返回有效 city_id，否则返回 0（不过滤）
 */
function get_current_city_article_filter($site) {
    if (empty($GLOBALS['is_city_channel']) || empty($GLOBALS['city_info'])) {
        return 0;
    }
    if (($site['city_article_mode'] ?? 'shared') === 'independent') {
        return intval($GLOBALS['city_info']['city_id'] ?? 0);
    }
    return 0;
}

function build_home_template_data($site, $site_template = null) {
    // v3.0: 检测管线版本，新版本不再fallback到旧字段
    $pipeline_version = $site['pipeline_version'] ?? '';
    $is_v3 = version_compare($pipeline_version, '3.0', '>=');
    
    // 城市分站TDK覆盖
    $city_tdk_override = [];
    if (!empty($GLOBALS['is_city_channel']) && !empty($GLOBALS['city_channel'])) {
        $city_channel = $GLOBALS['city_channel'];
        // Bug11修复：对 TDK 值进行城市变量替换，防止模板变量原样输出
        if (!empty($city_channel['tdk_title'])) {
            $city_tdk_override['title'] = replace_city_variables($city_channel['tdk_title'], $city_channel);
        }
        if (!empty($city_channel['tdk_keywords'])) {
            $city_tdk_override['keywords'] = replace_city_variables($city_channel['tdk_keywords'], $city_channel);
        }
        if (!empty($city_channel['tdk_description'])) {
            $city_tdk_override['description'] = replace_city_variables($city_channel['tdk_description'], $city_channel);
        }
    }
    
    $city_article_filter = get_current_city_article_filter($site);
    $articles = get_articles($site['id'], 20, 0, $city_article_filter);
    $top_ads = get_ads('top', $site['id']);
    $side_ads = get_ads('side', $site['id']);
    $footer_ads = get_ads('footer', $site['id']);
    
    $contact_phone = get_setting('contact_phone', $site['id']);
    $contact_email = get_setting('contact_email', $site['id']);
    $contact_address = get_setting('contact_address', $site['id']);
    $contact_wechat = get_setting('contact_wechat', $site['id']);
    $footer_code = get_setting('footer_code', $site['id']);
    $text_color = get_setting('text_color', $site['id']);
    // v20修复：城市分站从 articles 表读取 article_type='home' 的SEO文章作为首页内容
    // 而非从 settings 表读取（settings 表中的 home_seo_content 是旧逻辑）
    if (($site['site_type'] ?? '') === 'city') {
        $city_id_for_seo = $GLOBALS['city_channel']['city_id'] ?? ($site['city_id'] ?? 0);
        $home_article = db_get_one("SELECT content FROM " . table('articles') . " 
            WHERE site_id = {$site['id']} AND city_id = " . intval($city_id_for_seo) . " 
            AND article_type = 'home' 
            ORDER BY id DESC LIMIT 1");
        $seo_content = $home_article ? strip_full_html_doc($home_article['content']) : '';
    } else {
        $seo_content = strip_full_html_doc(get_setting('home_seo_content', $site['id']));
    }
    $sitemap_file = get_setting('sitemap_file', $site['id']);
    $fixed_images = [];  // 图库功能已移除，保留空数组兼容
    $latest_article = db_get_one("SELECT created_at FROM " . table('articles') . " WHERE site_id = {$site['id']} ORDER BY id DESC LIMIT 1");
    
    // SEO标签
    $baidu_verify = get_setting('baidu_verify_tag', $site['id']);
    $bing_verify = get_setting('bing_verify_tag', $site['id']);
    $google_verify = get_setting('google_verify_tag', $site['id']);
    $sogou_verify = get_setting('sogou_verify_tag', $site['id']);
    $verify_tags = '';
    // 验证标签：存储的可能是完整<meta>标签或纯验证码，完整标签不转义
    if (!empty($baidu_verify)) $verify_tags .= (stripos($baidu_verify, '<meta') !== false ? $baidu_verify : '<meta name="baidu-site-verification" content="' . htmlspecialchars($baidu_verify) . '" />') . "\n";
    if (!empty($bing_verify)) $verify_tags .= (stripos($bing_verify, '<meta') !== false ? $bing_verify : '<meta name="msvalidate.01" content="' . htmlspecialchars($bing_verify) . '" />') . "\n";
    if (!empty($google_verify)) $verify_tags .= (stripos($google_verify, '<meta') !== false ? $google_verify : '<meta name="google-site-verification" content="' . htmlspecialchars($google_verify) . '" />') . "\n";
    if (!empty($sogou_verify)) $verify_tags .= (stripos($sogou_verify, '<meta') !== false ? $sogou_verify : '<meta name="sogou-site-verification" content="' . htmlspecialchars($sogou_verify) . '" />') . "\n";
    
    // Schema.org - 多类型结构化数据
    $schema_array = [
        [
            '@context' => 'https://schema.org',
            '@type' => 'WebSite',
            'name' => $site['site_name'],
            'url' => build_site_base_url($site) . '/',
            'description' => $site['description'],
            'potentialAction' => [
                '@type' => 'SearchAction',
                'target' => build_site_base_url($site) . '/article/{search_term_string}.html',
                'query-input' => 'required name=search_term_string'
            ]
        ]
    ];

    // 城市分站：添加 LocalBusiness schema
    if (!empty($city_info)) {
        $lb = [
            '@context' => 'https://schema.org',
            '@type' => 'LocalBusiness',
            'name' => $site['site_name'] . ' - ' . $city_info['name'] . '分站',
            'description' => $site['description'],
            'url' => build_site_base_url($site) . '/',
            'areaServed' => [
                '@type' => 'City',
                'name' => $city_info['name']
            ],
            'address' => [
                '@type' => 'PostalAddress',
                'addressLocality' => $city_info['name'],
                'addressRegion' => $city_info['province'] ?? '',
                'addressCountry' => 'CN'
            ]
        ];
        // 联系方式（修复：此前误用未定义变量 $contact_info，导致城市分站首页电话/邮箱/地址写不进 schema）
        // 站点级优先，空则回退全局设置
        $lb_phone = !empty($contact_phone) ? $contact_phone : get_setting('contact_phone', 0);
        $lb_email = !empty($contact_email) ? $contact_email : get_setting('contact_email', 0);
        $lb_addr  = !empty($contact_address) ? $contact_address : get_setting('contact_address', 0);
        if (!empty($lb_phone)) {
            $lb['telephone'] = $lb_phone;
        }
        if (!empty($lb_email)) {
            $lb['email'] = $lb_email;
        }
        if (!empty($lb_addr)) {
            $lb['address']['streetAddress'] = $lb_addr;
        }
        $schema_array[] = $lb;
    }

    // BreadcrumbList schema
    $schema_array[] = [
        '@context' => 'https://schema.org',
        '@type' => 'BreadcrumbList',
        'itemListElement' => [
            ['@type' => 'ListItem', 'position' => 1, 'name' => '首页', 'item' => build_site_base_url($site) . '/'],
            !empty($city_info) ? ['@type' => 'ListItem', 'position' => 2, 'name' => $city_info['name'] . '分站', 'item' => build_site_base_url($site) . '/'] : ['@type' => 'ListItem', 'position' => 2, 'name' => $site['site_name']]
        ]
    ];

    $schema_json = '';
    foreach ($schema_array as $schema_item) {
        $schema_json .= '<script type="application/ld+json">' . "\n" . json_encode($schema_item, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n" . '</script>' . "\n";
    }
    $schema_json = trim($schema_json);
    
    // 导航
    $nav_links = build_pipeline_nav_links_html($site['id']);
    $nav_links = wrap_nav_links_in_li($nav_links);
    
    // 广告HTML
    $ad_top = '';
    foreach ($top_ads as $ad) {
        $ad_top .= '<div class="ad-box"><a href="' . htmlspecialchars($ad['link_url']) . '" target="_blank"><img src="' . htmlspecialchars($ad['image_url']) . '" alt="' . htmlspecialchars($ad['title'] ?? $site['site_name'] . ' - 广告') . '"></a></div>';
    }
    
    $ad_side = '';
    foreach ($side_ads as $ad) {
        $ad_side .= '<div class="ad-box"><a href="' . htmlspecialchars($ad['link_url']) . '" target="_blank"><img src="' . htmlspecialchars($ad['image_url']) . '" alt="' . htmlspecialchars($ad['title'] ?? $site['site_name'] . ' - 广告') . '"></a></div>';
    }
    
    $ad_footer = '';
    foreach ($footer_ads as $ad) {
        $ad_footer .= '<div class="ad-box"><a href="' . htmlspecialchars($ad['link_url']) . '" target="_blank"><img src="' . htmlspecialchars($ad['image_url']) . '" alt="' . htmlspecialchars($ad['title'] ?? $site['site_name'] . ' - 广告') . '"></a></div>';
    }
    
    // 图片画廊（已移除，保留空值兼容旧模板）
    $image_gallery = '';
    
    // 文章列表（时间格式：Y-m-d H:i，外层包裹ul确保HTML结构正确）
    $article_list = '';
    if (!empty($articles)) {
        foreach ($articles as $article) {
            $article_list .= '<li>';
            $article_list .= '<a href="' . site_url('article', ['id' => $article['id']]) . '">' . htmlspecialchars($article['title']) . '</a>';
            $time_str = !empty($article['created_at']) ? date('Y-m-d H:i', strtotime($article['created_at'])) : '';
            $article_list .= '<div class="date">' . $time_str . '</div>';
            $article_list .= '</li>';
        }
    } else {
        $article_list = '<li><p style="color:#999;">暂无文章</p></li>';
    }
    // 外层包裹ul，确保HTML结构正确（模板中无需再包裹）
    $article_list = '<ul class="article-list">' . $article_list . '</ul>';
    
    // 联系方式
    $contact_info = '';
    if ($contact_phone) $contact_info .= '<p>电话：' . htmlspecialchars($contact_phone) . '</p>';
    if ($contact_email) $contact_info .= '<p>邮箱：' . htmlspecialchars($contact_email) . '</p>';
    if ($contact_address) $contact_info .= '<p>地址：' . htmlspecialchars($contact_address) . '</p>';
    if ($contact_wechat) $contact_info .= '<p>微信：' . htmlspecialchars($contact_wechat) . '</p>';
    
    // 版权信息 —— 系统固定控制，站点地图由{SITEMAP_LINK}占位符单独处理
    $footer_parts = [$site['site_name'] . ' &copy; ' . date('Y')];
    if (!empty($footer_code)) $footer_parts[] = $footer_code;
    $footer_copyright = implode(' | ', $footer_parts);
    
    // 修复NEW-12: 正确提取CSS——只提取<style>标签内的内容，不把整个HTML当CSS
    $custom_css = '';
    if ($site_template) {
        $css_parts = [];
        // 优先使用content_1（CSS设计系统），其次从content_home/content_4提取<style>内容
        if (!empty($site_template['content_1'])) {
            // 清理 content_1 中可能残留的 <style> 标签（AI 有时会包裹，避免双 <style> 问题）
            $raw_css = $site_template['content_1'];
            // v4.1防御：如果content_1中存储的是原始JSON，自动提取css字段
            $trimmed_css = ltrim($raw_css);
            if (strlen($trimmed_css) > 0 && $trimmed_css[0] === '{' && stripos($trimmed_css, '"css"') !== false) {
                $extracted_css = null;
                if (function_exists('extract_json_from_ai')) {
                    $parsed_css = extract_json_from_ai($raw_css);
                    if ($parsed_css && isset($parsed_css['css'])) {
                        $extracted_css = $parsed_css['css'];
                    }
                }
                if (empty($extracted_css) && function_exists('extract_field_from_raw_json')) {
                    $extracted_css = extract_field_from_raw_json($raw_css, 'css');
                }
                if (!empty($extracted_css)) {
                    $raw_css = $extracted_css;
                    error_log('[v4.1] content_1包含原始JSON，已自动提取css字段');
                }
            }
            $raw_css = preg_replace('/^\s*<style[^>]*>\s*/i', '', $raw_css);
            $raw_css = preg_replace('/\s*<\/style>\s*$/i', '', $raw_css);
            $css_parts[] = trim($raw_css);
        }
        
        // 从content_home和content_article中提取<style>标签内容
        // 智能合并策略：如果HTML中包含<style>标签，提取其CSS并与content_1合并，然后统一注入
        $html_sources = [];
        if (!empty($site_template['content_home'])) {
            // 始终提取content_home中的<style>内容，实现CSS合并
            if (stripos($site_template['content_home'], '<style') !== false) {
                // 提取<style>标签中的CSS
                if (preg_match_all('/<style[^>]*>(.*?)<\/style>/is', $site_template['content_home'], $matches)) {
                    foreach ($matches[1] as $css_block) {
                        $css_content = trim($css_block);
                        // 跳过只包含占位符的style块
                        if ($css_content !== '{CUSTOM_CSS}' && !empty($css_content)) {
                            $css_parts[] = $css_content;
                        }
                    }
                }
            } else {
                // 兜底场景：content_home没有<style>标签，尝试从中提取CSS
                $html_sources[] = $site_template['content_home'];
            }
        }
        if (!empty($site_template['content_article'])) {
            // 同样处理content_article
            if (stripos($site_template['content_article'], '<style') !== false) {
                if (preg_match_all('/<style[^>]*>(.*?)<\/style>/is', $site_template['content_article'], $matches)) {
                    foreach ($matches[1] as $css_block) {
                        $css_content = trim($css_block);
                        if ($css_content !== '{CUSTOM_CSS}' && !empty($css_content)) {
                            $css_parts[] = $css_content;
                        }
                    }
                }
            } else {
                $html_sources[] = $site_template['content_article'];
            }
        }
        if (empty($html_sources) && !$is_v3 && !empty($site_template['content_4'])) {
            if (stripos($site_template['content_4'], '<style') !== false) {
                if (preg_match_all('/<style[^>]*>(.*?)<\/style>/is', $site_template['content_4'], $matches)) {
                    foreach ($matches[1] as $css_block) {
                        $css_content = trim($css_block);
                        if ($css_content !== '{CUSTOM_CSS}' && !empty($css_content)) {
                            $css_parts[] = $css_content;
                        }
                    }
                }
            } else {
                $html_sources[] = $site_template['content_4'];
            }
        }
        
        foreach ($html_sources as $src) {
            // 只提取 <style>...</style> 之间的CSS内容
            if (preg_match_all('/<style[^>]*>(.*?)<\/style>/is', $src, $matches)) {
                foreach ($matches[1] as $css_block) {
                    $css_parts[] = trim($css_block);
                }
            }
        }
        
        if (!empty($css_parts)) {
            $custom_css = '<style>' . implode("\n", $css_parts) . '</style>';
        }
    }
    
    // 城市分站TDK覆盖 - 先计算最终TDK值，后续OG标签和返回数据都使用此值
    $city_name = '';
    if (!empty($GLOBALS['is_city_channel']) && !empty($GLOBALS['city_channel'])) {
        $city_name = $GLOBALS['city_channel']['city_name'] ?? '';
    }
    $page_title_val = !empty($city_tdk_override['title']) ? $city_tdk_override['title'] : ($site['title'] ?: $site['site_name']);
    $meta_desc_val = !empty($city_tdk_override['description']) ? $city_tdk_override['description'] : $site['description'];
    $meta_kw_val = !empty($city_tdk_override['keywords']) ? $city_tdk_override['keywords'] : ($site['keywords'] ?? '');
    
    // SEO: 城市分站TDK必须包含城市名，避免与主站重复
    if (!empty($city_name)) {
        if (empty($city_tdk_override['title']) && mb_strpos($page_title_val, $city_name) === false) {
            $page_title_val = $city_name . $page_title_val;
        }
        if (empty($city_tdk_override['description']) && mb_strpos($meta_desc_val, $city_name) === false) {
            $meta_desc_val = '【' . $city_name . '】' . $meta_desc_val;
        }
        if (empty($city_tdk_override['keywords']) && mb_strpos($meta_kw_val, $city_name) === false) {
            $meta_kw_val = $city_name . ', ' . $meta_kw_val;
        }
    }
    
    // OG标签（使用最终TDK值，而非原始$site值）
    $og_image = '';
    // 优先从 SEO 文章内容中提取图片
    if (!empty($seo_content) && preg_match('/<img[^>]+src=["\']([^"\']+)["\']/i', $seo_content, $img_match)) {
        $og_image = $img_match[1];
    }
    // 兜底：从模板 HTML 中提取第一张图片
    if (empty($og_image) && !empty($data['home_template'])) {
        if (preg_match('/<img[^>]+src=["\']([^"\']+)["\']/i', $data['home_template'], $tpl_img_match)) {
            $og_image = $tpl_img_match[1];
        }
    }
    $site_base_url = build_site_base_url($site);
    $og_tags = '<meta property="og:type" content="website">' . "\n"
        . '<meta property="og:title" content="' . htmlspecialchars($page_title_val) . '">' . "\n"
        . '<meta property="og:description" content="' . htmlspecialchars($meta_desc_val) . '">' . "\n"
        . '<meta property="og:url" content="' . htmlspecialchars($site_base_url) . '">' . "\n"
        . '<meta property="og:site_name" content="' . htmlspecialchars($site['site_name']) . '">' . "\n"
        . '<meta property="og:locale" content="zh_CN">' . "\n"
        . '<meta name="twitter:card" content="summary">' . "\n"
        . '<meta name="twitter:title" content="' . htmlspecialchars($page_title_val) . '">' . "\n"
        . '<meta name="twitter:description" content="' . htmlspecialchars($meta_desc_val) . '">';
    if ($og_image) {
        $og_tags .= "\n" . '<meta property="og:image" content="' . htmlspecialchars($og_image) . '">' . "\n"
            . '<meta name="twitter:image" content="' . htmlspecialchars($og_image) . '">';
    }

    return [
        'site_id' => intval($site['id']),
        'site_name' => htmlspecialchars($site['site_name']),
        'site_description' => htmlspecialchars($site['description']),
        'site_domain' => $site['domain'],
        'page_title' => htmlspecialchars($page_title_val),
        'meta_title' => htmlspecialchars($page_title_val),
        'meta_description' => htmlspecialchars($meta_desc_val),
        'meta_keywords' => htmlspecialchars($meta_kw_val),
        'canonical_url' => build_site_base_url($site) . '/',
        'og_tags' => $og_tags,
        'nav_links' => $nav_links,
        'ad_top' => $ad_top,
        'ad_side' => $ad_side,
        'ad_footer' => $ad_footer,
        'ad_content' => '',
        'article_list' => $article_list,
        'seo_content' => $seo_content,
        'image_gallery' => $image_gallery,
        'breadcrumb' => '',
        'article_title' => '',
        'article_date' => '',
        'article_content' => '',
        'article_update' => '',
        'related_articles' => '',
        'hot_articles' => '',
        'contact_info' => $contact_info,
        'footer' => $site_template['content_footer'] ?? '',
        'footer_copyright' => $footer_copyright,
        'last_update' => !empty($latest_article) ? date('Y-m-d H:i', strtotime($latest_article['created_at'])) : date('Y-m-d H:i'),
        'sitemap_link' => $sitemap_file ? $sitemap_file : '',
        'schema_json' => $schema_json,
        'verify_tags' => $verify_tags,
        'icp_number' => build_icp_html(get_setting('icp_number', $site['id']) ?? ''),
        'custom_css' => $custom_css,
        'text_color' => $text_color,
        'article_summary' => !empty($article['summary']) ? htmlspecialchars($article['summary']) : '',
    ];
}

// 生成热门文章列表的辅助函数（时间格式：Y-m-d H:i）
function build_hot_articles_list($site_id, $count = 5, $city_id = 0) {
    $hot_articles = get_articles($site_id, $count, 0, $city_id);
    $html = '';
    if (!empty($hot_articles)) {
        foreach ($hot_articles as $article) {
            $html .= '<li>';
            $html .= '<a href="' . site_url('article', ['id' => $article['id']]) . '">' . htmlspecialchars($article['title']) . '</a>';
            $time_str = !empty($article['created_at']) ? date('Y-m-d H:i', strtotime($article['created_at'])) : '';
            $html .= '<div class="date">' . $time_str . '</div>';
            $html .= '</li>';
        }
    }
    return $html;
}

/**
 * 构建文章详情页模板数据
 */
function build_article_template_data($site, $article, $site_template = null) {
    // v3.0: 检测管线版本，新版本不再fallback到旧字段
    $pipeline_version = $site['pipeline_version'] ?? '';
    $is_v3 = version_compare($pipeline_version, '3.0', '>=');
    
    $top_ads = get_ads('top', $site['id']);
    $content_ads = get_ads('content', $site['id']);
    $side_ads = get_ads('side', $site['id']);
    $footer_ads = get_ads('footer', $site['id']);
    
    $contact_phone = get_setting('contact_phone', $site['id']);
    $contact_email = get_setting('contact_email', $site['id']);
    $contact_address = get_setting('contact_address', $site['id']);
    $contact_wechat = get_setting('contact_wechat', $site['id']);
    $footer_code = get_setting('footer_code', $site['id']);
    $text_color = get_setting('text_color', $site['id']);
    $sitemap_file = get_setting('sitemap_file', $site['id']);
    
    $hot_articles = get_hot_articles($site['id'], 10);
    $related_articles = get_related_articles($site['id'], $article['id'], 5);
    
    // SEO标签（与首页/栏目页同款：存储的可能是完整<meta>或纯验证码）
    $baidu_verify = get_setting('baidu_verify_tag', $site['id']);
    $bing_verify = get_setting('bing_verify_tag', $site['id']);
    $google_verify = get_setting('google_verify_tag', $site['id']);
    $sogou_verify = get_setting('sogou_verify_tag', $site['id']);
    $verify_tags = '';
    if (!empty($baidu_verify))  $verify_tags .= (stripos($baidu_verify, '<meta') !== false ? $baidu_verify : '<meta name="baidu-site-verification" content="' . htmlspecialchars($baidu_verify) . '" />') . "\n";
    if (!empty($bing_verify))   $verify_tags .= (stripos($bing_verify, '<meta') !== false ? $bing_verify : '<meta name="msvalidate.01" content="' . htmlspecialchars($bing_verify) . '" />') . "\n";
    if (!empty($google_verify)) $verify_tags .= (stripos($google_verify, '<meta') !== false ? $google_verify : '<meta name="google-site-verification" content="' . htmlspecialchars($google_verify) . '" />') . "\n";
    if (!empty($sogou_verify))  $verify_tags .= (stripos($sogou_verify, '<meta') !== false ? $sogou_verify : '<meta name="sogou-site-verification" content="' . htmlspecialchars($sogou_verify) . '" />') . "\n";
    
    // Schema.org - Article + BreadcrumbList
    // 提取文章中的第一张图片
    $article_image = '';
    if (!empty($article['content']) && preg_match('/<img[^>]+src=["\']([^"\']+)["\']/i', $article['content'], $img_match)) {
        $article_image = $img_match[1];
    }

    // 文章描述：优先 meta_description，其次 summary，最后正文截词；统一清洗空白
    $article_desc = '';
    if (!empty($article['meta_description'])) {
        $article_desc = clean_meta_excerpt($article['meta_description'], 200);
    }
    if ($article_desc === '' && !empty($article['summary'])) {
        $article_desc = clean_meta_excerpt($article['summary'], 200);
    }
    if ($article_desc === '') {
        $article_desc = clean_meta_excerpt($article['content'], 200);
    }

    // 文章规范链接（schema/og/canonical 共用）
    $article_canonical_url = build_site_base_url($site) . '/article/' . $article['id'] . '.html';

    $article_schema = [
        '@context' => 'https://schema.org',
        '@type' => 'Article',
        'headline' => $article['title'],
        'description' => $article_desc,
        'author' => [
            '@type' => 'Organization',
            'name' => $site['site_name'],
            'url' => build_site_base_url($site) . '/'
        ],
        'publisher' => [
            '@type' => 'Organization',
            'name' => $site['site_name'],
            'url' => build_site_base_url($site) . '/',
            'logo' => [
                '@type' => 'ImageObject',
                'url' => build_site_base_url($site) . '/favicon.ico'
            ]
        ],
        'datePublished' => $article['created_at'],
        'dateModified' => !empty($article['updated_at']) ? $article['updated_at'] : $article['created_at'],
        'mainEntityOfPage' => ['@type' => 'WebPage', '@id' => $article_canonical_url]
    ];
    if ($article_image) {
        $article_schema['image'] = $article_image;
    }

    // WebSite schema（与首页/栏目页一致，让文章页也具备 SearchAction）
    $website_schema = [
        '@context' => 'https://schema.org',
        '@type' => 'WebSite',
        'name' => $site['site_name'],
        'url' => build_site_base_url($site) . '/',
        'description' => $site['description'] ?? '',
        'potentialAction' => [
            '@type' => 'SearchAction',
            'target' => build_site_base_url($site) . '/article/{search_term_string}.html',
            'query-input' => 'required name=search_term_string'
        ]
    ];

    // 面包屑第三级补 item 字段（Google 富结果要求每个 ListItem 都有 item）
    $breadcrumb_schema = [
        '@context' => 'https://schema.org',
        '@type' => 'BreadcrumbList',
        'itemListElement' => [
            ['@type' => 'ListItem', 'position' => 1, 'name' => '首页', 'item' => build_site_base_url($site) . '/'],
            ['@type' => 'ListItem', 'position' => 2, 'name' => '文章', 'item' => build_site_base_url($site) . '/article.html'],
            ['@type' => 'ListItem', 'position' => 3, 'name' => $article['title'], 'item' => $article_canonical_url]
        ]
    ];

    $schema_scripts = [
        json_encode($website_schema, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT),
        json_encode($article_schema, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT),
        json_encode($breadcrumb_schema, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT),
    ];

    // 城市分站：追加 LocalBusiness schema（与首页一致）
    if (!empty($GLOBALS['is_city_channel']) && !empty($GLOBALS['city_channel'])) {
        $city_channel = $GLOBALS['city_channel'];
        $city_name = $city_channel['city_name'] ?? '';
        if ($city_name) {
            $lb = [
                '@context' => 'https://schema.org',
                '@type' => 'LocalBusiness',
                'name' => ($site['site_name'] ?? '') . ' - ' . $city_name . '分站',
                'description' => $site['description'] ?? '',
                'url' => build_site_base_url($site) . '/',
                'areaServed' => ['@type' => 'City', 'name' => $city_name],
                'address' => [
                    '@type' => 'PostalAddress',
                    'addressLocality' => $city_name,
                    'addressRegion' => $city_channel['province'] ?? '',
                    'addressCountry' => 'CN'
                ]
            ];
            $contact_phone_lb = get_setting('contact_phone', $site['id']);
            $contact_email_lb = get_setting('contact_email', $site['id']);
            $contact_address_lb = get_setting('contact_address', $site['id']);
            if ($contact_phone_lb) $lb['telephone'] = $contact_phone_lb;
            if ($contact_email_lb) $lb['email'] = $contact_email_lb;
            if ($contact_address_lb) $lb['address']['streetAddress'] = $contact_address_lb;
            $schema_scripts[] = json_encode($lb, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
        }
    }

    $schema_json = '';
    foreach ($schema_scripts as $_ss) {
        $schema_json .= '<script type="application/ld+json">' . "\n" . $_ss . "\n" . '</script>' . "\n";
    }
    
    // FAQ Schema (GEO: AI 搜索引擎优先引用有 FAQ 结构的内容)
    if (!empty($article['faq_json'])) {
        $faq_data = json_decode($article['faq_json'], true);
        if (is_array($faq_data) && !empty($faq_data)) {
            $faq_entities = [];
            foreach ($faq_data as $faq) {
                if (isset($faq['question']) && isset($faq['answer'])) {
                    $faq_entities[] = [
                        '@type' => 'Question',
                        'name' => $faq['question'],
                        'acceptedAnswer' => [
                            '@type' => 'Answer',
                            'text' => $faq['answer']
                        ]
                    ];
                }
            }
            if (!empty($faq_entities)) {
                $faq_schema = [
                    '@context' => 'https://schema.org',
                    '@type' => 'FAQPage',
                    'mainEntity' => $faq_entities
                ];
                $schema_json .= "\n" . '<script type="application/ld+json">' . "\n" . json_encode($faq_schema, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n" . '</script>';
            }
        }
    }
    
    // 导航
    $nav_links = build_pipeline_nav_links_html($site['id']);
    $nav_links = wrap_nav_links_in_li($nav_links);
    
    // 面包屑
    $breadcrumb = '<a href="' . site_url('home') . '">首页</a><span>&gt;</span><span>' . htmlspecialchars($article['title']) . '</span>';
    
    // 广告HTML
    $ad_top = '';
    foreach ($top_ads as $ad) {
        $ad_top .= '<div class="ad-box"><a href="' . htmlspecialchars($ad['link_url']) . '" target="_blank"><img src="' . htmlspecialchars($ad['image_url']) . '" alt="' . htmlspecialchars($ad['title'] ?? $site['site_name'] . ' - 广告') . '"></a></div>';
    }
    
    $ad_side = '';
    foreach ($side_ads as $ad) {
        $ad_side .= '<div class="ad-box"><a href="' . htmlspecialchars($ad['link_url']) . '" target="_blank"><img src="' . htmlspecialchars($ad['image_url']) . '" alt="' . htmlspecialchars($ad['title'] ?? $site['site_name'] . ' - 广告') . '"></a></div>';
    }
    
    $ad_footer = '';
    foreach ($footer_ads as $ad) {
        $ad_footer .= '<div class="ad-box"><a href="' . htmlspecialchars($ad['link_url']) . '" target="_blank"><img src="' . htmlspecialchars($ad['image_url']) . '" alt="' . htmlspecialchars($ad['title'] ?? $site['site_name'] . ' - 广告') . '"></a></div>';
    }
    
    $ad_content = '';
    foreach ($content_ads as $ad) {
        $ad_content .= '<div class="ad-box"><a href="' . htmlspecialchars($ad['link_url']) . '" target="_blank"><img src="' . htmlspecialchars($ad['image_url']) . '" alt="' . htmlspecialchars($ad['title'] ?? $site['site_name'] . ' - 广告') . '"></a></div>';
    }
    
    // 热门文章
    $hot_articles_html = '';
    if (!empty($hot_articles)) {
        foreach ($hot_articles as $h) {
            $hot_articles_html .= '<li>';
            $hot_articles_html .= '<a href="' . site_url('article', ['id' => $h['id']]) . '">' . htmlspecialchars($h['title']) . '</a>';
            $hot_articles_html .= '<div class="date">' . date('Y-m-d', strtotime($h['created_at'])) . '</div>';
            $hot_articles_html .= '</li>';
        }
    }
    
    // 相关文章
    $related_articles_html = '';
    if (!empty($related_articles)) {
        foreach ($related_articles as $r) {
            $related_articles_html .= '<li>';
            $related_articles_html .= '<a href="' . site_url('article', ['id' => $r['id']]) . '">' . htmlspecialchars($r['title']) . '</a>';
            $related_articles_html .= '<div class="date">' . date('Y-m-d', strtotime($r['created_at'])) . '</div>';
            $related_articles_html .= '</li>';
        }
    }
    
    // 文章内容（注入内容广告）
    $article_content = $article['content'];
    // 修复BUG-13: 广告插入位置计算修正——用整数除法，且广告HTML需要补</p>
    if (!empty($content_ads)) {
        $paragraphs = explode('</p>', $article_content);
        $total_paragraphs = count($paragraphs);
        // 在30%位置插入，至少在第1段之后，确保是整数
        $insert_pos = max(1, intval($total_paragraphs * 0.3));
        if ($insert_pos < $total_paragraphs) {
            // 广告div作为独立"段落"插入，需要补</p>以保持结构完整
            $ad_html = '<div class="ad-box" style="margin:20px 0;">' . $ad_content . '</div></p>';
            array_splice($paragraphs, $insert_pos, 0, $ad_html);
            $article_content = implode('</p>', $paragraphs);
        }
    }
    
    // 联系方式
    $contact_info = '';
    if ($contact_phone) $contact_info .= '<p>电话：' . htmlspecialchars($contact_phone) . '</p>';
    if ($contact_email) $contact_info .= '<p>邮箱：' . htmlspecialchars($contact_email) . '</p>';
    if ($contact_address) $contact_info .= '<p>地址：' . htmlspecialchars($contact_address) . '</p>';
    if ($contact_wechat) $contact_info .= '<p>微信：' . htmlspecialchars($contact_wechat) . '</p>';
    
    // 版权信息 —— 系统固定控制，站点地图由{SITEMAP_LINK}占位符单独处理
    $footer_parts = [$site['site_name'] . ' &copy; ' . date('Y')];
    if (!empty($footer_code)) $footer_parts[] = $footer_code;
    $footer_copyright = implode(' | ', $footer_parts);
    
    // 修复BUG-ARTICLE-CSS: 正确提取CSS——只提取<style>标签内的内容，不把整个HTML当CSS
    $custom_css = '';
    if ($site_template) {
        $css_parts = [];
        // 优先使用content_1（CSS设计系统）
        if (!empty($site_template['content_1'])) {
            // 清理 content_1 中可能残留的 <style> 标签（避免双 <style> 问题）
            $raw_css = $site_template['content_1'];
            $raw_css = preg_replace('/^\s*<style[^>]*>\s*/i', '', $raw_css);
            $raw_css = preg_replace('/\s*<\/style>\s*$/i', '', $raw_css);
            $css_parts[] = trim($raw_css);
        }
        
        // 从content_article中提取<style>标签内容（v3.0+ 不再fallback到content_4/3）
        $html_sources = [];
        if (!empty($site_template['content_article'])) {
            $html_sources[] = $site_template['content_article'];
        } elseif (!$is_v3 && !empty($site_template['content_4'])) {
            $html_sources[] = $site_template['content_4'];
        } elseif (!$is_v3 && !empty($site_template['content_3'])) {
            $html_sources[] = $site_template['content_3'];
        }
        
        foreach ($html_sources as $src) {
            // 只提取 <style>...</style> 之间的CSS内容
            if (preg_match_all('/<style[^>]*>(.*?)<\/style>/is', $src, $matches)) {
                foreach ($matches[1] as $css_block) {
                    $css_parts[] = trim($css_block);
                }
            }
        }
        
        if (!empty($css_parts)) {
            $custom_css = '<style>' . implode("\n", $css_parts) . '</style>';
        }
    }
    
    // OG标签（$article_desc 在上方已按 meta_description > summary > 正文截词 清洗过）
    $og_image = '';
    if ($article_image) {
        $og_image = $article_image;
    } elseif (preg_match('/<img[^>]+src=["\']([^"\']+)["\']/i', $article['content'], $img_match)) {
        $og_image = $img_match[1];
    }
    $site_base_url = build_site_base_url($site);
    $og_tags = '<meta property="og:type" content="article">' . "\n"
        . '<meta property="og:title" content="' . htmlspecialchars($article['title']) . '">' . "\n"
        . '<meta property="og:description" content="' . htmlspecialchars($article_desc) . '">' . "\n"
        . '<meta property="og:url" content="' . htmlspecialchars($site_base_url) . '/article/' . $article['id'] . '.html">' . "\n"
        . '<meta property="og:site_name" content="' . htmlspecialchars($site['site_name']) . '">' . "\n"
        . '<meta property="og:locale" content="zh_CN">' . "\n"
        . '<meta property="article:published_time" content="' . $article['created_at'] . '">' . "\n"
        . (!empty($article['updated_at']) ? '<meta property="article:modified_time" content="' . $article['updated_at'] . '">' . "\n" : '')
        . '<meta name="twitter:card" content="summary">' . "\n"
        . '<meta name="twitter:title" content="' . htmlspecialchars($article['title']) . '">' . "\n"
        . '<meta name="twitter:description" content="' . htmlspecialchars($article_desc) . '">';
    if ($og_image) {
        $og_tags .= "\n" . '<meta property="og:image" content="' . htmlspecialchars($og_image) . '">' . "\n"
            . '<meta name="twitter:image" content="' . htmlspecialchars($og_image) . '">';
    }

    // SEO: 构建更丰富的标题层次
    $article_keyword = $article['keyword'] ?? '';
    $title_parts = [htmlspecialchars($article['title'])];
    if (!empty($article_keyword)) {
        $title_parts[] = htmlspecialchars($article_keyword);
    }
    $title_parts[] = htmlspecialchars($site['site_name']);
    $page_title = implode(' | ', $title_parts);
    
    // SEO: 扩展关键词（文章关键词 + 站点关键词，去重）
    $kw_parts = [];
    if (!empty($article_keyword)) {
        $kw_parts[] = $article_keyword;
    }
    if (!empty($site['keywords'])) {
        $site_kws = array_map('trim', explode(',', $site['keywords']));
        foreach ($site_kws as $sk) {
            if (!empty($sk) && !in_array($sk, $kw_parts)) {
                $kw_parts[] = $sk;
            }
        }
    }
    $meta_keywords = htmlspecialchars(implode(', ', array_slice($kw_parts, 0, 8)));

    return [
        'site_id' => intval($site['id']),
        'site_name' => htmlspecialchars($site['site_name']),
        'site_description' => htmlspecialchars($site['description']),
        'site_domain' => $site['domain'],
        'page_title' => $page_title,
        'meta_description' => htmlspecialchars($article_desc),
        'meta_keywords' => $meta_keywords,
        'canonical_url' => build_site_base_url($site) . '/article/' . $article['id'] . '.html',
        'og_tags' => $og_tags,
        'nav_links' => $nav_links,
        'ad_top' => $ad_top,
        'ad_side' => $ad_side,
        'ad_footer' => $ad_footer,
        'ad_content' => $ad_content,
        'article_list' => '',
        'seo_content' => '',
        'image_gallery' => '',
        'breadcrumb' => $breadcrumb,
        'article_title' => htmlspecialchars($article['title']),
        'article_date' => date('Y-m-d H:i', strtotime($article['created_at'])),
        'article_content' => $article_content,
        'article_update' => '最后更新：' . date('Y-m-d H:i', strtotime($article['created_at'])),
        'related_articles' => $related_articles_html,
        'hot_articles' => $hot_articles_html,
        'contact_info' => $contact_info,
        'footer' => $site_template['content_footer'] ?? '',
        'footer_copyright' => $footer_copyright,
        'last_update' => date('Y-m-d H:i', strtotime($article['created_at'])),
        'sitemap_link' => $sitemap_file ? $sitemap_file : '',
        'schema_json' => $schema_json,
        'verify_tags' => $verify_tags,
        'icp_number' => build_icp_html(get_setting('icp_number', $site['id']) ?? ''),
        'custom_css' => $custom_css,
        'text_color' => $text_color,
    ];
}

// ==================== 统一404处理 ====================
function show_404($site, $site_template = null) {
    http_response_code(404);
    $data = build_home_template_data($site, $site_template);
    $data['page_title'] = '页面未找到 - ' . htmlspecialchars($site['site_name']);
    $data['seo_content'] = '<div style="text-align:center;padding:80px 20px;">'
        . '<h1 style="font-size:64px;color:#ddd;margin:0;">404</h1>'
        . '<p style="font-size:18px;color:#999;margin:20px 0;">抱歉，您访问的页面不存在</p>'
        . '<a href="' . site_url('home') . '" style="display:inline-block;padding:10px 30px;background:' . ($data['theme_color'] ?? '#1890ff') . ';color:#fff;border-radius:4px;text-decoration:none;">返回首页</a>'
        . '</div>';
    $template_html = get_template_html($site, $site_template, 'home');
    if ($template_html) {
        echo render_site_template($template_html, $data, '404');
    } else {
        echo '<!DOCTYPE html><html><head><meta charset="UTF-8"><title>404 - 页面未找到</title></head><body style="font-family:sans-serif;text-align:center;padding:80px 20px;"><h1 style="font-size:64px;color:#ddd;">404</h1><p style="color:#999;">抱歉，您访问的页面不存在</p><a href="/" style="color:#1890ff;">返回首页</a></body></html>';
    }
    exit;
}

// ==================== 通用TDK注入函数 ====================
/**
 * 通用TDK注入：清理HTML中已有的TDK标签，重新注入到<head>中
 * 适用于所有渲染路径（单页面、模板、默认）
 * 
 * @param string $html 原始HTML
 * @param array $site 站点信息
 * @param array $tdk_data TDK数据，可包含 page_title, meta_description, meta_keywords, verify_tags, og_tags, schema_json, custom_css
 * @return string 注入TDK后的HTML
 */
function inject_tdk_into_html($html, $site, $tdk_data = []) {
    $tdk_page_title = $tdk_data['page_title'] ?? '';
    $tdk_meta_desc = $tdk_data['meta_description'] ?? '';
    $tdk_meta_kw = $tdk_data['meta_keywords'] ?? '';
    $tdk_verify = $tdk_data['verify_tags'] ?? '';
    $tdk_og = $tdk_data['og_tags'] ?? '';
    $tdk_schema = $tdk_data['schema_json'] ?? '';
    $tdk_custom_css = $tdk_data['custom_css'] ?? '';
    
    // Step 1: 清理HTML中已有的TDK标签（避免重复）
    $html = preg_replace('/<title[^>]*>.*?<\/title>\s*/is', '', $html);
    $html = preg_replace('/<meta\s+name="description"[^>]*>\s*/is', '', $html);
    $html = preg_replace('/<meta\s+name="keywords"[^>]*>\s*/is', '', $html);
    $html = preg_replace('/<meta\s+property="og:[^"]*"[^>]*>\s*/is', '', $html);
    $html = preg_replace('/<meta\s+name="twitter:[^"]*"[^>]*>\s*/is', '', $html);
    $html = preg_replace('/<script\s+type="application\/ld\+json"[^>]*>[\s\S]*?<\/script>\s*/is', '', $html);
    
    // Step 2: 构建完整的<head>内容
    $head_content = '<meta charset="UTF-8">' . "\n";
    $head_content .= '<meta name="viewport" content="width=device-width, initial-scale=1.0">' . "\n";
    // 始终注入 title，兜底到站点名称
    $title_to_inject = !empty($tdk_page_title) ? $tdk_page_title : ($site['title'] ?: $site['site_name'] ?: '网站标题');
    $head_content .= '<title>' . $title_to_inject . '</title>' . "\n";
    if (!empty($tdk_meta_desc)) {
        $head_content .= '<meta name="description" content="' . $tdk_meta_desc . '">' . "\n";
    }
    if (!empty($tdk_meta_kw)) {
        $head_content .= '<meta name="keywords" content="' . $tdk_meta_kw . '">' . "\n";
    }
    if (!empty($tdk_verify)) {
        $head_content .= $tdk_verify;
    }
    if (!empty($tdk_og)) {
        $head_content .= $tdk_og . "\n";
    }
    // SEO: 注入 canonical URL
    $tdk_canonical = $tdk_data['canonical_url'] ?? '';
    if (!empty($tdk_canonical)) {
        $head_content .= '<link rel="canonical" href="' . htmlspecialchars($tdk_canonical) . '">' . "\n";
    }
    // SEO: 注入 RSS alternate 链接
    $site_domain = $site['domain'] ?? '';
    if (!empty($site_domain)) {
        $head_content .= '<link rel="alternate" type="application/rss+xml" title="Sitemap" href="' . build_site_base_url($site) . '/sitemap.xml">' . "\n";
    }
    if (!empty($tdk_schema)) {
        $head_content .= $tdk_schema . "\n";
    }
    if (!empty($tdk_custom_css)) {
        // v3.3 DEBUG: 检查 tdk_custom_css 是否含 {CUSTOM_CSS}
        $tdk_css_has_placeholder = (stripos($tdk_custom_css, '{CUSTOM_CSS}') !== false);
        debug_log('[v3.3 DEBUG] inject_tdk_into_html: tdk_custom_css 含{CUSTOM_CSS}=' . ($tdk_css_has_placeholder ? 'YES!' : 'no') . ', len=' . strlen($tdk_custom_css));
        // v3.3 FIX: 清理 tdk_custom_css 中残留的 {CUSTOM_CSS}
        $tdk_custom_css = preg_replace('/\{custom_css\}/i', '', $tdk_custom_css);
        // v3.7 FIX: 清理已有的 <style> 标签，避免双重包裹导致 <style><style>...</style></style>
        $tdk_custom_css = preg_replace('/^\s*<style[^>]*>\s*/i', '', $tdk_custom_css);
        $tdk_custom_css = preg_replace('/\s*<\/style>\s*$/i', '', $tdk_custom_css);
        $head_content .= '<style>' . $tdk_custom_css . '</style>' . "\n";
    }
    
    // Step 3: 检查模板结构并注入<head>
    // v23修复：使用精确正则匹配<head>，避免stripos('<head')误匹配<header>标签
    $has_html_tag = (stripos($html, '<html') !== false);
    $has_head = preg_match('/<head(\s|>)/i', $html);
    $has_doctype = (stripos($html, '<!doctype') !== false);
    
    if ($has_head) {
        // v24修复：保留<head>中已有的<style>和<link>标签，只替换TDK相关标签
        // 先提取<head>内的<style>和<link>标签（v3.14修复：只从<head>内提取，避免提取<body>中的style导致CSS重复）
        $existing_styles = '';
        $head_html = '';
        if (preg_match('/<head[^>]*>([\s\S]*?)<\/head>/is', $html, $head_match)) {
            $head_html = $head_match[1];
        }
        if ($head_html !== '' && preg_match_all('/<style[^>]*>[\s\S]*?<\/style>/is', $head_html, $style_matches)) {
            foreach ($style_matches[0] as $style_block) {
                // v3.3 FIX: 清理提取的 <style> 块中残留的 {CUSTOM_CSS}
                $cleaned = preg_replace('/\{custom_css\}/i', '', $style_block);
                if ($cleaned !== $style_block) {
                    debug_log('[v3.3 DEBUG] inject_tdk_into_html: 提取的style块含{CUSTOM_CSS}, 已清理');
                }
                $existing_styles .= "\n" . $cleaned;
            }
        }
        $existing_links = '';
        if ($head_html !== '' && preg_match_all('/<link[^>]*>/is', $head_html, $link_matches)) {
            // 过滤掉可能存在的TDK相关link（如og:相关的meta link）
            foreach ($link_matches[0] as $link) {
                if (stripos($link, 'stylesheet') !== false || stripos($link, 'icon') !== false || stripos($link, 'preconnect') !== false || stripos($link, 'preload') !== false) {
                    $existing_links .= "\n" . $link;
                }
            }
        }
        // 将原有的style和link追加到head_content后面
        $head_content .= $existing_links . $existing_styles;
        // 替换整个<head>内容
        $html = preg_replace('/<head[^>]*>[\s\S]*?<\/head>/is', '<head>' . "\n" . $head_content . "\n" . '</head>', $html, 1);
    } elseif ($has_html_tag) {
        // 有<html>但没有<head>，在<html>后注入<head>
        $html = preg_replace('/(<html[^>]*>)/is', '$1' . "\n" . '<head>' . "\n" . $head_content . '</head>', $html, 1);
    } else {
        // 没有<html>和<head>（AI生成的纯body内容），在开头注入完整结构
        $prefix = '';
        if (!$has_doctype) {
            $prefix .= '<!DOCTYPE html>' . "\n";
        }
        $prefix .= '<html lang="zh-CN">' . "\n" . '<head>' . "\n" . $head_content . '</head>' . "\n" . '<body>' . "\n";
        $html = $prefix . $html;
        $html .= "\n" . '</body>' . "\n" . '</html>';
    }
    
    return $html;
}

/**
 * 构建城市TDK数据（通用，供所有渲染路径使用）
 * 返回包含 page_title, meta_description, meta_keywords 的数组
 */
function build_city_tdk_data($site) {
    $tdk = [];
    $city_name = '';
    
    // 城市分站TDK覆盖
    if (!empty($GLOBALS['is_city_channel']) && !empty($GLOBALS['city_channel'])) {
        $city_channel = $GLOBALS['city_channel'];
        $city_name = $city_channel['city_name'] ?? '';
        if (!empty($city_channel['tdk_title'])) {
            $tdk['page_title'] = replace_city_variables($city_channel['tdk_title'], $city_channel);
        }
        if (!empty($city_channel['tdk_keywords'])) {
            $tdk['meta_keywords'] = replace_city_variables($city_channel['tdk_keywords'], $city_channel);
        }
        if (!empty($city_channel['tdk_description'])) {
            $tdk['meta_description'] = replace_city_variables($city_channel['tdk_description'], $city_channel);
        }
    }
    
    // 兜底：使用站点本身的TDK
    if (empty($tdk['page_title'])) {
        $base_title = htmlspecialchars($site['title'] ?: $site['site_name']);
        // SEO: 城市分站标题必须包含城市名，避免与主站标题重复
        if (!empty($city_name) && mb_strpos($base_title, $city_name) === false) {
            $tdk['page_title'] = $city_name . $base_title;
        } else {
            $tdk['page_title'] = $base_title;
        }
    }
    if (empty($tdk['meta_description'])) {
        $base_desc = htmlspecialchars($site['description'] ?? '');
        // SEO: 城市分站描述中补充城市名
        if (!empty($city_name) && mb_strpos($base_desc, $city_name) === false) {
            $tdk['meta_description'] = '【' . $city_name . '】' . $base_desc;
        } else {
            $tdk['meta_description'] = $base_desc;
        }
    }
    if (empty($tdk['meta_keywords'])) {
        $base_kw = htmlspecialchars($site['keywords'] ?? '');
        // SEO: 城市分站关键词中补充城市名
        if (!empty($city_name) && mb_strpos($base_kw, $city_name) === false) {
            $tdk['meta_keywords'] = $city_name . ', ' . $base_kw;
        } else {
            $tdk['meta_keywords'] = $base_kw;
        }
    }
    
    // SEO: 注入 canonical URL
    $tdk['canonical_url'] = build_site_base_url($site) . '/';
    
    return $tdk;
}

// ==================== CSS块合并（最终保障） ====================
// 确保页面只有一个<style>块，解决AI生成HTML中包含<style>标签导致的重复问题
function merge_style_blocks($html) {
    // 统计<style>块数量
    $style_count = preg_match_all('/<style[^>]*>(.*?)<\/style>/is', $html, $matches);
    
    if ($style_count <= 1) {
        // 只有一个或没有<style>块，无需处理
        return $html;
    }
    
    // 提取所有<style>块中的CSS内容
    $all_css = [];
    foreach ($matches[1] as $css_content) {
        $css_content = trim($css_content);
        if (!empty($css_content)) {
            $all_css[] = $css_content;
        }
    }
    
    if (empty($all_css)) {
        return $html;
    }
    
    // 合并所有CSS
    $combined_css = implode("\n\n/* --- merged --- */\n\n", $all_css);
    
    // 移除所有<style>块
    $html = preg_replace('/<style[^>]*>.*?<\/style>/is', '', $html);
    
    // 在</head>前插入合并后的<style>块
    $style_tag = '<style>' . "\n" . $combined_css . "\n" . '</style>';
    
    if (stripos($html, '</head>') !== false) {
        $html = str_ireplace('</head>', $style_tag . "\n</head>", $html);
    } elseif (stripos($html, '<body') !== false) {
        $html = preg_replace('/<body/i', $style_tag . "\n<body", $html, 1);
    } else {
        // 兜底：添加到开头
        $html = $style_tag . "\n" . $html;
    }
    
    return $html;
}

// ==================== 文章列表统一后置追加 ====================
// 解决单页面和管线站点文章列表不显示的问题
// 在所有渲染路径完成后，检测 HTML 中是否已有文章列表，没有则追加
function append_article_list_if_needed($html, $site_id) {
    $articles = get_articles($site_id, 10);

    // ========== 策略1：填充空壳 ==========
    // 检测模板中 {ARTICLE_LIST} 替换后留下的空壳 <div class="article-list"></div>
    $empty_shell_pattern = '/<div\s+class="article-list"\s*>\s*<\/div>/i';
    if (preg_match($empty_shell_pattern, $html)) {
        if (!empty($articles)) {
            // 有文章：填充第一个空壳，移除其余
            $article_list_html = build_article_list_html($articles, ['id' => $site_id]);
            if (!empty($article_list_html)) {
                // 替换第一个空壳为文章列表
                $html = preg_replace($empty_shell_pattern, $article_list_html, $html, 1);
            }
        }
        // 移除所有剩余的空壳（无论有无文章）
        $html = preg_replace($empty_shell_pattern, '', $html);
        // 同时移除包裹空壳的空 section（如 <section class="articles-section">\n<h2>...</h2>\n</section>）
        $html = preg_replace('/<section\s+class="articles-section"\s*>\s*(?:<h2[^>]*>.*?<\/h2>)?\s*<\/section>/is', '', $html);
        return $html;
    }

    // ========== 策略2：后置追加 ==========
    // 没有空壳，走原来的追加逻辑
    if (empty($articles)) {
        return $html;
    }
    // 检查是否已有文章列表（避免重复）
    if (stripos($html, 'sys-inj-articles') !== false ||
        stripos($html, 'data-sg-articles') !== false ||
        strpos($html, 'class="article-list"') !== false ||
        stripos($html, 'class=\'article-list\'') !== false) {
        return $html;
    }
    $article_list_html = build_article_list_html($articles, ['id' => $site_id]);
    if (empty($article_list_html)) {
        return $html;
    }
    $wrapper = '<div class="sys-inj-articles" style="background:transparent;padding:30px 15px;max-width:1000px;margin:0 auto;width:100%;box-sizing:border-box;clear:both;">' . $article_list_html . '</div>';

    // 注入位置：优先 </main> 前；否则最后一个真页脚（class 含 site-footer/page-footer，或最后一个 <footer>）；再否则 </body> 前
    $inject_pos = -1;
    if (preg_match('/<\/main>/i', $html, $mm, PREG_OFFSET_CAPTURE)) {
        $inject_pos = $mm[0][1];
    } elseif (preg_match('/<footer\b[^>]*class=["\'][^"\']*(?:site-footer|page-footer|main-footer)[^"\']*["\'][^>]*>/i', $html, $mf, PREG_OFFSET_CAPTURE)) {
        $inject_pos = $mf[0][1];
    } elseif (preg_match_all('/<footer\b/i', $html, $allf, PREG_OFFSET_CAPTURE)) {
        $last = end($allf[0]);
        $inject_pos = $last[1];
    } elseif (preg_match('/<\/body>/i', $html, $mb, PREG_OFFSET_CAPTURE)) {
        $inject_pos = $mb[0][1];
    }
    if ($inject_pos >= 0) {
        $html = substr($html, 0, $inject_pos) . "\n" . $wrapper . "\n" . substr($html, $inject_pos);
    }
    return $html;
}

// ==================== 首页 ====================
function show_home($site, $site_template = null) {
    // 记录访客统计
    if (!empty($site['id'])) {
        log_visitor($site['id'], 'home');
    }
    
    // v3.0: 检测管线版本，新版本不再fallback到旧字段
    $pipeline_version = $site['pipeline_version'] ?? '';
    $is_v3 = version_compare($pipeline_version, '3.0', '>=');
    
    // 检查是否为单页面类型
    $is_single_page = false;
    if ($site_template && !empty($site_template['template_type']) && $site_template['template_type'] === 'single') {
        $is_single_page = true;
    }
    
    // 单页面类型：直接输出AI生成的HTML，注入所有业务组件
    if ($is_single_page && !empty($site_template['content_home'])) {
        debug_log('[v3.3 DEBUG] show_home: 路径A-single_page, site_id=' . ($site['id'] ?? '?'));
        $html = $site_template['content_home'];
        
        // v4.1防御：如果content_home中存储的是原始JSON，自动提取html字段
        $trimmed_sp = ltrim($html);
        if (strlen($trimmed_sp) > 0 && $trimmed_sp[0] === '{' && stripos($trimmed_sp, '"html"') !== false) {
            $extracted_sp = null;
            if (function_exists('extract_json_from_ai')) {
                $parsed_sp = extract_json_from_ai($html);
                if ($parsed_sp && isset($parsed_sp['html'])) {
                    $extracted_sp = $parsed_sp['html'];
                }
            }
            if (empty($extracted_sp) && function_exists('extract_field_from_raw_json')) {
                $extracted_sp = extract_field_from_raw_json($html, 'html');
            }
            if (!empty($extracted_sp)) {
                $html = $extracted_sp;
                error_log('[v4.1] single_page content_home包含原始JSON，已自动提取html字段');
            }
        }
        
        // 占位符替换
        $data = build_single_page_data($site, $site_template);
        $raw_css = $site_template['content_1'] ?? '';
        // 渲染时再做一次 CSS 清洗（兼容老数据：修复 AI 偶尔生成的 `root {` 丢冒号等问题）
        if (function_exists('sanitize_css')) {
            $raw_css = sanitize_css($raw_css);
        }
        
        // CSS智能合并：如果HTML中包含<style>块，提取其CSS并与content_1合并；
        // 管线首页在生成后已剥离 <style>（整份CSS存于 content_1），因此必须无条件在 </head> 前注入 {CUSTOM_CSS}
        if (stripos($html, '<style') !== false) {
            if (preg_match_all('/<style[^>]*>(.*?)<\/style>/is', $html, $matches)) {
                foreach ($matches[1] as $css_block) {
                    $css_content = trim($css_block);
                    if (!empty($css_content) && $css_content !== '{CUSTOM_CSS}') {
                        $raw_css .= "\n" . $css_block;
                    }
                }
            }
            // 移除所有现有的<style>块
            $html = preg_replace('/<style[^>]*>.*?<\/style>/is', '', $html);
        }
        // 管线首页场景：content_home 已剥离 <style>，且没有 {CUSTOM_CSS} 占位符，
        // 必须在 </head> 前无条件注入一次。注意这里用 str_replace 只替换第一处，
        // 避免 apply_placeholders() 的兜底逻辑再追加一遍造成 CSS 重复。
        if (stripos($html, '{CUSTOM_CSS}') === false) {
            if (stripos($html, '</head>') !== false) {
                $html = preg_replace('/<\/head>/i', "<style>\n{CUSTOM_CSS}\n</style>\n</head>", $html, 1);
            } elseif (stripos($html, '<body') !== false) {
                $html = preg_replace('/<body/i', "<style>\n{CUSTOM_CSS}\n</style>\n<body", $html, 1);
            }
        }
        
        $html = render_single_page_template($html, $data, $raw_css);
        
        // 注入广告、弹窗、营销组件、Schema、统计代码等（与管线站点一致）
        $html = inject_single_page($html, $site);

        // 导航去重：AI 模板里可能在 hero 下写了第 3+ 个 <nav class="category-nav">，
        // 归一化后每个 nav 都会被注入 {NAV_LINKS} 导致多套栏目。这里在占位符已经
        // 全部替换为真实链接后，把多余 nav 里的链接（与第一个 nav 完全一致的那段）清空。
        // 注意：此时 {NAV_LINKS} 已替换成 <li><a>...</a></li>，需要按"重复 nav 内容"去重。
        // 为了安全，只清空与第一个 nav 内部 HTML 完全相同的后续 nav，不影响其他不同的链接。
        if (function_exists('pipeline_dedupe_nav_placeholders') && stripos($html, '<nav') !== false) {
            // Path A 占位符已替换，使用运行时去重：找出所有 nav，若内部链接集合与第一个相同则清空第 3+ 个
            if (preg_match_all('/<nav\b[^>]*>([\s\S]*?)<\/nav>/i', $html, $all_navs, PREG_SET_ORDER)) {
                if (count($all_navs) > 2) {
                    $first_inner = trim($all_navs[0][1]);
                    // 只处理"inner 与第一个完全相同"的后续 nav（避免误伤 footer 站点地图等不同链接）
                    $seen_count = 0;
                    $html = preg_replace_callback(
                        '/<nav\b([^>]*)>([\s\S]*?)<\/nav>/i',
                        function($m) use ($first_inner, &$seen_count) {
                            $inner = trim($m[2]);
                            // 只要内部链接结构与主导航完全一致，第 2 个之后的全部清空（第 2 个通常是手机菜单，保留）
                            if ($inner === $first_inner) {
                                $seen_count++;
                                if ($seen_count >= 3) {
                                    return '<nav' . $m[1] . '></nav>';
                                }
                            }
                            return $m[0];
                        },
                        $html
                    );
                }
            }
        }
        
        // v22修复：为单页面路径也注入TDK（城市分站TDK覆盖 + 站点TDK兜底）
        // 同时把 build_single_page_data() 补齐的 schema_json / verify_tags / og_tags / canonical
        // 一起传进去，与 Path B 完全对齐，避免管线首页丢 Schema.org / OG / 验证标签。
        // custom_css 传空：Path A 的整份 CSS 已由 render_single_page_template() 经
        // apply_placeholders() 注入，这里不能再注入，否则会有两份 <style>。
        $city_tdk = build_city_tdk_data($site);
        $html = inject_tdk_into_html($html, $site, [
            'page_title'       => $data['{PAGE_TITLE}'] ?? $city_tdk['page_title'] ?? '',
            'meta_description' => $data['{META_DESCRIPTION}'] ?? $city_tdk['meta_description'] ?? '',
            'meta_keywords'    => $data['{META_KEYWORDS}'] ?? $city_tdk['meta_keywords'] ?? '',
            'verify_tags'      => $data['verify_tags'] ?? '',
            'og_tags'          => $data['og_tags'] ?? '',
            'schema_json'      => $data['schema_json'] ?? '',
            'custom_css'       => '',
            'canonical_url'    => $data['canonical_url'] ?? ($city_tdk['canonical_url'] ?? ''),
        ]);
        
        // v24修复：城市选择器只在城市分站站点显示（AI单页面非城市分站时不显示）
        if (($site['site_type'] ?? '') === 'city' || ($site['parent_site_id'] ?? 0) > 0) {
            $selector_site_id = $site['parent_site_id'] ?: $site['id'];
            $selector_city_id = !empty($GLOBALS['city_channel']) ? ($GLOBALS['city_channel']['city_id'] ?? null) : null;
            $city_selector = build_city_selector_html($selector_site_id, $selector_city_id);
            if ($city_selector) {
                if (strpos($html, '{CITY_SELECTOR}') !== false) {
                    $html = str_replace('{CITY_SELECTOR}', $city_selector, $html);
                } elseif (preg_match('/(<header[^>]*>)/is', $html, $matches, PREG_OFFSET_CAPTURE)) {
                    $header_pos = $matches[1][1] + strlen($matches[1][0]);
                    $html = substr($html, 0, $header_pos) . $city_selector . substr($html, $header_pos);
                }
            }
        }
        
        // 文章列表统一后置追加（单页面路径）
        $html = append_article_list_if_needed($html, $site['id']);

        // 备案号兜底：Path A 走 build_single_page_data，里面用 {ICP_NUMBER} 占位符，
        // 但 AI 生成的首页如果没写该占位符，备案号就丢了（栏目/文章页走 render_pipeline_shell_page
        // 已经在前面兜底，这里给首页 Path A 补上同样的兜底）。
        $path_a_icp = $data['{ICP_NUMBER}'] ?? '';
        if ($path_a_icp !== '' && stripos($html, 'icp-number') === false && stripos($html, 'beian.miit.gov.cn') === false) {
            $path_a_icp_html = '<div class="icp-number" style="text-align:center;padding:10px 0;color:#999;font-size:13px;">' . $path_a_icp . '</div>';
            if (stripos($html, '</footer>') !== false) {
                $html = preg_replace('~(</footer\s*>)~i', $path_a_icp_html . '$1', $html, 1);
            } elseif (stripos($html, '</body>') !== false) {
                $html = str_ireplace('</body>', $path_a_icp_html . '</body>', $html);
            } else {
                $html .= $path_a_icp_html;
            }
        }

        // 营销组件注入到</body>前
        $html = inject_marketing_into_html($html, $site['id']);
        
        // ========== 最终广告兜底注入（管线路径 / 单页面 Path A） ==========
        // 在所有处理完成后注入，确保不会被后续代码移除。
        // 去重策略：用 ad_already_in_page() 按广告图片 URL / code 指纹判断，
        // 不依赖 class 名 —— 因为 AI 模板里可能直接手写了一份广告图，
        // render_ad_html 产出的广告也没有固定 class，之前的 class 正则会漏判导致双份。
        if (!empty($data['ad_top']) && stripos($html, '<body') !== false) {
            if (!ad_already_in_page($html, $data['ad_top'])) {
                $html = preg_replace('/(<body[^>]*>)/i', '$1' . "\n" . $data['ad_top'], $html, 1);
            }
        }
        if (!empty($data['ad_footer']) && stripos($html, '</body>') !== false) {
            // 先替换空的 ad-footer 占位容器
            if (preg_match('/<div\s+class=["\']ad-footer["\']\s*>\s*<\/div>/i', $html)) {
                $html = preg_replace('/<div\s+class=["\']ad-footer["\']\s*>\s*<\/div>/i', $data['ad_footer'], $html, 1);
            } elseif (!ad_already_in_page($html, $data['ad_footer'])) {
                // 页面中没有任何同款广告（按图片 URL / code 指纹判重），插到 </body> 前
                $html = str_replace('</body>', $data['ad_footer'] . "\n" . '</body>', $html);
            }
        }
        
        echo $html;
        return;
    }
    
    // 如果有自定义模板，使用模板渲染（优先content_home，v3.0+不再fallback到content_4/2）
    $template_html = null;
    if ($site_template) {
        if (!empty($site_template['content_home'])) {
            $template_html = $site_template['content_home'];
            // v4.1防御：如果content_home中存储的是原始JSON而非纯HTML，自动提取html字段
            $trimmed_tpl = ltrim($template_html);
            if (strlen($trimmed_tpl) > 0 && $trimmed_tpl[0] === '{' && stripos($trimmed_tpl, '"html"') !== false) {
                $extracted = null;
                // 优先用 extract_json_from_ai
                if (function_exists('extract_json_from_ai')) {
                    $parsed = extract_json_from_ai($template_html);
                    if ($parsed && isset($parsed['html'])) {
                        $extracted = $parsed['html'];
                    }
                }
                // 兜底用正则提取
                if (empty($extracted) && function_exists('extract_field_from_raw_json')) {
                    $extracted = extract_field_from_raw_json($template_html, 'html');
                }
                if (!empty($extracted)) {
                    $template_html = $extracted;
                    error_log('[v4.1] content_home包含原始JSON，已自动提取html字段');
                }
            }
        } elseif (!$is_v3 && !empty($site_template['content_4'])) {
            $template_html = $site_template['content_4'];
        } elseif (!$is_v3 && !empty($site_template['content_2'])) {
            $template_html = $site_template['content_2'];
        }
    }
    
    if ($template_html) {
        // CSS智能合并：移除模板中的所有<style>块，由inject_tdk_into_html()统一注入CSS
        // 这样确保页面只有一个<style>块，避免CSS重复注入
        $has_style_before = stripos($template_html, '<style') !== false;
        $style_count_before = preg_match_all('/<style[^>]*>.*?<\/style>/is', $template_html);
        
        if ($has_style_before) {
            // 移除所有现有的<style>...</style>块
            $template_html = preg_replace('/<style[^>]*>.*?<\/style>/is', '', $template_html);
        }
        
        $has_style_after = stripos($template_html, '<style') !== false;
        debug_log('[CSS DEBUG] template_html style check: before=' . ($has_style_before ? 'yes' : 'no') . ', count=' . $style_count_before . ', after=' . ($has_style_after ? 'yes' : 'no') . ', len=' . strlen($template_html));
        
        debug_log('[v3.3 DEBUG] show_home: 路径B-render_site_template, site_id=' . ($site['id'] ?? '?') . ', has_content_home=' . (!empty($site_template['content_home']) ? 'yes' : 'no'));
        $data = build_home_template_data($site, $site_template);
        $html = render_site_template($template_html, $data, 'home');
        
        $style_count_after_render = preg_match_all('/<style[^>]*>.*?<\/style>/is', $html);
        debug_log('[CSS DEBUG] after render_site_template: style_count=' . $style_count_after_render);
        
        // v22修复：使用通用TDK注入函数（替代内联代码，确保所有路径一致）
        $html = inject_tdk_into_html($html, $site, [
            'page_title' => $data['page_title'] ?? '',
            'meta_description' => $data['meta_description'] ?? '',
            'meta_keywords' => $data['meta_keywords'] ?? '',
            'verify_tags' => $data['verify_tags'] ?? '',
            'og_tags' => $data['og_tags'] ?? '',
            'schema_json' => $data['schema_json'] ?? '',
            'custom_css' => $data['custom_css'] ?? '',
            'canonical_url' => $data['canonical_url'] ?? '',
        ]);
        
        $style_count_after_tdk = preg_match_all('/<style[^>]*>.*?<\/style>/is', $html);
        debug_log('[CSS DEBUG] after inject_tdk_into_html: style_count=' . $style_count_after_tdk);
        
        // v24修复：城市选择器只在城市分站站点显示（管线模式非城市分站时不显示）
        if (($site['site_type'] ?? '') === 'city' || ($site['parent_site_id'] ?? 0) > 0) {
            $selector_site_id = $site['parent_site_id'] ?: $site['id'];
            $selector_city_id = !empty($GLOBALS['city_channel']) ? ($GLOBALS['city_channel']['city_id'] ?? null) : null;
            $city_selector = build_city_selector_html($selector_site_id, $selector_city_id);
        } else {
            $city_selector = '';
        }
        if ($city_selector) {
            $injected = false;

            // 方案1：替换模板中的 {CITY_SELECTOR} 占位符（v19+ 推荐）
            if (strpos($html, '{CITY_SELECTOR}') !== false) {
                $html = str_replace('{CITY_SELECTOR}', $city_selector, $html);
                $injected = true;
            }

            // 方案1.5：AI模板可能生成了 .city-selector-wrap 容器（而非占位符），将选择器注入其内部
            if (!$injected && stripos($html, 'city-selector-wrap') !== false) {
                // 尝试匹配完整的容器（含内容）
                if (preg_match('/<div[^>]*class="[^"]*city-selector-wrap[^"]*"[^>]*>.*?<\/div>/is', $html, $matches, PREG_OFFSET_CAPTURE)) {
                    $old_wrap = $matches[0][0];
                    $wrap_pos = $matches[0][1];
                    $html = substr($html, 0, $wrap_pos) . $city_selector . substr($html, $wrap_pos + strlen($old_wrap));
                    $injected = true;
                } else {
                    // 兜底：只匹配开始标签，在其后注入选择器，并移除该空容器
                    if (preg_match('/<div[^>]*class="[^"]*city-selector-wrap[^"]*"[^>]*>/is', $html, $matches, PREG_OFFSET_CAPTURE)) {
                        $tag = $matches[0][0];
                        $tag_pos = $matches[0][1];
                        // 移除该标签，在原位置注入完整选择器
                        $html = substr($html, 0, $tag_pos) . $city_selector . substr($html, $tag_pos + strlen($tag));
                        $injected = true;
                    }
                }
            }

            // 方案2：兜底逻辑 - 在nav元素后注入（兼容旧模板）
            if (!$injected) {
                $nav_patterns = [
                    '/(<nav[^>]*>.*?<\/nav>)/is',
                    '/(<div[^>]*class="[^"]*nav[^"]*"[^>]*>.*?<\/div>)/is',
                    '/(<ul[^>]*class="[^"]*nav[^"]*"[^>]*>.*?<\/ul>)/is',
                ];

                foreach ($nav_patterns as $pattern) {
                    if (preg_match($pattern, $html, $matches, PREG_OFFSET_CAPTURE)) {
                        $nav_html = $matches[1][0];
                        $nav_pos = $matches[1][1];
                        $nav_end = $nav_pos + strlen($nav_html);

                        $html = substr($html, 0, $nav_end) . $city_selector . substr($html, $nav_end);
                        $injected = true;
                        break;
                    }
                }
            }

            // 方案3：最后兜底 - 在header中注入
            if (!$injected && preg_match('/(<header[^>]*>)/is', $html, $matches, PREG_OFFSET_CAPTURE)) {
                $header_pos = $matches[1][1] + strlen($matches[1][0]);
                $html = substr($html, 0, $header_pos) . $city_selector . substr($html, $header_pos);
            }
        }
        
        // 文章列表统一后置追加（管线路径）
        $html = append_article_list_if_needed($html, $site['id']);
        
        // ========== 广告兜底注入（所有路径统一处理） ==========
        // 顶部广告：注入到 <body> 后（按图片 URL / code 指纹去重，避免与 AI 模板内置广告重复）
        if (!empty($data['ad_top']) && stripos($html, '<body') !== false) {
            if (!ad_already_in_page($html, $data['ad_top'])) {
                $html = preg_replace('/(<body[^>]*>)/i', '$1' . $data['ad_top'], $html, 1);
            }
        }
        // 底部广告：注入到 <footer> 前（如果存在footer），否则注入到 </body> 前
        if (!empty($data['ad_footer']) && !ad_already_in_page($html, $data['ad_footer'])) {
            if (stripos($html, '<footer') !== false) {
                $html = preg_replace('/(<footer[^>]*>)/i', $data['ad_footer'] . "\n" . '$1', $html, 1);
            } elseif (stripos($html, '</body>') !== false) {
                $html = str_replace('</body>', $data['ad_footer'] . "\n" . '</body>', $html);
            }
        }
        
        // 最终CSS合并：确保页面只有一个<style>块
        $html = merge_style_blocks($html);
        
        // 营销组件注入到</body>前
        $html = inject_marketing_into_html($html, $site['id']);
        echo $html;
        return;
    }
    
    // 使用默认HTML
    $city_article_filter = get_current_city_article_filter($site);
    $articles = get_articles($site['id'], 20, 0, $city_article_filter);
    $top_ads = get_ads('top', $site['id']);
    $side_ads = get_ads('side', $site['id']);
    $footer_ads = get_ads('footer', $site['id']);
    
    $contact_phone = get_setting('contact_phone', $site['id']);
    $contact_email = get_setting('contact_email', $site['id']);
    $contact_address = get_setting('contact_address', $site['id']);
    $contact_wechat = get_setting('contact_wechat', $site['id']);
    $footer_code = get_setting('footer_code', $site['id']);
    // v20：城市分站从 sg_articles 查 article_type='home'，普通站点保留从 settings 查
    if (($site['site_type'] ?? '') === 'city') {
        $city_id_for_seo = $city_channel['city_id'] ?? ($site['city_id'] ?? 0);
        $home_article = db_get_one("SELECT content FROM " . table('articles') . " 
            WHERE site_id = {$site['id']} AND city_id = " . intval($city_id_for_seo) . " 
            AND article_type = 'home' 
            ORDER BY id DESC LIMIT 1");
        $seo_content = $home_article ? strip_full_html_doc($home_article['content']) : '';
    } else {
        $seo_content = strip_full_html_doc(get_setting('home_seo_content', $site['id']));
    }
    $sitemap_file = get_setting('sitemap_file', $site['id']);
    $fixed_images = [];  // 图库功能已移除，保留空数组兼容
    $latest_article = db_get_one("SELECT created_at FROM " . table('articles') . " WHERE site_id = {$site['id']} ORDER BY id DESC LIMIT 1");
    $last_update = !empty($latest_article) ? date('Y-m-d', strtotime($latest_article['created_at'])) : date('Y-m-d');
    
    $baidu_verify_tag = get_setting('baidu_verify_tag', $site['id']);
    $bing_verify_tag = get_setting('bing_verify_tag', $site['id']);
    $google_verify_tag = get_setting('google_verify_tag', $site['id']);
    $sogou_verify_tag = get_setting('sogou_verify_tag', $site['id']);
    
    // v22修复：默认路径也使用城市TDK覆盖
    $default_tdk = build_city_tdk_data($site);
    $default_page_title = $default_tdk['page_title'];
    $default_meta_desc = $default_tdk['meta_description'];
    $default_meta_kw = $default_tdk['meta_keywords'];
    ?>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title><?php echo $default_page_title; ?></title>
    <meta name="description" content="<?php echo $default_meta_desc; ?>">
    <meta name="keywords" content="<?php echo $default_meta_kw; ?>">
    <link rel="canonical" href="<?php echo build_site_base_url($site); ?>/">
    <meta property="og:type" content="website">
    <meta property="og:title" content="<?php echo $default_page_title; ?>">
    <meta property="og:description" content="<?php echo $default_meta_desc; ?>">
    <meta property="og:url" content="<?php echo build_site_base_url($site); ?>/">
    <meta property="og:site_name" content="<?php echo htmlspecialchars($site['site_name']); ?>">
    <meta name="twitter:card" content="summary">
    <meta name="twitter:title" content="<?php echo $default_page_title; ?>">
    <meta name="twitter:description" content="<?php echo $default_meta_desc; ?>">
    <?php if ($sitemap_file): ?><link rel="alternate" type="application/rss+xml" title="Sitemap" href="<?php echo htmlspecialchars($sitemap_file); ?>"><?php endif; ?>
    <?php if (!empty($baidu_verify_tag)): echo $baidu_verify_tag . "\n"; endif; ?>
    <?php if (!empty($bing_verify_tag)): echo $bing_verify_tag . "\n"; endif; ?>
    <?php if (!empty($google_verify_tag)): echo $google_verify_tag . "\n"; endif; ?>
    <?php if (!empty($sogou_verify_tag)): echo $sogou_verify_tag . "\n"; endif; ?>
    <script type="application/ld+json">
    {
        "@context": "https://schema.org",
        "@type": "WebSite",
        "name": "<?php echo htmlspecialchars($site['site_name']); ?>",
        "url": "<?php echo build_site_base_url($site); ?>/",
        "description": "<?php echo $default_meta_desc; ?>",
        "potentialAction": {
            "@type": "SearchAction",
            "target": "<?php echo build_site_base_url($site); ?>/article/{search_term_string}.html",
            "query-input": "required name=search_term_string"
        }
    }
    </script>
    <style>
        body { font-family: Arial, sans-serif; margin: 0; padding: 0; background: #f5f5f5; }
        .header { background: #333; color: #fff; padding: 20px; text-align: center; }
        .header h1 { margin: 0; font-size: 24px; }
        .nav { background: #444; padding: 10px; text-align: center; overflow-x: auto; white-space: nowrap; }
        .nav a { color: #fff; text-decoration: none; margin: 0 15px; display: inline-block; }
        .nav a.active { color: #1890ff; }
        .container { max-width: 1200px; margin: 20px auto; display: flex; gap: 20px; padding: 0 20px; }
        .main { flex: 2; min-width: 0; }
        .sidebar { flex: 1; min-width: 280px; max-width: 350px; }
        .ad-box { background: transparent; padding: 10px; margin-bottom: 20px; border-radius: 4px; text-align: center; }
        .ad-box img { max-width: 100%; height: auto; }
        .seo-content { background: transparent; padding: 25px; margin-bottom: 20px; border-radius: 4px; }
        .seo-content h1, .seo-content h2 { color: #333; margin-top: 0; }
        .seo-content p { line-height: 1.8; margin-bottom: 15px; }
        .seo-content img { max-width: 100%; height: auto; margin: 15px auto; display: block; border-radius: 4px; }
        .article-list-box { background: transparent; padding: 20px; border-radius: 4px; margin-bottom: 20px; }
        .article-list-box h3 { margin-top: 0; color: #333; border-bottom: 2px solid #1890ff; padding-bottom: 10px; }
        .article-list { list-style: none; padding: 0; margin: 0; }
        .article-list li { padding: 12px 0; border-bottom: 1px solid #eee; }
        .article-list li:last-child { border-bottom: none; }
        .article-list a { color: #333; text-decoration: none; font-size: 15px; display: block; }
        .article-list a:hover { color: #1890ff; }
        .article-list .date { color: #999; font-size: 12px; margin-top: 5px; }
        .footer { background: #333; color: #fff; padding: 30px 20px; margin-top: 40px; }
        .footer-content { max-width: 1200px; margin: 0 auto; display: flex; justify-content: space-between; flex-wrap: wrap; gap: 20px; }
        .footer-section { flex: 1; min-width: 250px; }
        .footer-section h4 { margin-top: 0; margin-bottom: 15px; color: #fff; }
        .footer-section p { margin: 5px 0; color: #ccc; font-size: 14px; word-break: break-all; }
        .footer-bottom { text-align: center; padding-top: 20px; border-top: 1px solid #555; margin-top: 20px; color: #999; font-size: 12px; }
        .footer-bottom a { color: #1890ff; text-decoration: none; }
        @media (max-width: 768px) {
            .header { padding: 15px 10px; }
            .header h1 { font-size: 18px; }
            .container { flex-direction: column; padding: 0 10px; gap: 15px; }
            .sidebar { max-width: 100%; order: -1; }
            .seo-content { padding: 15px; font-size: 14px; }
            .article-list-box { padding: 15px; }
            .footer { padding: 20px 15px; }
            .footer-section { min-width: 100%; }
        }
    </style>
</head>
<body>
    <div class="header">
        <h1><?php echo htmlspecialchars($site['site_name']); ?></h1>
    </div>
    <div class="nav">
        <a href="<?php echo site_url('home'); ?>" class="active">首页</a>
    </div>
    
    <?php if (!empty($top_ads)): ?>
    <div style="max-width:1200px;margin:20px auto;padding:0 20px;">
        <?php foreach ($top_ads as $ad): ?>
        <div class="ad-box">
            <a href="<?php echo htmlspecialchars($ad['link_url']); ?>" target="_blank">
                <img src="<?php echo htmlspecialchars($ad['image_url']); ?>" alt="广告" loading="lazy">
            </a>
        </div>
        <?php endforeach; ?>
    </div>
    <?php endif; ?>
    
    <div class="container">
        <div class="main">
            <?php if (!empty($seo_content)): ?>
            <div class="seo-content">
                <?php echo $seo_content; ?>
            </div>
            <?php endif; ?>
        </div>
        <div class="sidebar">
            <div class="article-list-box">
                <h3>最新文章</h3>
                <?php if (empty($articles)): ?>
                    <p style="color:#999;">暂无文章</p>
                <?php else: ?>
                    <ul class="article-list">
                        <?php foreach ($articles as $article): ?>
                        <li>
                            <a href="<?php echo site_url('article', ['id' => $article['id']]); ?>"><?php echo htmlspecialchars($article['title']); ?></a>
                            <div class="date"><?php echo date('Y-m-d', strtotime($article['created_at'])); ?></div>
                        </li>
                        <?php endforeach; ?>
                    </ul>
                <?php endif; ?>
            </div>
            
            <?php if (!empty($side_ads)): ?>
                <?php foreach ($side_ads as $ad): ?>
                <div class="ad-box">
                    <a href="<?php echo htmlspecialchars($ad['link_url']); ?>" target="_blank">
                        <img src="<?php echo htmlspecialchars($ad['image_url']); ?>" alt="广告">
                    </a>
                </div>
                <?php endforeach; ?>
            <?php endif; ?>
        </div>
    </div>
    
    <?php if (!empty($footer_ads)): ?>
    <div style="max-width:1200px;margin:20px auto;padding:0 20px;">
        <?php foreach ($footer_ads as $ad): ?>
        <div class="ad-box">
            <a href="<?php echo htmlspecialchars($ad['link_url']); ?>" target="_blank">
                <img src="<?php echo htmlspecialchars($ad['image_url']); ?>" alt="广告" loading="lazy">
            </a>
        </div>
        <?php endforeach; ?>
    </div>
    <?php endif; ?>
    
    <div class="footer">
        <div class="footer-content">
            <div class="footer-section">
                <h4>关于我们</h4>
                <p><?php echo htmlspecialchars($site['site_name']); ?></p>
                <p><?php echo htmlspecialchars($site['description']); ?></p>
            </div>
            <div class="footer-section">
                <h4>联系方式</h4>
                <?php if ($contact_phone): ?><p>电话：<?php echo htmlspecialchars($contact_phone); ?></p><?php endif; ?>
                <?php if ($contact_email): ?><p>邮箱：<?php echo htmlspecialchars($contact_email); ?></p><?php endif; ?>
                <?php if ($contact_address): ?><p>地址：<?php echo htmlspecialchars($contact_address); ?></p><?php endif; ?>
                <?php if ($contact_wechat): ?><p>微信：<?php echo htmlspecialchars($contact_wechat); ?></p><?php endif; ?>
            </div>
        </div>
        <div class="footer-bottom">
            <p>
                <?php echo htmlspecialchars($site['site_name']); ?> &copy; <?php echo date('Y'); ?> 
                | <?php echo $footer_code; ?>
                <?php if ($sitemap_file): ?>| <a href="<?php echo htmlspecialchars($sitemap_file); ?>" target="_blank">网站地图</a><?php endif; ?>
            </p>
            <p style="margin-top:10px;font-size:11px;">最后更新：<?php echo $last_update; ?></p>
        </div>
    </div>
    
    
</html>
    <?php
}

/**
 * 文章列表页（伪静态: /articles.html）
 */
function show_article_list($site, $site_template = null) {
    // 访客统计
    log_visitor($site['id'], 'home');
    
    $page_num = max(1, intval($_GET['page'] ?? 1));
    $per_page = 20;
    $offset = ($page_num - 1) * $per_page;
    
    $city_article_filter = get_current_city_article_filter($site);
    $city_count_filter = ($city_article_filter > 0) ? " AND city_id = {$city_article_filter}" : "";
    $total = db_get_one("SELECT COUNT(*) as cnt FROM " . table('articles') . " WHERE site_id = {$site['id']}{$city_count_filter}");
    $total_count = intval($total['cnt'] ?? 0);
    $articles = get_articles($site['id'], $per_page, $offset, $city_article_filter);
    
    // 构建文章列表HTML
    $list_html = '';
    if (!empty($articles)) {
        foreach ($articles as $article) {
            $list_html .= '<div class="article-item" style="margin-bottom:20px;padding:15px;background:transparent;border-radius:4px;">';
            $list_html .= '<h3 style="margin:0 0 8px;"><a href="' . site_url('article', ['id' => $article['id']]) . '" style="color:#333;text-decoration:none;">' . htmlspecialchars($article['title']) . '</a></h3>';
            $list_html .= '<p style="color:#666;margin:0 0 5px;">' . htmlspecialchars(mb_substr(strip_tags($article['content']), 0, 150)) . '...</p>';
            $date_str = !empty($article['created_at']) ? date('Y-m-d', strtotime($article['created_at'])) : '';
            $list_html .= '<span style="color:#999;font-size:12px;">' . $date_str . '</span>';
            $list_html .= '</div>';
        }
    } else {
        $list_html = '<p style="text-align:center;color:#999;padding:40px;">暂无文章</p>';
    }
    
    // 分页
    $total_pages = max(1, ceil($total_count / $per_page));
    $pagination = '';
    if ($total_pages > 1) {
        $pagination .= '<div class="pagination" style="text-align:center;padding:20px;">';
        if ($page_num > 1) {
            $pagination .= '<a href="' . site_url('list', ['page' => $page_num - 1]) . '" style="margin:0 5px;padding:5px 12px;border:1px solid #ddd;text-decoration:none;border-radius:3px;">上一页</a>';
        }
        for ($i = max(1, $page_num - 3); $i <= min($total_pages, $page_num + 3); $i++) {
            $style = $i == $page_num ? 'background:#1890ff;color:#fff;border-color:#1890ff;' : '';
            $pagination .= '<a href="' . site_url('list', ['page' => $i]) . '" style="margin:0 3px;padding:5px 12px;border:1px solid #ddd;text-decoration:none;border-radius:3px;' . $style . '">' . $i . '</a>';
        }
        if ($page_num < $total_pages) {
            $pagination .= '<a href="' . site_url('list', ['page' => $page_num + 1]) . '" style="margin:0 5px;padding:5px 12px;border:1px solid #ddd;text-decoration:none;border-radius:3px;">下一页</a>';
        }
        $pagination .= '</div>';
    }
    
    // 使用首页模板，替换内容区
    $data = build_home_template_data($site, $site_template);
    $data['page_title'] = htmlspecialchars($site['site_name']) . ' - 文章列表';
    $data['seo_content'] = $list_html . $pagination;
    $data['canonical_url'] = build_site_base_url($site) . '/articles.html';
    $data['breadcrumb'] = '<a href="' . site_url('home') . '">首页</a><span>&gt;</span><span>文章列表</span>';
    
    $template_html = get_template_html($site, $site_template, 'home');
        // 模板拆分方案：优先使用组件拼装
        $column_content = '';
        if (!empty($data['seo_content'])) {
            $column_content .= '<div class="column-content-box" style="max-width:1200px;margin:20px auto;padding:20px;">' . $data['seo_content'] . '</div>';
        }
        $assembled = assemble_component_page($site, $site_template, $data, 'page', $column_content);
        if ($assembled !== null) {
            $assembled = inject_marketing_into_html($assembled, $site['id']);
            echo $assembled;
            return;
        }
        // 回退到旧逻辑（没有拆分组件的站点）
    $html = render_site_template($template_html, $data, 'list');
    $html = inject_marketing_into_html($html, $site['id']);
    echo $html;
}

/**
 * 栏目页（伪静态: /about.html, /contact.html 等）
 */
function show_site_page($site, $site_template = null) {
    $slug = preg_replace('/[^a-z0-9_-]/', '', $_GET['slug'] ?? '');
    if (empty($slug)) {
        show_home($site, $site_template);
        return;
    }
    
    // 查找栏目页
    try {
        $page_data = db_get_row("SELECT * FROM " . table('site_pages') . " WHERE site_id = " . intval($site['id']) . " AND slug = '" . addslashes($slug) . "' LIMIT 1");
    } catch (Exception $e) {
        $page_data = null;
    }
    
    if (!$page_data) {
        show_404($site, $site_template);
        return;
    }
    
    // 使用首页模板，但标记为栏目页，渲染时会隐藏首页特有模块
    $page_content = strip_full_html_doc($page_data['content']);

    // 剥除 AI 在栏目正文中夹带的首页"门面"组件（hero/banner/carousel/cta/slideshow）。
    // Step4 prompt 要求只输出栏目正文，但 AI（尤其 DeepSeek）经常复制首页 hero 区块，
    // 导致栏目页顶部出现巨幅 banner、与栏目标题形成双 h1、内容"飘出窗口"。
    $page_content = strip_homepage_boilerplate_from_column($page_content);
    
    // 兜底：如果栏目内容为空或太短，生成默认内容
    if (strlen(trim(strip_tags($page_content))) < 30) {
        $page_content = '<h2>' . htmlspecialchars($page_data['title']) . '</h2>';
        $page_content .= '<p>欢迎来到' . htmlspecialchars($page_data['title']) . '页面。本栏目为您精选相关内容，希望能为您提供有价值的信息和参考。</p>';
        $page_content .= '<p>我们致力于为您提供最优质的服务和最新的资讯，如有任何问题，欢迎随时联系我们。</p>';
    }
    
    $data = build_home_template_data($site, $site_template);
    
    // 标记为栏目页（用于模板渲染时隐藏首页特有模块）
    $data['is_column_page'] = true;
    
    // 清空首页特有模块数据
    $data['banner'] = '';
    $data['card_grid'] = '';
    
    $data['page_title'] = htmlspecialchars($page_data['title']) . ' - ' . htmlspecialchars($site['site_name']);
    
    // 栏目页内容区域：栏目标题 + 栏目介绍 + 文章列表
    $page_section_html = '<section class="column-page">';
    $page_section_html .= '<h1 class="column-title" style="font-size:2em;margin-bottom:0.5em;">' . htmlspecialchars($page_data['title']) . '</h1>';
    $page_section_html .= '<div class="column-content" style="max-width:800px;line-height:1.8;margin:0 auto;">' . $page_content . '</div>';
    $page_section_html .= '</section>';
    $data['seo_content'] = $page_section_html; // 栏目内容替代首页SEO内容
    
    // 栏目页文章列表（显示该站点最新10篇文章，时间格式：Y-m-d H:i，外层包裹ul）
    $city_article_filter = get_current_city_article_filter($site);
    $page_articles = get_articles($site['id'], 10, 0, $city_article_filter);
    $article_list_html = '';
    if (!empty($page_articles)) {
        foreach ($page_articles as $article) {
            $article_list_html .= '<li>';
            $article_list_html .= '<a href="' . site_url('article', ['id' => $article['id']]) . '">' . htmlspecialchars($article['title']) . '</a>';
            $time_str = !empty($article['created_at']) ? date('Y-m-d H:i', strtotime($article['created_at'])) : '';
            $article_list_html .= '<div class="date">' . $time_str . '</div>';
            $article_list_html .= '</li>';
        }
    } else {
        $article_list_html = '<li><p style="color:#999;">暂无文章</p></li>';
    }
    // 外层包裹ul，确保HTML结构正确
    $data['article_list'] = '<ul class="article-list">' . $article_list_html . '</ul>';
    
    // 栏目页不显示热门文章（保持栏目页聚焦于栏目内容）
    $data['hot_articles'] = '';
    
    $data['canonical_url'] = build_site_base_url($site) . '/' . $slug . '.html';
    $data['breadcrumb'] = '<a href="' . site_url('home') . '">首页</a><span>&gt;</span><span>' . htmlspecialchars($page_data['title']) . '</span>';
    $data['meta_keywords'] = htmlspecialchars($page_data['title'] . ',' . ($site['keywords'] ?? ''));
    // 栏目描述：优先 Step2 生成的 summary，其次正文截词；统一清洗空白
    $column_desc = '';
    if (!empty($page_data['summary'])) {
        $column_desc = clean_meta_excerpt($page_data['summary'], 160);
    }
    if ($column_desc === '') {
        $column_desc = clean_meta_excerpt($page_content, 160);
    }
    $data['meta_description'] = $column_desc;

    // 栏目 og:image：从栏目正文取第一张图；没有则用首页 logo/默认图
    $column_og_image = '';
    if (!empty($page_content) && preg_match('/<img[^>]+src=["\']([^"\']+)["\']/i', $page_content, $cimg)) {
        $column_og_image = $cimg[1];
    }
    if ($column_og_image === '' && !empty($data['og_image'])) {
        $column_og_image = $data['og_image'];
    }

    // OG标签 - 使用栏目标题 + 清洗后的描述（补齐 og:site_name/og:locale/twitter:image，与首页对齐）
    $og_tags  = '<meta property="og:type" content="website">' . "\n"
        . '<meta property="og:title" content="' . htmlspecialchars($page_data['title'] . ' - ' . $site['site_name']) . '">' . "\n"
        . '<meta property="og:description" content="' . htmlspecialchars($column_desc) . '">' . "\n"
        . '<meta property="og:url" content="' . htmlspecialchars($data['canonical_url']) . '">' . "\n"
        . '<meta property="og:site_name" content="' . htmlspecialchars($site['site_name'] ?? '') . '">' . "\n"
        . '<meta property="og:locale" content="zh_CN">' . "\n"
        . '<meta name="twitter:card" content="summary">' . "\n"
        . '<meta name="twitter:title" content="' . htmlspecialchars($page_data['title'] . ' - ' . $site['site_name']) . '">' . "\n"
        . '<meta name="twitter:description" content="' . htmlspecialchars($column_desc) . '">';
    if ($column_og_image !== '') {
        $og_tags .= "\n" . '<meta property="og:image" content="' . htmlspecialchars($column_og_image) . '">' . "\n"
            . '<meta name="twitter:image" content="' . htmlspecialchars($column_og_image) . '">';
    }
    $data['og_tags'] = $og_tags;

    // 校验标签：保留 build_home_template_data() 已生成的 verify_tags（百度/Bing/Google/搜狗）
    // 不覆盖，让首页同款验证标签在栏目页也生效

    // Schema.org：在首页 WebSite/Breadcrumb/LocalBusiness 基础上追加 WebPage（而不是替换）
    $page_schema = [
        '@context' => 'https://schema.org',
        '@type' => 'WebPage',
        'name' => $page_data['title'],
        'description' => $column_desc,
        'url' => $data['canonical_url'],
        'isPartOf' => ['@type' => 'WebSite', 'name' => $site['site_name'], 'url' => build_site_base_url($site) . '/'],
        'breadcrumb' => ['@type' => 'BreadcrumbList', 'itemListElement' => [
            ['@type' => 'ListItem', 'position' => 1, 'name' => '首页', 'item' => build_site_base_url($site) . '/'],
            ['@type' => 'ListItem', 'position' => 2, 'name' => $page_data['title'], 'item' => $data['canonical_url']],
        ]],
    ];
    if ($column_og_image !== '') {
        $page_schema['image'] = $column_og_image;
    }
    // 解析已有首页 schema（build_home_template_data 已生成 WebSite + 城市分站 LocalBusiness + Breadcrumb）
    $existing_schema_array = [];
    if (!empty($data['schema_json'])) {
        // schema_json 是 <script>...</script> 包裹的 JSON；提取并解码
        $_schema_matches = [];
        if (preg_match_all('/<script[^>]*>([\s\S]*?)<\/script>/i', $data['schema_json'], $_schema_matches)) {
            foreach ($_schema_matches[1] as $_json_str) {
                $_decoded = json_decode(trim($_json_str), true);
                if ($_decoded) {
                    if (isset($_decoded['@type'])) {
                        $existing_schema_array[] = $_decoded;
                    } elseif (isset($_decoded[0])) {
                        foreach ($_decoded as $_sub) {
                            if (is_array($_sub) && isset($_sub['@type'])) $existing_schema_array[] = $_sub;
                        }
                    }
                }
            }
        }
    }
    // 追加 WebPage schema
    $existing_schema_array[] = $page_schema;
    $data['schema_json'] = '<script type="application/ld+json">' . "\n"
        . json_encode($existing_schema_array, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)
        . "\n" . '</script>';
    
    $template_html = get_template_html($site, $site_template, 'home');

    // 栏目 main 内部片段：面包屑 + 栏目标题/正文 + 最新文章
    $breadcrumb_html = '<div class="site-breadcrumb" style="max-width:1200px;margin:16px auto;padding:0 20px;font-size:14px;color:#666;">'
        . $data['breadcrumb'] . '</div>';
    $page_section_html = $breadcrumb_html;
    $page_section_html .= '<section class="column-page" style="max-width:1200px;margin:0 auto;padding:20px;">';
    $page_section_html .= '<h1 class="column-title" style="font-size:2em;margin:0 0 0.6em;">' . htmlspecialchars($page_data['title']) . '</h1>';
    $page_section_html .= '<div class="column-content" style="line-height:1.8;">' . $page_content . '</div>';
    if (!empty($article_list_html)) {
        $page_section_html .= '<section class="column-article-list" style="margin-top:32px;">';
        $page_section_html .= '<h2 style="font-size:1.3em;margin:0 0 16px;padding-bottom:10px;border-bottom:2px solid #1890ff;display:inline-block;">最新文章</h2>';
        // $article_list_html 已经是 <li> 集合；这里包一次 ul 即可（不再重复包裹）
        $page_section_html .= '<ul class="article-list" style="list-style:none;padding:0;margin:0;">' . $article_list_html . '</ul>';
        $page_section_html .= '</section>';
    }
    $page_section_html .= '</section>';

    // 渲染层选择：
    // - 管线 v3 站点（template_type=single 且有内容区壳）：走主内容区置换，页头/页脚/导航/CSS 与首页 100% 同源
    // - 其它旧 multi 模板：保留 assemble_component_page -> render_site_template 旧路径
    $is_pipeline_shell = ($site_template && ($site_template['template_type'] ?? '') === 'single'
        && !empty($site_template['content_header']) && !empty($site_template['content_footer']));
    if ($is_pipeline_shell) {
        $data['__canonical'] = $data['canonical_url'];
        $data['__raw_css'] = $site_template['content_1'] ?? '';
        // TDK 占位符（inject_tdk_into_html 会从 data 读这些 key）
        $data['{PAGE_TITLE}'] = htmlspecialchars($page_data['title'] . ' - ' . $site['site_name']);
        $data['{META_TITLE}'] = $data['{PAGE_TITLE}'];
        $data['{META_DESCRIPTION}'] = htmlspecialchars($column_desc);
        $data['{META_KEYWORDS}'] = htmlspecialchars($page_data['title'] . ',' . ($site['keywords'] ?? ''));
        $html = render_pipeline_shell_page($site, $site_template, $data, 'page', $page_section_html);
        if ($html === null) {
            // 理论上不会走到这里（已校验壳完整），兜底最小骨架
            $html = render_minimal_shell_page($site, $data, $page_section_html);
        }
    } else {
        // 旧 multi 模板路径：保留原 assemble_component_page -> render_site_template 回退链
        $column_content = '';
        if (!empty($data['seo_content'])) $column_content .= $data['seo_content'];
        if (!empty($data['article_list'])) {
            $column_content .= '<div class="column-article-list-box" style="margin-top:20px;">';
            $column_content .= '<h3 style="margin-top:0;border-bottom:2px solid #1890ff;padding-bottom:10px;">最新文章</h3>';
            $column_content .= '<ul class="article-list" style="list-style:none;padding:0;margin:0;">' . $data['article_list'] . '</ul>';
            $column_content .= '</div>';
        }
        $assembled = assemble_component_page($site, $site_template, $data, 'page', $column_content);
        if ($assembled !== null) {
            $html = $assembled;
        } else {
            $html = render_site_template($template_html, $data, 'page');
        }
    }
    $html = inject_marketing_into_html($html, $site['id']);
    echo $html;
}

/**
 * 站点地图XML（伪静态: /sitemap.xml）
 */
function show_sitemap($site) {
    $city_article_filter = get_current_city_article_filter($site);
    $articles = get_articles($site['id'], 1000, 0, $city_article_filter);
    $site_pages = [];
    try {
        $site_pages = db_get_all("SELECT slug, title FROM " . table('site_pages') . " WHERE site_id = " . intval($site['id']) . " ORDER BY sort_order ASC, id ASC");
    } catch (Exception $e) {}
    
    header('Content-Type: application/xml; charset=utf-8');
    echo '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
    echo '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . "\n";
    
    // 首页
    echo '  <url>' . "\n";
    echo '    <loc>' . build_site_base_url($site) . '/</loc>' . "\n";
    echo '    <changefreq>daily</changefreq>' . "\n";
    echo '    <priority>1.0</priority>' . "\n";
    echo '  </url>' . "\n";
    
    // 文章列表页
    echo '  <url>' . "\n";
    echo '    <loc>' . build_site_base_url($site) . '/articles.html</loc>' . "\n";
    echo '    <changefreq>daily</changefreq>' . "\n";
    echo '    <priority>0.8</priority>' . "\n";
    echo '  </url>' . "\n";
    
    // 栏目页
    if (!empty($site_pages)) {
        foreach ($site_pages as $sp) {
            echo '  <url>' . "\n";
            echo '    <loc>' . build_site_base_url($site) . '/' . htmlspecialchars($sp['slug']) . '.html</loc>' . "\n";
            echo '    <changefreq>weekly</changefreq>' . "\n";
            echo '    <priority>0.7</priority>' . "\n";
            echo '  </url>' . "\n";
        }
    }
    
    // 文章页（使用站点URL格式配置）
    if (!empty($articles)) {
        foreach ($articles as $article) {
            echo '  <url>' . "\n";
            $lastmod = !empty($article['created_at']) ? '<lastmod>' . date('Y-m-d', strtotime($article['created_at'])) . '</lastmod>' . "\n" : '';
            $article_url = site_url('article', ['id' => $article['id'], 'site_id' => $site['id'], 'created_at' => $article['created_at'] ?? '']);
            echo '    <loc>' . build_site_base_url($site) . htmlspecialchars($article_url) . '</loc>' . "\n";
            echo '    ' . $lastmod;
            echo '    <changefreq>monthly</changefreq>' . "\n";
            echo '    <priority>0.6</priority>' . "\n";
            echo '  </url>' . "\n";
        }
    }
    
    // 标签聚合页（基于站点关键词）
    try {
        $tag_keywords = db_get_all("SELECT DISTINCT k.keyword FROM " . table('keywords') . " k JOIN " . table('keyword_sites') . " ks ON k.id=ks.keyword_id WHERE ks.site_id=" . intval($site['id']) . " ORDER BY k.id DESC LIMIT 50");
        if (!empty($tag_keywords)) {
            foreach ($tag_keywords as $tk) {
                echo '  <url>' . "\n";
                echo '    <loc>' . build_site_base_url($site) . '/tag/' . rawurlencode($tk['keyword']) . '.html</loc>' . "\n";
                echo '    <changefreq>weekly</changefreq>' . "\n";
                echo '    <priority>0.5</priority>' . "\n";
                echo '  </url>' . "\n";
            }
        }
    } catch (Exception $e) {}
    
    echo '</urlset>';
}

// ==================== robots.txt ====================
function show_robots($site) {
    header('Content-Type: text/plain; charset=utf-8');
    $domain = htmlspecialchars($site['domain']);
    echo "User-agent: *\n";
    echo "Allow: /\n";
    echo "Disallow: /admin/\n";
    echo "Disallow: /pipeline_execute.php\n";
    echo "Disallow: /pipeline_progress.php\n";
    echo "Disallow: /batch_worker.php\n";
    echo "Disallow: /auto_update.php\n";
    echo "Disallow: /install.php\n";
    echo "\n";
    echo "Sitemap: " . build_site_base_url($site) . "/sitemap.xml\n";
}

// ==================== llms.txt (GEO: AI 搜索引擎友好概览) ====================
function show_llms_txt($site) {
    header('Content-Type: text/plain; charset=utf-8');
    $site_name = $site['site_name'] ?? '';
    $domain = $site['domain'] ?? '';
    $description = $site['description'] ?? '';
    $city = $site['city'] ?? '';
    $industry = $site['industry'] ?? '';
    
    echo "# {$site_name}\n\n";
    
    if (!empty($description)) {
        echo "> {$description}\n\n";
    }
    
    echo "## About\n\n";
    if (!empty($industry)) echo "- Industry: {$industry}\n";
    if (!empty($city)) echo "- Location: {$city}\n";
    echo "- Website: https://{$domain}\n";
    // GEO: 暴露官方联系方式，便于 AI 搜索引擎抓取实体信息（站点级优先，空则回退全局）
    $llms_phone   = trim((string)get_setting('contact_phone', $site['id'])) ?: trim((string)get_setting('contact_phone', 0));
    $llms_email   = trim((string)get_setting('contact_email', $site['id'])) ?: trim((string)get_setting('contact_email', 0));
    $llms_address = trim((string)get_setting('contact_address', $site['id'])) ?: trim((string)get_setting('contact_address', 0));
    if ($llms_phone !== '' && preg_match('/\d{3,}/', $llms_phone)) echo "- Phone: {$llms_phone}\n";
    if ($llms_email !== '' && strpos($llms_email, '@') !== false) echo "- Email: {$llms_email}\n";
    if ($llms_address !== '') echo "- Address: {$llms_address}\n";
    echo "\n";
    
    // 最新文章（最多 10 篇）
    try {
        $articles = db_get_all("SELECT id, title, keyword, created_at FROM " . table('articles') . " WHERE site_id=" . intval($site['id']) . " AND status=1 ORDER BY id DESC LIMIT 10");
        if (!empty($articles)) {
            echo "## Latest Articles\n\n";
            foreach ($articles as $article) {
                $title = $article['title'];
                $url = "https://{$domain}/article/{$article['id']}.html";
                echo "- [{$title}]({$url})\n";
            }
            echo "\n";
        }
    } catch (Exception $e) {}
    
    // 关键词/主题
    try {
        $keywords = db_get_all("SELECT keyword FROM " . table('keywords') . " WHERE site_id=" . intval($site['id']) . " ORDER BY id DESC LIMIT 20");
        if (!empty($keywords)) {
            echo "## Topics\n\n";
            $kw_list = array_map(function($k) { return $k['keyword']; }, $keywords);
            echo implode(', ', $kw_list) . "\n\n";
        }
    } catch (Exception $e) {}
    
    echo "## Optional\n\n";
    echo "- Full content: https://{$domain}/llms-full.txt\n";
    echo "- Sitemap: " . build_site_base_url($site) . "/sitemap.xml\n";
}

// ==================== llms-full.txt (GEO: AI 搜索引擎完整内容) ====================
function show_llms_full_txt($site) {
    header('Content-Type: text/plain; charset=utf-8');
    $site_name = $site['site_name'] ?? '';
    $domain = $site['domain'] ?? '';
    $description = $site['description'] ?? '';
    $city = $site['city'] ?? '';
    $industry = $site['industry'] ?? '';
    
    echo "# {$site_name} - Full Content\n\n";
    
    if (!empty($description)) {
        echo "> {$description}\n\n";
    }
    
    echo "## About\n\n";
    if (!empty($industry)) echo "- Industry: {$industry}\n";
    if (!empty($city)) echo "- Location: {$city}\n";
    echo "- Website: https://{$domain}\n";
    // GEO: 官方联系方式（与 llms.txt 对齐）
    $full_phone   = trim((string)get_setting('contact_phone', $site['id'])) ?: trim((string)get_setting('contact_phone', 0));
    $full_email   = trim((string)get_setting('contact_email', $site['id'])) ?: trim((string)get_setting('contact_email', 0));
    $full_address = trim((string)get_setting('contact_address', $site['id'])) ?: trim((string)get_setting('contact_address', 0));
    if ($full_phone !== '' && preg_match('/\d{3,}/', $full_phone)) echo "- Phone: {$full_phone}\n";
    if ($full_email !== '' && strpos($full_email, '@') !== false) echo "- Email: {$full_email}\n";
    if ($full_address !== '') echo "- Address: {$full_address}\n";
    echo "\n";
    
    // 所有文章完整内容（最多 50 篇）
    try {
        $articles = db_get_all("SELECT id, title, keyword, content, summary, faq_json, created_at, updated_at FROM " . table('articles') . " WHERE site_id=" . intval($site['id']) . " AND status=1 ORDER BY id DESC LIMIT 50");
        if (!empty($articles)) {
            echo "## Articles\n\n";
            foreach ($articles as $article) {
                echo "### {$article['title']}\n\n";
                echo "- URL: https://{$domain}/article/{$article['id']}.html\n";
                if (!empty($article['keyword'])) echo "- Topic: {$article['keyword']}\n";
                echo "- Published: {$article['created_at']}\n";
                if (!empty($article['updated_at'])) echo "- Updated: {$article['updated_at']}\n";
                echo "\n";
                
                // 摘要
                if (!empty($article['summary'])) {
                    echo "**Summary:** {$article['summary']}\n\n";
                }
                
                // 正文（纯文本，去除 HTML 标签）
                $plain_content = strip_tags($article['content']);
                $plain_content = html_entity_decode($plain_content, ENT_QUOTES, 'UTF-8');
                // 限制长度避免文件过大
                if (mb_strlen($plain_content) > 2000) {
                    $plain_content = mb_substr($plain_content, 0, 2000) . '...';
                }
                echo "{$plain_content}\n\n";
                
                // FAQ
                if (!empty($article['faq_json'])) {
                    $faq_data = json_decode($article['faq_json'], true);
                    if (!empty($faq_data)) {
                        echo "**FAQ:**\n\n";
                        foreach ($faq_data as $faq) {
                            if (isset($faq['question']) && isset($faq['answer'])) {
                                echo "Q: {$faq['question']}\n";
                                echo "A: {$faq['answer']}\n\n";
                            }
                        }
                    }
                }
                
                echo "---\n\n";
            }
        }
    } catch (Exception $e) {}
    
    echo "## Metadata\n\n";
    echo "- Sitemap: " . build_site_base_url($site) . "/sitemap.xml\n";
    echo "- Overview: https://{$domain}/llms.txt\n";
}

// ==================== 标签聚合页 ====================
function show_tag($site, $site_template = null) {
    // 记录访客统计
    log_visitor($site['id']);
    
    // v3.0: 检测管线版本，新版本不再fallback到旧字段
    $pipeline_version = $site['pipeline_version'] ?? '';
    $is_v3 = version_compare($pipeline_version, '3.0', '>=');
    
    $keyword = isset($_GET['keyword']) ? trim($_GET['keyword']) : '';
    if (empty($keyword)) {
        show_404($site, $site_template);
        return;
    }
    
    // 根据关键词搜索文章
    $esc_kw = db_escape($keyword);
    $page_num = max(1, intval($_GET['page'] ?? 1));
    $per_page = 20;
    $offset = ($page_num - 1) * $per_page;
    
    $articles = db_get_all("SELECT a.* FROM " . table('articles') . " a LEFT JOIN " . table('keywords') . " k ON a.keyword_id = k.id WHERE a.site_id=" . intval($site['id']) . " AND a.status=1 AND (a.title LIKE '%{$esc_kw}%' OR a.content LIKE '%{$esc_kw}%' OR k.keyword LIKE '%{$esc_kw}%') ORDER BY a.id DESC LIMIT {$per_page} OFFSET {$offset}");
    $total_count = db_get_one("SELECT COUNT(*) as cnt FROM " . table('articles') . " a LEFT JOIN " . table('keywords') . " k ON a.keyword_id = k.id WHERE a.site_id=" . intval($site['id']) . " AND a.status=1 AND (a.title LIKE '%{$esc_kw}%' OR a.content LIKE '%{$esc_kw}%' OR k.keyword LIKE '%{$esc_kw}%')");
    $total = $total_count ? intval($total_count['cnt']) : 0;
    
    // 使用首页模板渲染，但替换主内容区域为标签文章列表（v3.0+ 不再fallback到content_4）
    $template_html = null;
    if ($site_template) {
        if (!empty($site_template['content_home'])) {
            $template_html = $site_template['content_home'];
        } elseif (!$is_v3 && !empty($site_template['content_4'])) {
            $template_html = $site_template['content_4'];
        }
    }
    
    // 构建标签页数据
    $data = build_home_template_data($site, $site_template);
    
    // 覆盖页面标题和meta
    $data['page_title'] = htmlspecialchars($keyword) . ' - ' . htmlspecialchars($site['site_name']);
    $data['meta_description'] = htmlspecialchars('关于"' . $keyword . '"的相关内容 - ' . $site['site_name']);
    $data['meta_keywords'] = htmlspecialchars($keyword . ',' . ($site['keywords'] ?? ''));
    $data['canonical_url'] = build_site_base_url($site) . '/tag/' . urlencode($keyword) . '.html';
    $data['breadcrumb'] = '<a href="' . site_url('home') . '">首页</a> &gt; <span>' . htmlspecialchars($keyword) . '</span>';
    
    // OG标签
    $data['og_tags'] = '<meta property="og:type" content="website">' . "\n"
        . '<meta property="og:title" content="' . htmlspecialchars($keyword . ' - ' . $site['site_name']) . '">' . "\n"
        . '<meta property="og:description" content="' . htmlspecialchars('关于"' . $keyword . '"的相关内容') . '">' . "\n"
        . '<meta property="og:url" content="' . build_site_base_url($site) . '/tag/' . urlencode($keyword) . '.html">' . "\n"
        . '<meta name="twitter:card" content="summary">' . "\n"
        . '<meta name="twitter:title" content="' . htmlspecialchars($keyword . ' - ' . $site['site_name']) . '">';
    
    // Schema.org - CollectionPage
    $data['schema_json'] = '<script type="application/ld+json">' . "\n" . json_encode([
        '@context' => 'https://schema.org',
        '@type' => 'CollectionPage',
        'name' => $keyword,
        'description' => '关于"' . $keyword . '"的相关内容',
        'url' => build_site_base_url($site) . '/tag/' . urlencode($keyword) . '.html',
        'isPartOf' => ['@type' => 'WebSite', 'name' => $site['site_name'], 'url' => build_site_base_url($site) . '/'],
    ], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n" . '</script>';
    
    // 构建标签文章列表内容（替代首页SEO内容）
    $tag_content = '<div class="tag-page">';
    $tag_content .= '<h1>' . htmlspecialchars($keyword) . '</h1>';
    $tag_content .= '<p class="tag-desc">以下是关于"' . htmlspecialchars($keyword) . '"的相关内容，共 ' . $total . ' 篇。</p>';
    
    if (!empty($articles)) {
        $tag_content .= '<ul class="tag-article-list">';
        foreach ($articles as $article) {
            $tag_content .= '<li>';
            $tag_content .= '<a href="' . site_url('article', ['id' => $article['id']]) . '">' . htmlspecialchars($article['title']) . '</a>';
            $tag_content .= '<span class="date">' . date('Y-m-d', strtotime($article['created_at'])) . '</span>';
            if (!empty($article['description'])) {
                $tag_content .= '<p class="snippet">' . htmlspecialchars(mb_substr(strip_tags($article['description']), 0, 120)) . '</p>';
            }
            $tag_content .= '</li>';
        }
        $tag_content .= '</ul>';
        
        // 分页
        if ($total > $per_page) {
            $total_pages = ceil($total / $per_page);
            $tag_content .= '<div class="tag-pagination">';
            if ($page_num > 1) {
                $tag_content .= '<a href="/tag/' . urlencode($keyword) . '.html?page=' . ($page_num - 1) . '" class="prev">&laquo; 上一页</a>';
            }
            $tag_content .= ' <span>第 ' . $page_num . ' / ' . $total_pages . ' 页</span> ';
            if ($page_num < $total_pages) {
                $tag_content .= '<a href="/tag/' . urlencode($keyword) . '.html?page=' . ($page_num + 1) . '" class="next">下一页 &raquo;</a>';
            }
            $tag_content .= '</div>';
        }
    } else {
        $tag_content .= '<p class="no-articles">暂无关于"' . htmlspecialchars($keyword) . '"的相关内容。</p>';
        // 推荐其他文章
        $city_article_filter = get_current_city_article_filter($site);
        $latest = get_articles($site['id'], 5, 0, $city_article_filter);
        if (!empty($latest)) {
            $tag_content .= '<div class="tag-recommend"><h2>推荐阅读</h2><ul>';
            foreach ($latest as $a) {
                $tag_content .= '<li><a href="' . site_url('article', ['id' => $a['id']]) . '">' . htmlspecialchars($a['title']) . '</a></li>';
            }
            $tag_content .= '</ul></div>';
        }
    }
    $tag_content .= '</div>';
    
    // 用标签内容替换SEO内容和文章列表
    $data['seo_content'] = $tag_content;
    $data['article_list'] = ''; // 标签页不需要额外的文章列表
    
    if ($template_html) {
        // 模板拆分方案：优先使用组件拼装
        $tag_content = '';
        if (!empty($data['seo_content'])) {
            $tag_content .= '<div class="tag-content-box" style="max-width:1200px;margin:20px auto;padding:20px;">' . $data['seo_content'] . '</div>';
        }
        if (!empty($data['article_list'])) {
            $tag_content .= '<div class="tag-article-list-box" style="max-width:1200px;margin:20px auto;padding:20px;">';
            $tag_content .= '<h3 style="margin-top:0;border-bottom:2px solid #1890ff;padding-bottom:10px;">' . htmlspecialchars($data['page_title'] ?? '标签文章') . '</h3>';
            $tag_content .= '<ul class="article-list" style="list-style:none;padding:0;margin:0;">' . $data['article_list'] . '</ul>';
            $tag_content .= '</div>';
        }
        $assembled = assemble_component_page($site, $site_template, $data, 'tag', $tag_content);
        if ($assembled !== null) {
            $assembled = inject_marketing_into_html($assembled, $site['id']);
            echo $assembled;
            return;
        }
        // 回退到旧逻辑（没有拆分组件的站点）
        $html = render_site_template($template_html, $data, 'tag');
        $html = inject_marketing_into_html($html, $site['id']);
        echo $html;
        return;
    }
    
    // 没有模板时使用简易HTML
    echo '<!DOCTYPE html><html><head><meta charset="UTF-8">';
    echo '<title>' . $data['page_title'] . '</title>';
    echo '<meta name="description" content="' . $data['meta_description'] . '">';
    echo '<link rel="canonical" href="' . $data['canonical_url'] . '">';
    echo $data['og_tags'];
    echo $data['schema_json'];
    echo '<style>body{font-family:Arial,sans-serif;max-width:800px;margin:0 auto;padding:20px}h1{color:#333}.tag-article-list{list-style:none;padding:0}.tag-article-list li{padding:12px 0;border-bottom:1px solid #eee}.tag-article-list a{color:#1890ff;text-decoration:none;font-size:16px}.tag-article-list .date{color:#999;font-size:13px;margin-left:10px}.tag-article-list .snippet{color:#666;font-size:14px;margin:5px 0 0}.tag-pagination{margin:20px 0;text-align:center}.tag-pagination a{color:#1890ff;text-decoration:none;margin:0 10px}.tag-recommend{margin-top:30px}.no-articles{color:#999}</style>';
    echo '</head><body>';
    echo '<div class="breadcrumb">' . $data['breadcrumb'] . '</div>';
    echo $tag_content;
    echo '</body></html>';
}

/**
 * 获取模板HTML内容
 */
function get_template_html($site, $site_template, $type = 'home') {
    if ($site_template) {
        if ($type === 'article' && !empty($site_template['content_article'])) {
            return $site_template['content_article'];
        }
        if (!empty($site_template['content_home'])) {
            return $site_template['content_home'];
        }
        // v3.0: 新管线站点不再 fallback 到旧字段 content_4/content_3/content_2
        // 旧字段格式不同，占位符体系不兼容，会导致渲染异常
        $pipeline_version = $site['pipeline_version'] ?? '';
        if (version_compare($pipeline_version, '3.0', '>=')) {
            return ''; // v3.0+ 站点：没有新字段就返回空，使用默认HTML
        }
        // 旧版站点保持原有 fallback 链
        if ($type === 'article' && !empty($site_template['content_4'])) {
            return $site_template['content_4'];
        }
        if (!empty($site_template['content_3'])) {
            return $site_template['content_3'];
        }
    }
    return ''; // 返回空表示使用默认HTML
}

// ==================== 文章详情页 ====================
function show_article($site, $site_template = null) {
    // 记录访客统计
    if (!empty($site['id'])) {
        log_visitor($site['id'], 'article');
    }
    
    $id = isset($_GET['id']) ? intval($_GET['id']) : 0;
    $article = get_article($id);
    
    if (!$article || $article['site_id'] != $site['id']) {
        show_404($site, $site_template);
    }
    
    // 如果有自定义模板，使用模板渲染（仅用content_article，不fallback到content_4/3）
    // content_4是旧版整合模板(含首页内容)，会导致文章页出现首页底部模版
    $template_html = null;
    if ($site_template && !empty($site_template['content_article'])) {
        $template_html = $site_template['content_article'];
        // v4.1防御：如果content_article中存储的是原始JSON，自动提取html字段
        $trimmed_art = ltrim($template_html);
        if (strlen($trimmed_art) > 0 && $trimmed_art[0] === '{' && stripos($trimmed_art, '"html"') !== false) {
            $extracted_art = null;
            if (function_exists('extract_json_from_ai')) {
                $parsed_art = extract_json_from_ai($template_html);
                if ($parsed_art && isset($parsed_art['html'])) {
                    $extracted_art = $parsed_art['html'];
                }
            }
            if (empty($extracted_art) && function_exists('extract_field_from_raw_json')) {
                $extracted_art = extract_field_from_raw_json($template_html, 'html');
            }
            if (!empty($extracted_art)) {
                $template_html = $extracted_art;
                error_log('[v4.1] content_article包含原始JSON，已自动提取html字段');
            }
        }
    }
    
    if ($template_html) {
        // 防御性清理：如果content_article中在DOCTYPE之前有额外的HTML内容（旧版auto_fix_template注入的首页框架），
        // 移除DOCTYPE之前的所有内容
        $doctype_pos = stripos($template_html, '<!doctype');
        if ($doctype_pos > 0) {
            $template_html = substr($template_html, $doctype_pos);
        }
        // 如果有多个DOCTYPE（即多个完整HTML文档拼接），只保留最后一个
        $doc_count = substr_count(strtolower($template_html), '<!doctype');
        if ($doc_count > 1) {
            $last_doctype_pos = strripos($template_html, '<!doctype');
            $template_html = substr($template_html, $last_doctype_pos);
        }

        // CSS智能合并：移除模板中的所有<style>块，由inject_tdk_into_html()统一注入CSS
        if (stripos($template_html, '<style') !== false) {
            $template_html = preg_replace('/<style[^>]*>.*?<\/style>/is', '', $template_html);
        }

        $data = build_article_template_data($site, $article, $site_template);

        // 文章 main 内部片段（仅正文，不含整页框架）
        $article_inner = '<article class="article-detail" style="max-width:900px;margin:0 auto;padding:20px;">';
        if (!empty($data['breadcrumb'])) {
            $article_inner .= '<div class="article-breadcrumb" style="font-size:14px;color:#666;margin-bottom:16px;">' . $data['breadcrumb'] . '</div>';
        }
        $article_inner .= '<h1 class="article-title" style="font-size:1.8em;margin:0 0 10px;line-height:1.4;">' . htmlspecialchars($data['article_title'] ?? '') . '</h1>';
        $article_inner .= '<div class="article-meta" style="color:#888;font-size:14px;margin-bottom:24px;padding-bottom:12px;border-bottom:1px solid #eee;">' . htmlspecialchars($data['article_date'] ?? '') . '</div>';
        // 文章内容广告位（顶部）
        if (!empty($data['ad_article'])) $article_inner .= '<div class="ad-article-top" style="margin:0 0 20px;">' . $data['ad_article'] . '</div>';
        $article_inner .= '<div class="article-content" style="line-height:1.8;font-size:16px;">' . ($data['article_content'] ?? '') . '</div>';
        // 文章内容广告位（底部）
        if (!empty($data['ad_below_article'])) $article_inner .= '<div class="ad-below-article" style="margin:24px 0;">' . $data['ad_below_article'] . '</div>';
        if (!empty($data['related_articles'])) {
            $article_inner .= '<div class="related-articles" style="margin-top:32px;padding-top:24px;border-top:1px solid #eee;">';
            $article_inner .= '<h2 style="font-size:1.3em;margin:0 0 16px;">相关文章</h2>' . $data['related_articles'] . '</div>';
        }
        if (!empty($data['hot_articles'])) {
            $article_inner .= '<div class="hot-articles" style="margin-top:24px;">';
            $article_inner .= '<h2 style="font-size:1.3em;margin:0 0 16px;">热门文章</h2>' . $data['hot_articles'] . '</div>';
        }
        if (!empty($data['contact_info'])) {
            $article_inner .= '<div class="article-contact" style="margin-top:32px;padding:20px;background:#f8f9fa;border-radius:8px;">' . $data['contact_info'] . '</div>';
        }
        $article_inner .= '</article>';

        // 管线 v3：有完整主内容壳，走主内容区置换
        $is_pipeline_shell = ($site_template && ($site_template['template_type'] ?? '') === 'single'
            && !empty($site_template['content_header']) && !empty($site_template['content_footer']));
        if ($is_pipeline_shell) {
            $data['__canonical'] = build_site_base_url($site) . '/article/' . $id . '.html';
            $data['__raw_css'] = $site_template['content_1'] ?? '';
            // TDK 占位符
            $art_title = !empty($article['title']) ? $article['title'] : ($data['article_title'] ?? '');
            $art_desc = clean_meta_excerpt(
                !empty($article['meta_description']) ? $article['meta_description'] :
                (!empty($article['summary']) ? $article['summary'] : ($article['content'] ?? '')),
                200
            );
            $data['{PAGE_TITLE}'] = htmlspecialchars($art_title . ' - ' . $site['site_name']);
            $data['{META_TITLE}'] = $data['{PAGE_TITLE}'];
            $data['{META_DESCRIPTION}'] = htmlspecialchars($art_desc);
            $data['{META_KEYWORDS}'] = htmlspecialchars($article['keyword'] ?? ($site['keywords'] ?? ''));
            $html = render_pipeline_shell_page($site, $site_template, $data, 'article', $article_inner);
            if ($html === null) {
                $html = render_minimal_shell_page($site, $data, $article_inner);
            }
        } else {
            $assembled = assemble_component_page($site, $site_template, $data, 'article', $article_inner);
            if ($assembled !== null) {
                $html = $assembled;
            } else {
                $html = render_site_template($template_html, $data, 'article');
            }
        }
        $html = inject_marketing_into_html($html, $site['id']);
        echo $html;
        return;
    }
    
    // 使用默认HTML
    $top_ads = get_ads('top', $site['id']);
    $content_ads = get_ads('content', $site['id']);
    $side_ads = get_ads('side', $site['id']);
    $footer_ads = get_ads('footer', $site['id']);
    
    $contact_phone = get_setting('contact_phone', $site['id']);
    $contact_email = get_setting('contact_email', $site['id']);
    $contact_address = get_setting('contact_address', $site['id']);
    $contact_wechat = get_setting('contact_wechat', $site['id']);
    $footer_code = get_setting('footer_code', $site['id']);
    $sitemap_file = get_setting('sitemap_file', $site['id']);
    
    $hot_articles = get_hot_articles($site['id'], 10);
    $related_articles = get_related_articles($site['id'], $id, 5);
    
    $baidu_verify_tag = get_setting('baidu_verify_tag', $site['id']);
    $bing_verify_tag = get_setting('bing_verify_tag', $site['id']);
    $google_verify_tag = get_setting('google_verify_tag', $site['id']);
    $sogou_verify_tag = get_setting('sogou_verify_tag', $site['id']);
    ?>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title><?php echo htmlspecialchars($article['title']); ?><?php if (!empty($article['keyword'])): ?> | <?php echo htmlspecialchars($article['keyword']); ?><?php endif; ?> | <?php echo htmlspecialchars($site['site_name']); ?></title>
    <?php $_art_desc = clean_meta_excerpt(!empty($article['meta_description']) ? $article['meta_description'] : (!empty($article['summary']) ? $article['summary'] : $article['content']), 200); ?>
    <meta name="description" content="<?php echo htmlspecialchars($_art_desc); ?>">
    <link rel="canonical" href="<?php echo build_site_base_url($site); ?>/article/<?php echo $id; ?>.html">
    <link rel="alternate" type="application/rss+xml" title="Sitemap" href="<?php echo build_site_base_url($site); ?>/sitemap.xml">
    <meta property="og:type" content="article">
    <meta property="og:title" content="<?php echo htmlspecialchars($article['title']); ?>">
    <meta property="og:description" content="<?php echo htmlspecialchars($_art_desc); ?>">
    <meta property="og:url" content="<?php echo build_site_base_url($site); ?>/article/<?php echo $id; ?>.html">
    <meta property="og:site_name" content="<?php echo htmlspecialchars($site['site_name']); ?>">
    <meta property="og:locale" content="zh_CN">
    <meta property="article:published_time" content="<?php echo $article['created_at']; ?>">
    <?php if (!empty($article['updated_at'])): ?>
    <meta property="article:modified_time" content="<?php echo $article['updated_at']; ?>">
    <?php endif; ?>
    <meta name="twitter:card" content="summary">
    <meta name="twitter:title" content="<?php echo htmlspecialchars($article['title']); ?>">
    <meta name="twitter:description" content="<?php echo htmlspecialchars(mb_substr(strip_tags($article['content']), 0, 200)); ?>">
    <?php if (!empty($baidu_verify_tag)): echo $baidu_verify_tag . "\n"; endif; ?>
    <?php if (!empty($bing_verify_tag)): echo $bing_verify_tag . "\n"; endif; ?>
    <?php if (!empty($google_verify_tag)): echo $google_verify_tag . "\n"; endif; ?>
    <?php if (!empty($sogou_verify_tag)): echo $sogou_verify_tag . "\n"; endif; ?>
    <script type="application/ld+json">
    {
        "@context": "https://schema.org",
        "@type": "Article",
        "headline": "<?php echo htmlspecialchars($article['title']); ?>",
        "description": "<?php echo htmlspecialchars(mb_substr(strip_tags($article['content']), 0, 200)); ?>",
        "author": {
            "@type": "Organization",
            "name": "<?php echo htmlspecialchars($site['site_name']); ?>"
        },
        "publisher": {
            "@type": "Organization",
            "name": "<?php echo htmlspecialchars($site['site_name']); ?>",
            "url": "<?php echo build_site_base_url($site); ?>/"
        },
        "datePublished": "<?php echo $article['created_at']; ?>",
        "dateModified": "<?php echo $article['created_at']; ?>",
        "mainEntityOfPage": {
            "@type": "WebPage",
            "@id": "<?php echo build_site_base_url($site); ?>/article/<?php echo $id; ?>.html"
        }
    }
    </script>
    <style>
        body { font-family: Arial, sans-serif; margin: 0; padding: 0; background: #f5f5f5; }
        .header { background: #333; color: #fff; padding: 20px; text-align: center; }
        .header h1 { margin: 0; font-size: 24px; }
        .nav { background: #444; padding: 10px; text-align: center; overflow-x: auto; white-space: nowrap; }
        .nav a { color: #fff; text-decoration: none; margin: 0 15px; display: inline-block; }
        .container { max-width: 1200px; margin: 20px auto; display: flex; gap: 20px; padding: 0 20px; }
        .main { flex: 1; min-width: 0; background: transparent; padding: 30px; border-radius: 4px; }
        .sidebar { width: 300px; min-width: 280px; }
        .ad-box { background: transparent; padding: 10px; margin-bottom: 20px; border-radius: 4px; text-align: center; }
        .ad-box img { max-width: 100%; height: auto; }
        .article-title { font-size: 24px; margin-bottom: 10px; line-height: 1.4; }
        .article-meta { color: #999; font-size: 14px; margin-bottom: 20px; }
        .article-content { line-height: 1.8; font-size: 16px; }
        .article-content p { margin-bottom: 15px; }
        .article-content img { max-width: 100%; height: auto; margin: 15px auto; display: block; border-radius: 4px; }
        .article-content h1, .article-content h2, .article-content h3 { color: #333; }
        .sidebar-box { background: transparent; padding: 20px; border-radius: 4px; margin-bottom: 20px; }
        .sidebar-box h3 { margin-top: 0; color: #333; border-bottom: 2px solid #1890ff; padding-bottom: 10px; font-size: 16px; }
        .hot-list { list-style: none; padding: 0; margin: 0; }
        .hot-list li { padding: 10px 0; border-bottom: 1px solid #eee; }
        .hot-list li:last-child { border-bottom: none; }
        .hot-list a { color: #333; text-decoration: none; font-size: 14px; display: block; line-height: 1.4; }
        .hot-list a:hover { color: #1890ff; }
        .hot-list .date { color: #999; font-size: 12px; margin-top: 3px; }
        .breadcrumb { padding: 10px 0; font-size: 14px; color: #999; margin-bottom: 15px; }
        .breadcrumb a { color: #1890ff; text-decoration: none; }
        .breadcrumb span { margin: 0 8px; }
        .related-articles { margin-top: 40px; padding-top: 20px; border-top: 1px solid #eee; }
        .related-articles h3 { margin-top: 0; color: #333; font-size: 18px; margin-bottom: 15px; }
        .related-list { display: grid; grid-template-columns: repeat(2, 1fr); gap: 15px; list-style: none; padding: 0; margin: 0; }
        .related-list li { background: #f9f9f9; padding: 15px; border-radius: 4px; }
        .related-list a { color: #333; text-decoration: none; font-size: 14px; display: block; line-height: 1.5; }
        .related-list a:hover { color: #1890ff; }
        .article-update { color: #999; font-size: 12px; margin-top: 20px; padding-top: 15px; border-top: 1px dashed #eee; }
        .footer { background: #333; color: #fff; padding: 30px 20px; margin-top: 40px; }
        .footer-content { max-width: 1200px; margin: 0 auto; display: flex; justify-content: space-between; flex-wrap: wrap; gap: 20px; }
        .footer-section { flex: 1; min-width: 250px; }
        .footer-section h4 { margin-top: 0; margin-bottom: 15px; color: #fff; }
        .footer-section p { margin: 5px 0; color: #ccc; font-size: 14px; word-break: break-all; }
        .footer-bottom { text-align: center; padding-top: 20px; border-top: 1px solid #555; margin-top: 20px; color: #999; font-size: 12px; }
        .footer-bottom a { color: #1890ff; text-decoration: none; }
        @media (max-width: 768px) {
            .header { padding: 15px 10px; }
            .header h1 { font-size: 18px; }
            .container { flex-direction: column; padding: 0 10px; gap: 15px; }
            .main { padding: 15px; order: 1; }
            .sidebar { width: 100%; min-width: auto; order: 0; }
            .article-title { font-size: 20px; }
            .article-content { font-size: 15px; line-height: 1.7; }
            .related-list { grid-template-columns: 1fr; }
            .footer { padding: 20px 15px; }
            .footer-section { min-width: 100%; }
        }
    </style>
</head>
<body>
    <div class="header">
        <h1><?php echo htmlspecialchars($site['site_name']); ?></h1>
    </div>
    <div class="nav">
        <a href="<?php echo site_url('home'); ?>">首页</a>
    </div>
    
    <?php if (!empty($top_ads)): ?>
    <div style="max-width:1200px;margin:20px auto;padding:0 20px;">
        <?php foreach ($top_ads as $ad): ?>
        <div class="ad-box">
            <a href="<?php echo htmlspecialchars($ad['link_url']); ?>" target="_blank">
                <img src="<?php echo htmlspecialchars($ad['image_url']); ?>" alt="广告" loading="lazy">
            </a>
        </div>
        <?php endforeach; ?>
    </div>
    <?php endif; ?>
    
    <div class="container">
        <div class="main">
            <div class="breadcrumb">
                <a href="<?php echo site_url('home'); ?>">首页</a><span>&gt;</span>
                <span><?php echo htmlspecialchars($article['title']); ?></span>
            </div>
            
            <h1 class="article-title"><?php echo htmlspecialchars($article['title']); ?></h1>
            <div class="article-meta">发布时间：<?php echo date('Y-m-d', strtotime($article['created_at'])); ?></div>
            
            <div class="article-content">
                <?php echo $article['content']; ?>
            </div>
            
            <?php if (!empty($related_articles)): ?>
            <div class="related-articles">
                <h3>相关文章</h3>
                <ul class="related-list">
                    <?php foreach ($related_articles as $r): ?>
                    <li>
                        <a href="<?php echo site_url('article', ['id' => $r['id']]); ?>"><?php echo htmlspecialchars($r['title']); ?></a>
                        <div class="date"><?php echo date('Y-m-d', strtotime($r['created_at'])); ?></div>
                    </li>
                    <?php endforeach; ?>
                </ul>
            </div>
            <?php endif; ?>
            
            <div class="article-update">最后更新：<?php echo date('Y-m-d', strtotime($article['created_at'])); ?></div>
        </div>
        
        <div class="sidebar">
            <?php if (!empty($hot_articles)): ?>
            <div class="sidebar-box">
                <h3>热门文章</h3>
                <ul class="hot-list">
                    <?php foreach ($hot_articles as $h): ?>
                    <li>
                        <a href="<?php echo site_url('article', ['id' => $h['id']]); ?>"><?php echo htmlspecialchars($h['title']); ?></a>
                        <div class="date"><?php echo date('Y-m-d', strtotime($h['created_at'])); ?></div>
                    </li>
                    <?php endforeach; ?>
                </ul>
            </div>
            <?php endif; ?>
            
            <?php if (!empty($side_ads)): ?>
                <?php foreach ($side_ads as $ad): ?>
                <div class="ad-box">
                    <a href="<?php echo htmlspecialchars($ad['link_url']); ?>" target="_blank">
                        <img src="<?php echo htmlspecialchars($ad['image_url']); ?>" alt="广告">
                    </a>
                </div>
                <?php endforeach; ?>
            <?php endif; ?>
        </div>
    </div>
    
    <?php if (!empty($footer_ads)): ?>
    <div style="max-width:1200px;margin:20px auto;padding:0 20px;">
        <?php foreach ($footer_ads as $ad): ?>
        <div class="ad-box">
            <a href="<?php echo htmlspecialchars($ad['link_url']); ?>" target="_blank">
                <img src="<?php echo htmlspecialchars($ad['image_url']); ?>" alt="广告" loading="lazy">
            </a>
        </div>
        <?php endforeach; ?>
    </div>
    <?php endif; ?>
    
    <div class="footer">
        <div class="footer-content">
            <div class="footer-section">
                <h4>关于我们</h4>
                <p><?php echo htmlspecialchars($site['site_name']); ?></p>
                <p><?php echo htmlspecialchars($site['description']); ?></p>
            </div>
            <div class="footer-section">
                <h4>联系方式</h4>
                <?php if ($contact_phone): ?><p>电话：<?php echo htmlspecialchars($contact_phone); ?></p><?php endif; ?>
                <?php if ($contact_email): ?><p>邮箱：<?php echo htmlspecialchars($contact_email); ?></p><?php endif; ?>
                <?php if ($contact_address): ?><p>地址：<?php echo htmlspecialchars($contact_address); ?></p><?php endif; ?>
                <?php if ($contact_wechat): ?><p>微信：<?php echo htmlspecialchars($contact_wechat); ?></p><?php endif; ?>
            </div>
        </div>
        <div class="footer-bottom">
            <p>
                <?php echo htmlspecialchars($site['site_name']); ?> &copy; <?php echo date('Y'); ?> 
                | <?php echo $footer_code; ?>
                <?php if ($sitemap_file): ?>| <a href="<?php echo htmlspecialchars($sitemap_file); ?>" target="_blank">网站地图</a><?php endif; ?>
            </p>
        </div>
    </div>
    
    <?php render_marketing_component($site['id']); ?>
    <?php if (function_exists('render_bottom_contact_bar')) echo render_bottom_contact_bar($site); ?>
    <?php try { echo render_popup_html($site['id']); } catch (Exception $e) {} ?>
</body>
</html>
    <?php
}
