<?php
/**
 * 前台入口 - 根据当前域名查找站点，直接输出对应的HTML模板文件
 */
require_once __DIR__ . '/includes/storage.php';
require_once __DIR__ . '/includes/admin_routes.php';
require_once __DIR__ . '/includes/image_generator.php';
require_once __DIR__ . '/includes/seo.php';
require_once __DIR__ . '/includes/news.php';
require_once __DIR__ . '/includes/search_engines.php';
require_once __DIR__ . '/includes/tongji_bridge.php';
require_once __DIR__ . '/includes/add_js_loader.php';
require_once __DIR__ . '/includes/generated_runtime.php';

function frontRequestPath() {
    $keys = [
        'REQUEST_URI',
        'REDIRECT_URL',
        'SCRIPT_URL',
        'REDIRECT_SCRIPT_URL',
        'ORIG_PATH_INFO',
        'PATH_INFO',
        'HTTP_X_ORIGINAL_URI',
        'UNENCODED_URL',
    ];

    $fallback = '/';
    foreach ($keys as $key) {
        $raw = $_SERVER[$key] ?? '';
        if ($raw === '') {
            continue;
        }
        $path = parse_url($raw, PHP_URL_PATH);
        if (!$path) {
            continue;
        }
        if ($path !== '') {
            $normalizedFallback = rtrim($fallback, '/');
            if ($normalizedFallback === '') {
                $normalizedFallback = '/';
            }
            if ($fallback === '/' || strtolower($normalizedFallback) === '/index.php') {
                $fallback = $path;
            }
        }
        if ($path === '/news' || strpos($path, '/news/') === 0 || $path === '/responsible-gambling' || $path === '/responsible-gambling/' || $path === '/privacy' || $path === '/privacy/' || $path === '/terms' || $path === '/terms/' || $path === '/robots.txt' || preg_match('#^/sitemap(?:-static|-pan-[0-9]+)?\.xml$#i', $path) || $path === '/favicon.ico' || $path === '/apple-touch-icon.png' || preg_match('/^\/[A-Za-z0-9_-]{8,128}\.txt$/', $path)) {
            return $path;
        }
    }

    return $fallback ?: '/';
}

function frontAdminPath() {
    $value = trim((string)getConfig('admin_path', 'admin'));
    $value = trim($value, "/\\ \t\n\r\0\x0B");
    $value = str_replace('\\', '/', $value);
    $value = preg_replace('#/+#', '/', $value);
    $value = strtolower($value);
    if ($value !== '' && strlen($value) <= 120) {
        $segments = explode('/', $value);
        $valid = true;
        foreach ($segments as $segment) {
            if (!preg_match('/^[a-z0-9][a-z0-9_-]{2,48}$/', $segment)) {
                $valid = false;
                break;
            }
            if ($segment === 'admin') {
                $valid = false;
                break;
            }
        }
        if ($valid) {
            return $value;
        }
    }
    return 'admin';
}

function frontAdminNotFound() {
    http_response_code(404);
    header('Content-Type: text/plain; charset=utf-8');
    echo "Not found\n";
    exit;
}

function frontRouteAdminAlias($requestPath) {
    $adminPath = frontAdminPath();
    if ($adminPath === 'admin') {
        return false;
    }
    $prefix = '/' . $adminPath;
    if ($requestPath !== $prefix && strpos($requestPath, $prefix . '/') !== 0) {
        return false;
    }
    if ($requestPath === $prefix) {
        header('Location: ' . $prefix . '/', true, 301);
        exit;
    }

    $relative = trim(substr($requestPath, strlen($prefix)), '/');
    $route = function_exists('adminRouteResolveAlias') ? adminRouteResolveAlias($relative) : null;
    if (!$route || empty($route['file'])) {
        frontAdminNotFound();
    }
    if (!empty($route['ajax'])) {
        $_GET['ajax'] = $route['ajax'];
        $_REQUEST['ajax'] = $route['ajax'];
    }
    $file = __DIR__ . '/admin/' . $route['file'];
    if (!is_file($file)) {
        frontAdminNotFound();
    }
    define('ADMIN_ALIAS_REQUEST', true);
    require $file;
    exit;
}

function frontOutputRobots($site) {
    $sitemapUrl = getCanonicalSiteUrl($site['domain'] ?? '', '/sitemap.xml');
    header('Content-Type: text/plain; charset=utf-8');
    echo "User-agent: Baiduspider\n";
    echo "Allow: /\n\n";
    echo "User-agent: 360Spider\n";
    echo "Allow: /\n\n";
    echo "User-agent: Sogou web spider\n";
    echo "Allow: /\n\n";
    echo "User-agent: YisouSpider\n";
    echo "Allow: /\n\n";
    echo "User-agent: Bingbot\n";
    echo "Allow: /\n\n";
    echo "User-agent: *\n";
    echo "Allow: /\n";
    if ($sitemapUrl !== '') {
        echo "Sitemap: {$sitemapUrl}\n";
    }
}

function frontSitemapDate($value, $fallback = '') {
    $time = strtotime((string)$value);
    if ($time) {
        return date('Y-m-d', $time);
    }
    return $fallback !== '' ? $fallback : date('Y-m-d');
}

function frontSitemapLatestDate($values, $fallback = '') {
    $latest = 0;
    foreach ((array)$values as $value) {
        if ($value === '' || $value === null) {
            continue;
        }
        $time = strtotime((string)$value);
        if ($time && $time > $latest) {
            $latest = $time;
        }
    }
    return $latest > 0 ? date('Y-m-d', $latest) : ($fallback !== '' ? $fallback : date('Y-m-d'));
}

function frontSitemapNewsLastmod($articles, $fallback) {
    $latest = frontNewsLastmodTimestamp([], $articles);
    return $latest > 0 ? date('Y-m-d', $latest) : $fallback;
}

function frontTimestampFromValue($value) {
    if ($value === '' || $value === null) {
        return 0;
    }
    $time = strtotime((string)$value);
    return $time ? (int)$time : 0;
}

function frontLatestTimestamp($values) {
    $latest = 0;
    foreach ((array)$values as $value) {
        $time = is_int($value) ? $value : frontTimestampFromValue($value);
        if ($time > $latest) {
            $latest = $time;
        }
    }
    return $latest;
}

function frontTimestampDate($timestamp, $fallback = '') {
    $timestamp = (int)$timestamp;
    if ($timestamp > 0) {
        return date('Y-m-d', $timestamp);
    }
    return $fallback !== '' ? $fallback : date('Y-m-d');
}

function frontTemplateLastmodTimestamp($site, $templateFile = '') {
    $values = [
        $site['template_updated_at'] ?? '',
        $site['updated_at'] ?? '',
        $site['created_at'] ?? '',
    ];
    if ($templateFile !== '' && is_file($templateFile)) {
        $values[] = filemtime($templateFile) ?: 0;
    }
    return frontLatestTimestamp($values);
}

function frontNewsLastmodTimestamp($site, $articles = null) {
    $values = [
        is_array($site) ? ($site['news_updated_at'] ?? '') : '',
    ];
    foreach ((array)$articles as $article) {
        foreach (['updated_at', 'published_at', 'date'] as $key) {
            $values[] = $article[$key] ?? '';
        }
    }
    return frontLatestTimestamp($values);
}

function frontSitemapPanContext($site) {
    if (($site['generator_version'] ?? '') !== 'v2'
        || !function_exists('generatedRuntimeCurrentBundle')
        || !function_exists('generatorPanLoadPlan')
        || !function_exists('generatorPanLibraryIndexReady')) {
        return null;
    }
    $bundle = generatedRuntimeCurrentBundle($site['id'] ?? '');
    $plan = $bundle ? generatorPanLoadPlan($bundle) : null;
    if (!is_array($plan) || !is_array($bundle)) {
        return null;
    }
    $config = function_exists('generatorConfigFromPlan') ? generatorConfigFromPlan($plan) : [];
    if (empty($config['generation']['pan_directory_enabled'])
        || !generatorPanLibraryIndexReady($config, [
            'directory' => $bundle['directory'] ?? $bundle['revision_dir'] ?? '',
            'site_dir' => $bundle['site_dir'] ?? '',
            'index_directory' => $bundle['site_dir'] ?? '',
        ])) {
        return null;
    }
    return [
        'bundle' => [
            'directory' => $bundle['directory'] ?? $bundle['revision_dir'] ?? '',
            'site_dir' => $bundle['site_dir'] ?? '',
            'index_directory' => $bundle['site_dir'] ?? '',
        ],
        'plan' => $plan,
        'config' => $config,
    ];
}

function frontOutputSitemap($site, $currentDomain = '', $requestedPath = '/sitemap.xml') {
    if ($currentDomain !== '') {
        $site = frontUseRequestHostForCanonical($site, $currentDomain);
    }
    $requestedPath = generatedRuntimeNormalizePath($requestedPath ?: '/sitemap.xml');
    $panContext = frontSitemapPanContext($site);
    if ($requestedPath === '/sitemap.xml' && $panContext) {
        $count = generatorPanLibraryIndexCount($panContext['config'], $panContext['bundle']);
        $chunkSize = max(1000, min(50000, (int)($panContext['config']['generation']['pan_sitemap_chunk_size'] ?? 20000)));
        $chunkCount = $count > 0 ? (int)ceil($count / $chunkSize) : 0;
        header('Content-Type: application/xml; charset=utf-8');
        echo "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
        echo "<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n";
        echo "  <sitemap><loc>" . htmlspecialchars(getCanonicalSiteUrl($site['domain'] ?? '', '/sitemap-static.xml'), ENT_XML1 | ENT_COMPAT, 'UTF-8') . "</loc><lastmod>" . date('Y-m-d') . "</lastmod></sitemap>\n";
        for ($index = 1; $index <= $chunkCount; $index++) {
            $path = '/sitemap-pan-' . $index . '.xml';
            echo "  <sitemap><loc>" . htmlspecialchars(getCanonicalSiteUrl($site['domain'] ?? '', $path), ENT_XML1 | ENT_COMPAT, 'UTF-8') . "</loc><lastmod>" . date('Y-m-d') . "</lastmod></sitemap>\n";
        }
        echo "</sitemapindex>\n";
        return;
    }
    if (preg_match('#^/sitemap-pan-([0-9]+)\.xml$#i', $requestedPath, $match)) {
        if (!$panContext) {
            http_response_code(404);
            header('Content-Type: text/plain; charset=utf-8');
            echo "Not found\n";
            return;
        }
        $page = max(1, (int)$match[1]);
        $chunkSize = max(1000, min(50000, (int)($panContext['config']['generation']['pan_sitemap_chunk_size'] ?? 20000)));
        $entries = generatorPanSitemapEntries($panContext['plan'], $panContext['bundle'], $chunkSize, ($page - 1) * $chunkSize);
        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";
        foreach ($entries as $entry) {
            $path = (string)($entry['url'] ?? '');
            if ($path === '') {
                continue;
            }
            echo "  <url><loc>" . htmlspecialchars(getCanonicalSiteUrl($site['domain'] ?? '', $path), ENT_XML1 | ENT_COMPAT, 'UTF-8') . "</loc><lastmod>" . htmlspecialchars(frontTimestampDate((int)($entry['lastmod'] ?? 0), date('Y-m-d')), ENT_XML1 | ENT_COMPAT, 'UTF-8') . "</lastmod><changefreq>monthly</changefreq><priority>0.5</priority></url>\n";
        }
        echo "</urlset>\n";
        return;
    }
    $siteId = $site['id'] ?? '';
    $templateFile = getTemplateFilePath($siteId);
    $baseTimestamp = frontTemplateLastmodTimestamp($site, $templateFile);
    $baseLastmod = frontTimestampDate($baseTimestamp, date('Y-m-d'));
    $generatedPaths = function_exists('generatedRuntimePaths')
        ? generatedRuntimePaths($site)
        : [];
    $hasGeneratedV2Bundle = ($site['generator_version'] ?? '') === 'v2' && !empty($generatedPaths);
    // v2 pages are already the canonical generated article set. Do not add
    // the legacy news storage URLs beside them, or one article gets two
    // indexable addresses (semantic .html and nYYYY...shtml).
    $newsArticles = $hasGeneratedV2Bundle ? [] : loadNewsArticles($siteId);
    $newsTimestamp = frontNewsLastmodTimestamp($site, $newsArticles);
    $siteLatestTimestamp = max($baseTimestamp, $newsTimestamp);
    $newsLastmod = frontTimestampDate($siteLatestTimestamp, $baseLastmod);
    $homeLastmod = frontTimestampDate($siteLatestTimestamp, $baseLastmod);
    $urls = [
        [
            'loc' => getCanonicalSiteUrl($site['domain'] ?? '', '/'),
            'lastmod' => $homeLastmod,
            'changefreq' => 'weekly',
            'priority' => '1.0',
        ],
        [
            'loc' => getCanonicalSiteUrl($site['domain'] ?? '', '/news/'),
            'lastmod' => $newsLastmod,
            'changefreq' => 'daily',
            'priority' => '0.8',
        ],
        [
            'loc' => getCanonicalSiteUrl($site['domain'] ?? '', '/privacy/'),
            'lastmod' => $baseLastmod,
            'changefreq' => 'monthly',
            'priority' => '0.3',
        ],
        [
            'loc' => getCanonicalSiteUrl($site['domain'] ?? '', '/terms/'),
            'lastmod' => $baseLastmod,
            'changefreq' => 'monthly',
            'priority' => '0.3',
        ],
    ];

    if (!$hasGeneratedV2Bundle) {
        $totalNewsPages = max(1, (int)ceil(count($newsArticles) / max(1, (int)NEWS_PER_PAGE)));
        for ($page = 2; $page <= $totalNewsPages; $page++) {
            $urls[] = [
                'loc' => newsIndexUrl($site, $page),
                'lastmod' => $newsLastmod,
                'changefreq' => 'daily',
                'priority' => '0.7',
            ];
        }

        foreach ($newsArticles as $article) {
            $articleTimestamp = frontLatestTimestamp([
                $article['updated_at'] ?? '',
                $article['published_at'] ?? '',
                $article['date'] ?? '',
            ]);
            $urls[] = [
                'loc' => newsArticleUrl($site, $article),
                'lastmod' => frontTimestampDate($articleTimestamp, $newsLastmod),
                'changefreq' => 'monthly',
                'priority' => '0.6',
            ];
        }
    }

    foreach ($generatedPaths as $generatedPath) {
        if (!is_string($generatedPath) || $generatedPath === '' || $generatedPath === '/') {
            continue;
        }
        $urls[] = [
            'loc' => getCanonicalSiteUrl($site['domain'] ?? '', $generatedPath),
            'lastmod' => $homeLastmod,
            'changefreq' => strpos($generatedPath, '/news/') === 0 ? 'weekly' : 'monthly',
            'priority' => $generatedPath === '/news/' ? '0.8' : '0.6',
        ];
    }

    // Pan-directory URLs are dynamic and therefore do not appear in the
    // generated bundle manifest. Submit only finite, validated library
    // articles with a stable category URL; never enumerate synthetic or
    // unbounded random URLs in the sitemap.
    if ($requestedPath === '/sitemap.xml' && !$panContext && $hasGeneratedV2Bundle && function_exists('generatedRuntimeCurrentBundle')
        && function_exists('generatorPanLoadPlan') && function_exists('generatorPanSitemapEntries')) {
        $bundle = generatedRuntimeCurrentBundle($siteId);
        $plan = $bundle ? generatorPanLoadPlan($bundle) : null;
        if (is_array($plan) && is_array($bundle)) {
            $planConfig = function_exists('generatorConfigFromPlan') ? generatorConfigFromPlan($plan) : [];
            // An enabled library index is intentionally the only source for
            // large pan sitemaps. Do not make the first public request scan
            // thousands of raw files while the background index is missing.
            $fallbackEntries = empty($planConfig['generation']['pan_library_index_enabled'])
                ? generatorPanSitemapEntries($plan, ['directory' => $bundle['directory'] ?? ''], 5000)
                : [];
            foreach ($fallbackEntries as $entry) {
                $path = (string)($entry['url'] ?? '');
                if ($path === '' || $path === '/') {
                    continue;
                }
                $lastmod = frontTimestampDate((int)($entry['lastmod'] ?? 0), $newsLastmod);
                $urls[] = [
                    'loc' => getCanonicalSiteUrl($site['domain'] ?? '', $path),
                    'lastmod' => $lastmod,
                    'changefreq' => 'monthly',
                    'priority' => '0.5',
                ];
            }
        }
    }

    if (function_exists('isGamblingSiteProfile') && isGamblingSiteProfile(
        $site['title'] ?? '',
        $site['description'] ?? '',
        $site['core_keyword'] ?? '',
        $site['keywords'] ?? '',
        $site['industry'] ?? ''
    )) {
        $urls[] = [
            'loc' => getCanonicalSiteUrl($site['domain'] ?? '', '/responsible-gambling/'),
            'lastmod' => $baseLastmod,
            'changefreq' => 'monthly',
            'priority' => '0.5',
        ];
    }

    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";
    $seenLoc = [];
    foreach ($urls as $url) {
        if (($url['loc'] ?? '') === '') {
            continue;
        }
        if (isset($seenLoc[$url['loc']])) {
            continue;
        }
        $seenLoc[$url['loc']] = true;
        echo "  <url>\n";
        echo "    <loc>" . htmlspecialchars($url['loc'], ENT_XML1 | ENT_COMPAT, 'UTF-8') . "</loc>\n";
        echo "    <lastmod>" . htmlspecialchars($url['lastmod'], ENT_XML1 | ENT_COMPAT, 'UTF-8') . "</lastmod>\n";
        echo "    <changefreq>" . htmlspecialchars($url['changefreq'], ENT_XML1 | ENT_COMPAT, 'UTF-8') . "</changefreq>\n";
        echo "    <priority>" . htmlspecialchars($url['priority'], ENT_XML1 | ENT_COMPAT, 'UTF-8') . "</priority>\n";
        echo "  </url>\n";
    }
    echo "</urlset>\n";
}

function frontLoadNormalizedBrandTemplateHtml($site) {
    $templateHtml = '';
    $templateFile = getTemplateFilePath($site['id'] ?? '');
    if ($templateFile !== '' && is_file($templateFile)) {
        $templateHtml = (string)@file_get_contents($templateFile);
    }
    if ($templateHtml !== '' && function_exists('normalizeTemplateBrandNavigation')) {
        $homeUrl = function_exists('getCanonicalSiteUrl') ? getCanonicalSiteUrl($site['domain'] ?? '', '/') : '/';
        if ($homeUrl === '') {
            $homeUrl = '/';
        }
        $templateHtml = normalizeTemplateBrandNavigation(
            $templateHtml,
            $site['title'] ?? '',
            $site['domain'] ?? '',
            $homeUrl,
            $site['core_keyword'] ?? '',
            $site['language'] ?? 'zh-CN'
        );
        if (function_exists('cleanupTemplateBrandAnchorClosings')) {
            $templateHtml = cleanupTemplateBrandAnchorClosings($templateHtml);
        }
    }
    return $templateHtml;
}

function frontRequestHostForSite($site, $currentDomain = '') {
    $current = normalizeSiteDomain($currentDomain);
    if ($current !== '' && siteMatchesDomain($site, $current)) {
        return $current;
    }
    return getPrimaryDomain($site['domain'] ?? '');
}

function frontUseRequestHostForCanonical($site, $currentDomain = '') {
    if (!is_array($site)) {
        return $site;
    }

    $host = frontRequestHostForSite($site, $currentDomain);
    if ($host !== '') {
        $site['request_domain'] = $host;
        $site['canonical_domain'] = $host;
        $site['domain'] = $host;
    }

    return $site;
}



function frontInternalAnchorPathForSubdomainHref($href, $baseDomain) {
    $href = trim(html_entity_decode((string)$href, ENT_QUOTES | ENT_HTML5, 'UTF-8'));
    if ($href === '' || preg_match('/^(?:mailto|tel|sms|javascript|data):/i', $href)) {
        return null;
    }
    if ($href[0] === '#') {
        return '/';
    }
    if (strpos($href, '//') === 0) {
        $href = 'https:' . $href;
    }
    if (preg_match('#^https?://#i', $href)) {
        $host = normalizeSiteDomain(parse_url($href, PHP_URL_HOST) ?: '');
        if ($host === '' || getRegistrableDomain($host) !== $baseDomain) {
            return null;
        }
        $path = parse_url($href, PHP_URL_PATH);
        if ($path === null || $path === false || $path === '') {
            $path = '/';
        }
        $query = parse_url($href, PHP_URL_QUERY);
        if ($query !== null && $query !== false && $query !== '') {
            $path .= '?' . $query;
        }
        return $path;
    }
    if ($href[0] === '/') {
        return $href;
    }
    return null;
}

function frontFixedCurrentHostAnchorPath($open, $tag, $path, $language = 'zh-CN') {
    $open = (string)$open;
    $tag = (string)$tag;
    $path = (string)$path;
    $pathOnly = $path;
    $query = '';
    $queryPos = strpos($pathOnly, '?');
    if ($queryPos !== false) {
        $query = substr($pathOnly, $queryPos);
        $pathOnly = substr($pathOnly, 0, $queryPos);
    }
    if ($pathOnly === '') {
        $pathOnly = '/';
    }

    $isBrand = function_exists('templateHtmlClassContains')
        && templateHtmlClassContains($open, ['brand', 'logo', 'site-title', 'site-name', 'navbar-brand', 'nav-brand', 'brand-name', 'brand-text', 'header-title', 'site-logo', 'nav-logo']);
    $isHomeText = function_exists('templateNavBodyHasHome') && templateNavBodyHasHome($tag, $language);
    $isNewsText = function_exists('templateNavBodyHasNews') && templateNavBodyHasNews($tag, $language);

    if ($isBrand || $isHomeText) {
        return '/';
    }
    if ($isNewsText && ($pathOnly === '/' || $pathOnly === '#')) {
        return '/news/';
    }
    if ($pathOnly === '/sitemap.xml') {
        return '/sitemap.xml';
    }
    if (preg_match('#^/news(?:/|$)#i', $pathOnly)) {
        return $pathOnly . $query;
    }
    return null;
}

function frontDiversifySubdomainHrefLinks($html, $site, $currentDomain = '') {
    $html = (string)$html;
    if ($html === '' || stripos($html, '<a') === false) {
        return $html;
    }
    $requestHost = frontRequestHostForSite($site, $currentDomain);
    $baseDomain = getRegistrableDomain($requestHost);
    if ($baseDomain === '' || isIpOrLocalDomain($baseDomain)) {
        return $html;
    }
    $pool = function_exists('templateFragmentHrefSubdomainPool')
        ? templateFragmentHrefSubdomainPool($requestHost, $site)
        : [];
    $rewriteSubdomainHrefs = !empty($pool);
    $counter = 0;
    $usedHosts = [];
    $language = function_exists('normalizeSiteLanguage') ? normalizeSiteLanguage($site['language'] ?? 'zh-CN') : ($site['language'] ?? 'zh-CN');
    $salt = strtolower((string)$requestHost) . '|' . ($site['id'] ?? '') . '|front-href-v3';
    return preg_replace_callback('/<a\b[^>]*>[\s\S]*?<\/a>/i', function($matches) use ($baseDomain, $pool, $rewriteSubdomainHrefs, $salt, $requestHost, $language, &$counter, &$usedHosts) {
        $tag = $matches[0];
        if (!preg_match('/^<a\b[^>]*>/i', $tag, $openMatch)) {
            return $tag;
        }
        $open = $openMatch[0];
        if (stripos($open, 'href') === false) {
            return $tag;
        }
        $href = templateHtmlGetAttribute($open, 'href');
        $path = frontInternalAnchorPathForSubdomainHref($href, $baseDomain);
        if ($path === null) {
            return $tag;
        }
        $fixedPath = frontFixedCurrentHostAnchorPath($open, $tag, $path, $language);
        if ($fixedPath !== null) {
            $newOpen = templateHtmlSetAttribute($open, 'href', getCanonicalSiteUrl($requestHost, $fixedPath));
            return $newOpen . substr($tag, strlen($open));
        }
        if (!$rewriteSubdomainHrefs) {
            return $tag;
        }
        $counter++;
        $host = templatePickDistinctFragmentHrefHost($pool, $salt, $href . '|' . $counter . '|' . $open, $usedHosts);
        if ($host === '') {
            return $tag;
        }
        $newOpen = templateHtmlSetAttribute($open, 'href', getCanonicalSiteUrl($host, '/'));
        return $newOpen . substr($tag, strlen($open));
    }, $html);
}
function frontOutputFavicon($site) {
    require_once __DIR__ . '/includes/image_generator.php';
    $templateHtml = frontLoadNormalizedBrandTemplateHtml($site);
    $assets = ensureSiteBrandAssets(
        $site['id'] ?? '',
        $site['title'] ?? '',
        $site['domain'] ?? '',
        $site['industry'] ?? '',
        $site['core_keyword'] ?? '',
        $templateHtml
    );
    $path = $assets['favicon_path'] ?? '';
    if ($path === '' || !is_file($path)) {
        http_response_code(404);
        header('Content-Type: text/plain; charset=utf-8');
        echo "Not found\n";
        return;
    }

    header('Content-Type: image/x-icon');
    header('Cache-Control: public, max-age=300, must-revalidate');
    header('Content-Length: ' . filesize($path));
    readfile($path);
}

function frontOutputAppleTouchIcon($site) {
    require_once __DIR__ . '/includes/image_generator.php';
    $templateHtml = frontLoadNormalizedBrandTemplateHtml($site);
    $assets = ensureSiteBrandAssets(
        $site['id'] ?? '',
        $site['title'] ?? '',
        $site['domain'] ?? '',
        $site['industry'] ?? '',
        $site['core_keyword'] ?? '',
        $templateHtml
    );
    $path = $assets['apple_path'] ?? ($assets['logo_path'] ?? '');
    if ($path === '' || !is_file($path)) {
        http_response_code(404);
        header('Content-Type: text/plain; charset=utf-8');
        echo "Not found\n";
        return;
    }

    header('Content-Type: image/png');
    header('Cache-Control: public, max-age=300, must-revalidate');
    header('Content-Length: ' . filesize($path));
    readfile($path);
}

function frontBuildContactBar() {
    $contact = trim((string)getConfig('site_contact_info', ''));
    if ($contact === '') {
        return '';
    }

    $contact = preg_replace('/\s+/u', ' ', $contact);
    $contact = trim($contact);
    if ($contact === '') {
        return '';
    }

    $contactHtml = htmlspecialchars($contact, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
    return <<<HTML
<style>
.site-contact-fixed{position:fixed;left:0;right:0;bottom:0;z-index:2147483000;box-sizing:border-box;padding:10px 16px calc(10px + env(safe-area-inset-bottom));background:rgba(8,10,18,.88);backdrop-filter:blur(12px);border-top:1px solid rgba(255,255,255,.14);color:#f8fafc;font:500 14px/1.55 -apple-system,BlinkMacSystemFont,"Segoe UI",Arial,sans-serif;text-align:center;letter-spacing:0;box-shadow:0 -10px 30px rgba(0,0,0,.24)}
.site-contact-fixed strong{color:#fff;margin-right:8px}
body{padding-bottom:max(56px, env(safe-area-inset-bottom))!important}
@media(max-width:640px){.site-contact-fixed{font-size:13px;text-align:left;padding-left:12px;padding-right:12px}}
</style>
<div class="site-contact-fixed" role="contentinfo"><strong>联系方式</strong>{$contactHtml}</div>
HTML;
}

function frontApplyContactBar($html) {
    $statusCode = (int)http_response_code();
    if ($statusCode >= 400) {
        return (string)$html;
    }
    $contactBar = frontBuildContactBar();
    if ($contactBar === '') {
        return (string)$html;
    }
    if (stripos((string)$html, 'site-contact-fixed') !== false) {
        return (string)$html;
    }
    if (stripos((string)$html, '</body>') !== false) {
        return str_ireplace('</body>', $contactBar . "\n</body>", (string)$html);
    }
    return (string)$html . $contactBar;
}

function frontOutputHtml($html, $previewDomain = '') {
    $html = (string)$html;
    $html = frontApplyContactBar($html);
    // 广告和统计统一由 add.js 负责。这里不再把旧版内联代码注入
    // HTML，避免 PC、蜘蛛和移动搜索访问出现重复脚本或重复广告。
    $html = addJsLoaderInjectHead($html, $previewDomain);
    if (frontApplyHtmlCacheValidators($html, $previewDomain)) {
        if (function_exists('tongjiBridgeFinishResponse')) {
            tongjiBridgeFinishResponse();
        }
        return;
    }
    if (strtoupper((string)($_SERVER['REQUEST_METHOD'] ?? 'GET')) !== 'HEAD') {
        echo $html;
    }
    // Let PHP-FPM send the crawler response before the shutdown logger
    // performs its file appends. This keeps statistics without making the
    // crawler wait for log I/O under concurrent spider traffic.
    if (function_exists('tongjiBridgeFinishResponse')) {
        tongjiBridgeFinishResponse();
    }
}

function frontApplyHtmlCacheValidators($html, $previewDomain = '') {
    if ($previewDomain !== '' || headers_sent() || (int)http_response_code() >= 400) {
        return false;
    }

    $method = strtoupper((string)($_SERVER['REQUEST_METHOD'] ?? 'GET'));
    if ($method !== 'GET' && $method !== 'HEAD') {
        return false;
    }

    $etag = '"' . hash('sha256', (string)$html) . '"';
    header('ETag: ' . $etag);
    if (!empty($GLOBALS['__add_js_loader_response_injected'])) {
        // 注入结果取决于 Referer、UA 和 Fetch Metadata，禁止 CDN/浏览器
        // 将带 add.js 的搜索访问复用给直接访问或手机模拟请求。
        header('Cache-Control: private, no-store, no-cache, must-revalidate, max-age=0');
        header('Vary: Referer, User-Agent, Sec-Fetch-Site, Sec-Fetch-Mode, Sec-Fetch-Dest, Sec-Fetch-User, Sec-CH-UA-Mobile');
    } else {
        header('Cache-Control: no-cache, must-revalidate');
    }

    $ifNoneMatch = trim((string)($_SERVER['HTTP_IF_NONE_MATCH'] ?? ''));
    if ($ifNoneMatch === '') {
        return false;
    }

    $tags = array_map('trim', explode(',', $ifNoneMatch));
    if (in_array('*', $tags, true) || in_array($etag, $tags, true) || in_array('W/' . $etag, $tags, true)) {
        http_response_code(304);
        return true;
    }

    return false;
}

function frontIsHomepagePath($path) {
    $path = parse_url((string)$path, PHP_URL_PATH);
    if (!$path) {
        return true;
    }
    $path = '/' . ltrim($path, '/');
    $normalized = rtrim($path, '/');
    if ($normalized === '') {
        $normalized = '/';
    }
    return $normalized === '/' || strtolower($normalized) === '/index.php';
}

function frontOutputSite404($site, $homePath = '/') {
    $language = normalizeSiteLanguage($site['language'] ?? 'zh-CN');
    $title = getSiteLocalizedText($language, 'not_found_title');
    $description = getSiteLocalizedText($language, 'not_found_description');
    $homeUrl = getCanonicalSiteUrl($site['domain'] ?? '', $homePath);
    if ($homeUrl === '') {
        $homeUrl = '/';
    }
    http_response_code(404);
    header('Content-Type: text/html; charset=utf-8');
    echo renderNewsLayout(
        $site,
        $title,
        $description,
        '<main><section class="hero"><h1>' . htmlspecialchars($title, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '</h1><p class="muted">' . htmlspecialchars($description, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '</p><a class="back" href="' . htmlspecialchars($homeUrl, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '">Home</a></section></main>',
        $homeUrl
    );
    exit;
}

// 获取当前域名
function frontLegalPageTexts($site, $type) {
    $language = normalizeSiteLanguage($site['language'] ?? 'zh-CN');
    $brand = trim((string)($site['core_keyword'] ?? ''));
    if ($brand === '') {
        $brand = trim((string)($site['title'] ?? ''));
    }
    if ($brand === '') {
        $brand = trim((string)($site['domain'] ?? ''));
    }
    $isPrivacy = ($type === 'privacy');
    $maps = [
        'zh-CN' => [
            'privacy_title' => '隐私政策',
            'privacy_description' => '本页面说明本站如何处理访问数据、Cookie、联系请求和基础安全记录。',
            'terms_title' => '使用条款',
            'terms_description' => '本页面说明本站内容使用规则、责任边界和访问者应遵守的基本条款。',
            'privacy_sections' => [
                ['信息收集', '本站可能记录必要的访问日志、设备信息、来源页面和基础安全数据，用于维护访问稳定性、排查异常请求和改进页面体验。'],
                ['Cookie 使用', '本站可能使用必要 Cookie 或同类技术来保持页面功能、统计基础访问表现和识别异常流量。你可以通过浏览器设置管理 Cookie。'],
                ['联系与安全', '如果你通过页面联系方式提交信息，相关内容仅用于回复请求、处理问题或履行必要的站点管理职责。'],
            ],
            'terms_sections' => [
                ['内容用途', '本站内容用于信息展示和主题说明，不构成法律、财务、医疗或其他专业建议。访问者应结合自身情况独立判断。'],
                ['访问规则', '访问者不得滥用页面功能、干扰站点运行、批量抓取造成异常负载，或将页面内容用于违法违规用途。'],
                ['责任边界', '本站会尽量保持内容准确和可访问，但不保证所有信息在任何时间都完全无误或持续可用。'],
            ],
        ],
        'pt-BR' => [
            'privacy_title' => 'Política de Privacidade',
            'privacy_description' => 'Esta página resume como o site trata dados de navegação, cookies, contatos e registros básicos de segurança.',
            'terms_title' => 'Termos de Uso',
            'terms_description' => 'Estes termos explicam regras de uso do conteúdo, limites de responsabilidade e condições gerais de acesso.',
            'privacy_sections' => [
                ['Dados de navegação', 'O site pode registrar logs essenciais, dados técnicos do dispositivo, páginas de origem e eventos de segurança para manter estabilidade, investigar abuso e melhorar a experiência.'],
                ['Cookies', 'Podemos usar cookies necessários ou tecnologias semelhantes para funcionamento básico, leitura de métricas agregadas e prevenção de tráfego anormal. Você pode gerenciar cookies no navegador.'],
                ['Contato e proteção', 'Informações enviadas por canais de contato são usadas para responder solicitações, tratar dúvidas e cumprir rotinas necessárias de administração do site.'],
            ],
            'terms_sections' => [
                ['Uso do conteúdo', 'O conteúdo tem finalidade informativa e editorial. Ele não substitui aconselhamento legal, financeiro, médico ou profissional específico.'],
                ['Conduta de acesso', 'Visitantes não devem abusar de recursos, interferir na operação, gerar carga automatizada anormal ou usar o conteúdo para fins ilegais.'],
                ['Limites', 'Buscamos manter as páginas corretas e acessíveis, mas não garantimos que toda informação esteja sempre completa, atualizada ou disponível sem interrupção.'],
            ],
        ],
        'en' => [
            'privacy_title' => 'Privacy Policy',
            'privacy_description' => 'This page explains how the site handles browsing data, cookies, contact requests and basic security records.',
            'terms_title' => 'Terms of Use',
            'terms_description' => 'These terms explain content usage rules, responsibility limits and general access conditions.',
            'privacy_sections' => [
                ['Browsing data', 'The site may keep essential logs, device information, referrer pages and security events to maintain stability, investigate abuse and improve the experience.'],
                ['Cookies', 'Necessary cookies or similar technologies may be used for basic functionality, aggregate measurement and abnormal traffic prevention.'],
                ['Contact information', 'Information sent through contact channels is used to respond to requests, handle questions and support necessary site administration.'],
            ],
            'terms_sections' => [
                ['Content use', 'Content is informational and editorial. It does not replace legal, financial, medical or other professional advice.'],
                ['Access rules', 'Visitors must not misuse functionality, disrupt site operation, create abnormal automated load or use content for unlawful purposes.'],
                ['Limitations', 'We try to keep pages accurate and accessible, but do not guarantee every detail is always complete, current or uninterrupted.'],
            ],
        ],
    ];
    $fallback = $maps[$language] ?? $maps['en'];
    return [
        'title' => $isPrivacy ? $fallback['privacy_title'] : $fallback['terms_title'],
        'description' => $isPrivacy ? $fallback['privacy_description'] : $fallback['terms_description'],
        'sections' => $isPrivacy ? $fallback['privacy_sections'] : $fallback['terms_sections'],
        'brand' => $brand,
    ];
}

function frontOutputLegalPage($site, $type, $previewDomain = '') {
    $type = $type === 'terms' ? 'terms' : 'privacy';
    $data = frontLegalPageTexts($site, $type);
    $pageHeading = $data['title'];
    $brand = newsCleanText($site['core_keyword'] ?? '', 48);
    if ($brand === '') {
        $brand = newsShortSiteName($site);
    }
    $title = $pageHeading;
    if ($brand !== '' && strpos($title, $brand) === false) {
        $title .= ' - ' . $brand;
    }
    $description = $data['description'];
    $canonical = getCanonicalSiteUrl($site['domain'] ?? '', '/' . $type . '/');
    $body = '<main>'
        . newsBreadcrumbHtml($site, $pageHeading)
        . '<section class="news-page-hero"><h1>' . htmlspecialchars($pageHeading, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '</h1><p class="news-page-muted">' . htmlspecialchars($description, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '</p></section>'
        . '<article class="news-article news-legal-page">';
    foreach ($data['sections'] as $section) {
        $body .= '<section><h2>' . htmlspecialchars($section[0], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '</h2><p>' . htmlspecialchars($section[1], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '</p></section>';
    }
    $body .= '</article></main>';
    header('Content-Type: text/html; charset=utf-8');
    frontOutputHtml(renderNewsLayout($site, $title, $description, $body, $canonical, '', 'article', 'website'), $previewDomain);
    exit;
}
$currentDomain = $_SERVER['HTTP_HOST'] ?? '';
$requestPath = frontRequestPath();

// The root-level news directory is a server-side content library for pan
// pages, not a public document tree. Block source-file extensions if the web
// server forwards the request to PHP instead of serving the static file first.
// Keep /news/ and generated .html/.shtml article URLs available.
if (preg_match('#^/news/.+\.(?:txt|md|json)(?:/)?$#i', $requestPath)) {
    http_response_code(404);
    header('Content-Type: text/plain; charset=utf-8');
    header('X-Robots-Tag: noindex, nofollow, noarchive', true);
    echo "Not found\n";
    exit;
}

frontRouteAdminAlias($requestPath);

// 支持预览模式（后台预览）
$previewDomain = $_GET['preview_domain'] ?? '';
if ($previewDomain) {
    $currentDomain = $previewDomain;
}

// 根据域名查找站点
$site = getSiteByDomain($currentDomain);

if ($site && !$previewDomain) {
    $site = frontUseRequestHostForCanonical($site, $currentDomain);
}

if ($site && !$previewDomain) {
    try {
        tongjiRecordRequest($site);
    } catch (Throwable $e) {
        if (function_exists('appLog')) {
            appLog('tongji_runtime_error', 'Tongji 统计桥接运行异常，已跳过本次统计', [
                'error' => $e->getMessage(),
                'file' => $e->getFile(),
                'line' => $e->getLine(),
            ], 'warning');
        }
    }
}
$isIndexNowKeyRequest = $site ? searchEngineIsIndexNowKeyRequest($requestPath, $site['domain'] ?? '') : false;
if (!$previewDomain && ($requestPath === '/robots.txt' || preg_match('#^/sitemap(?:-static|-pan-[0-9]+)?\.xml$#i', $requestPath) || $requestPath === '/favicon.ico' || $requestPath === '/apple-touch-icon.png' || $isIndexNowKeyRequest)) {
    if (!$site) {
        http_response_code(404);
        header('Content-Type: text/plain; charset=utf-8');
        echo "Not found\n";
        exit;
    }

    if ($requestPath === '/robots.txt') {
        frontOutputRobots($site);
    } elseif ($requestPath === '/favicon.ico') {
        frontOutputFavicon($site);
    } elseif ($requestPath === '/apple-touch-icon.png') {
        frontOutputAppleTouchIcon($site);
    } elseif ($isIndexNowKeyRequest) {
        searchEngineOutputIndexNowKey($site['domain'] ?? '');
    } else {
        frontOutputSitemap($site, $currentDomain, $requestPath);
    }
    exit;
}

if (!$site) {
    // 未找到站点，显示404
    http_response_code(404);
    ?>
    <!DOCTYPE html>
    <html lang="zh-CN">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>页面未找到</title>
        <style>
            * { margin: 0; padding: 0; box-sizing: border-box; }
            body {
                min-height: 100vh;
                display: flex;
                align-items: center;
                justify-content: center;
                background: #0a0a1a;
                font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
                color: #e2e8f0;
                text-align: center;
                padding: 32px;
            }
            .error-code {
                font-size: 120px;
                font-weight: 900;
                background: linear-gradient(135deg, #6366f1, #ec4899);
                -webkit-background-clip: text;
                -webkit-text-fill-color: transparent;
                line-height: 1;
                margin-bottom: 16px;
            }
            h1 { font-size: 24px; margin-bottom: 12px; }
            p { color: rgba(255,255,255,0.5); font-size: 16px; margin-bottom: 24px; }
            .domain-info {
                display: inline-block;
                padding: 8px 20px;
                background: rgba(255,255,255,0.05);
                border: 1px solid rgba(255,255,255,0.1);
                border-radius: 8px;
                font-family: monospace;
                color: #60a5fa;
                font-size: 14px;
                margin-bottom: 24px;
            }
            a {
                display: inline-block;
                padding: 12px 32px;
                background: linear-gradient(135deg, #6366f1, #8b5cf6);
                color: #fff;
                text-decoration: none;
                border-radius: 8px;
                font-size: 14px;
                transition: opacity 0.3s;
            }
            a:hover { opacity: 0.9; }
        </style>
    </head>
    <body>
        <div>
            <div class="error-code">404</div>
            <h1>站点未找到</h1>
            <p>当前域名尚未配置站点页面</p>
            <div class="domain-info"><?= htmlspecialchars($currentDomain) ?></div>
            <br>
            <a href="/<?= htmlspecialchars(frontAdminPath()) ?>/">进入后台管理</a>
        </div>
    </body>
    </html>
    <?php
    exit;
}

generatedRuntimeTryServe($site, $requestPath, $currentDomain, $previewDomain);

if (!$previewDomain && $requestPath === '/news') {
    header('Location: ' . getCanonicalSiteUrl($site['domain'] ?? '', '/news/'), true, 301);
    exit;
}

if (!$previewDomain && $requestPath === '/responsible-gambling') {
    header('Location: ' . getCanonicalSiteUrl($site['domain'] ?? '', '/responsible-gambling/'), true, 301);
    exit;
}

if (!$previewDomain && $requestPath === '/responsible-gambling/') {
    $isGamblingSite = function_exists('isGamblingSiteProfile') && isGamblingSiteProfile(
        $site['title'] ?? '',
        $site['description'] ?? '',
        $site['core_keyword'] ?? '',
        $site['keywords'] ?? '',
        $site['industry'] ?? ''
    );
    if (!$isGamblingSite) {
        http_response_code(404);
        header('Content-Type: text/html; charset=utf-8');
        echo renderNewsLayout(
            $site,
            getSiteLocalizedText($site['language'] ?? 'zh-CN', 'not_found_title'),
            getSiteLocalizedText($site['language'] ?? 'zh-CN', 'not_found_description'),
            '<main><section class="hero"><h1>' . htmlspecialchars(getSiteLocalizedText($site['language'] ?? 'zh-CN', 'not_found_title'), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '</h1><p class="muted">' . htmlspecialchars(getSiteLocalizedText($site['language'] ?? 'zh-CN', 'not_found_description'), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '</p></section></main>',
            getCanonicalSiteUrl($site['domain'] ?? '', '/')
        );
        exit;
    }

    $language = normalizeSiteLanguage($site['language'] ?? 'zh-CN');
    $title = getSiteLocalizedText($language, 'compliance_page_title');
    $description = getSiteLocalizedText($language, 'compliance_page_description');
    $canonical = getCanonicalSiteUrl($site['domain'] ?? '', '/responsible-gambling/');
    $body = '<main>'
        . newsBreadcrumbHtml($site, $title)
        . '<section class="news-page-hero"><h1>' . htmlspecialchars($title, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '</h1><p class="news-page-muted">' . htmlspecialchars($description, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '</p></section>'
        . (function_exists('renderGamblingComplianceSection') ? renderGamblingComplianceSection($language) : '')
        . '</main>';
    header('Content-Type: text/html; charset=utf-8');
    frontOutputHtml(renderNewsLayout($site, $title, $description, $body, $canonical, '', 'article', 'website'), $previewDomain);
    exit;
}

if (!$previewDomain && ($requestPath === '/privacy' || $requestPath === '/terms')) {
    $target = $requestPath === '/privacy' ? '/privacy/' : '/terms/';
    header('Location: ' . getCanonicalSiteUrl($site['domain'] ?? '', $target), true, 301);
    exit;
}

if (!$previewDomain && ($requestPath === '/privacy/' || $requestPath === '/terms/')) {
    frontOutputLegalPage($site, trim($requestPath, '/'), $previewDomain);
}
if (!$previewDomain && newsIsIndexPath($requestPath)) {
    $newsPage = newsPageFromPath($requestPath);
    if ($newsPage <= 1 && rtrim($requestPath, '/') !== '/news') {
        header('Location: ' . newsIndexUrl($site, 1), true, 301);
        exit;
    }
    if ($newsPage === 1 && preg_match('#/news/page/1/?$#', $requestPath)) {
        header('Location: ' . newsIndexUrl($site, 1), true, 301);
        exit;
    }
    $maxNewsPage = max(1, (int)ceil(count(loadNewsArticles($site['id'] ?? '')) / max(1, (int)NEWS_PER_PAGE)));
    if ($newsPage > $maxNewsPage) {
        header('Location: ' . newsIndexUrl($site, $maxNewsPage), true, 301);
        exit;
    }
    header('Content-Type: text/html; charset=utf-8');
    frontOutputHtml(renderNewsIndexPage($site, $newsPage), $previewDomain);
    exit;
}

if (!$previewDomain) {
    $newsSlug = newsSlugFromPath($requestPath);
    if ($newsSlug !== '') {
        $article = getNewsArticleByPublicSlug($site, $newsSlug);
        if (!$article) {
            http_response_code(404);
            header('Content-Type: text/html; charset=utf-8');
            echo renderNewsLayout(
                $site,
                '资讯文章未找到',
                '当前资讯文章不存在或已经被删除。',
                '<main><section class="hero"><h1>资讯文章未找到</h1><p class="muted">当前资讯文章不存在或已经被删除。</p><a class="back" href="/news/">返回资讯列表</a></section></main>',
                newsUrl($site, '/news/')
            );
            exit;
        }
        $canonicalNewsPath = parse_url(newsArticleUrl($site, $article), PHP_URL_PATH) ?: '';
        if ($canonicalNewsPath !== '' && $requestPath !== $canonicalNewsPath) {
            header('Location: ' . newsArticleUrl($site, $article), true, 301);
            exit;
        }
        header('Content-Type: text/html; charset=utf-8');
        frontOutputHtml(renderNewsArticlePage($site, $article), $previewDomain);
        exit;
    }
}

if (!$previewDomain && !frontIsHomepagePath($requestPath)) {
    frontOutputSite404($site);
}

// 查找模板文件
$templateFile = getTemplateFilePath($site['id']);

if (!file_exists($templateFile)) {
    // 站点存在但模板未生成
    ?>
    <!DOCTYPE html>
    <html lang="zh-CN">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title><?= htmlspecialchars($site['title']) ?></title>
        <style>
            * { margin: 0; padding: 0; box-sizing: border-box; }
            body {
                min-height: 100vh;
                display: flex;
                align-items: center;
                justify-content: center;
                background: #0a0a1a;
                font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
                color: #e2e8f0;
                text-align: center;
                padding: 32px;
            }
            .icon { font-size: 64px; margin-bottom: 20px; }
            h1 { font-size: 24px; margin-bottom: 12px; }
            p { color: rgba(255,255,255,0.5); font-size: 16px; margin-bottom: 8px; }
        </style>
    </head>
    <body>
        <div>
            <div class="icon">🚧</div>
            <h1><?= htmlspecialchars($site['title']) ?></h1>
            <p>站点页面正在建设中，请稍后再访问</p>
        </div>
    </body>
    </html>
    <?php
    exit;
}

// 直接输出模板HTML文件
header('Content-Type: text/html; charset=utf-8');

$html = file_get_contents($templateFile);
if ($html === false) {
    appLog('template_read_error', '读取模板文件失败', [
        'site_id' => $site['id'] ?? '',
        'path' => $templateFile,
    ]);
    http_response_code(500);
    echo '模板文件读取失败，请检查文件权限';
    exit;
}

$normalizedHtml = normalizeGeneratedImageTags($html);
$normalizedHtml = prioritizeTemplateHeroImage($normalizedHtml);
if ($normalizedHtml !== $html) {
    $html = $normalizedHtml;
    if (@file_put_contents($templateFile, $html) === false) {
        appLog('template_write_error', '清理本地图片尺寸属性后写回失败', [
            'site_id' => $site['id'] ?? '',
            'path' => $templateFile,
        ]);
    }
}

// 兼容旧模板：如果模板里还残留随机远程图，访问时快速替换成本地图片并入队异步补图。
if (preg_match('/(?:picsum\.photos|source\.unsplash\.com|images\.unsplash\.com|via\.placeholder\.com|placehold\.co|placekitten\.com)/i', $html)) {
    $imageResult = processTemplateImages(
        $html,
        $site['id'],
        $site['title'] ?? '',
        $site['description'] ?? '',
        $site['domain'] ?? '',
        $site['industry'] ?? '',
        $site['core_keyword'] ?? ''
    );

    if (!empty($imageResult['html']) && $imageResult['html'] !== $html) {
        $html = $imageResult['html'];
        if (@file_put_contents($templateFile, $html) === false) {
            appLog('template_write_error', '旧模板图片本地化后写回失败', [
                'site_id' => $site['id'] ?? '',
                'path' => $templateFile,
            ]);
        } else {
            appLog('image_queue_info', '旧模板图片已自动本地化并加入队列', [
                'site_id' => $site['id'] ?? '',
                'localized' => $imageResult['localized'] ?? 0,
                'queued' => $imageResult['queued'] ?? 0,
            ], 'info');
        }
    }
}

$brandedHtml = ensureTemplateBrandAssets(
    $html,
    $site['id'] ?? '',
    $site['title'] ?? '',
    $site['domain'] ?? '',
    $site['industry'] ?? '',
    $site['core_keyword'] ?? '',
    $site['language'] ?? 'zh-CN'
);
$brandedHtml = ensureTemplateHomeNavigation(
    $brandedHtml,
    $site['domain'] ?? '',
    $site['language'] ?? 'zh-CN'
);
if (function_exists('cleanupTemplateBrandAnchorClosings')) {
    $brandedHtml = cleanupTemplateBrandAnchorClosings($brandedHtml);
}
$frontSeoTitle = $site['title'] ?? '';
$frontSeoDescription = $site['description'] ?? '';
$frontSeoKeywords = $site['keywords'] ?? '';
if (function_exists('seoDowngradeSensitiveMetadata')) {
    $frontSafeMeta = seoDowngradeSensitiveMetadata([
        'title' => $frontSeoTitle,
        'description' => $frontSeoDescription,
        'keywords' => $frontSeoKeywords,
        'core_keyword' => $site['core_keyword'] ?? '',
        'industry' => $site['industry'] ?? '',
    ], $site['language'] ?? 'zh-CN');
    $frontSeoTitle = $frontSafeMeta['title'] ?? $frontSeoTitle;
    $frontSeoDescription = $frontSafeMeta['description'] ?? $frontSeoDescription;
    $frontSeoKeywords = $frontSafeMeta['keywords'] ?? $frontSeoKeywords;
}
$frontDisplayTitle = trim((string)($site['core_keyword'] ?? ''));
if ($frontDisplayTitle === '') {
    $frontDisplayTitle = trim((string)$frontSeoTitle);
}
if (function_exists('seoDowngradeSensitiveHtmlHeadings')) {
    $brandedHtml = seoDowngradeSensitiveHtmlHeadings($brandedHtml, $site['language'] ?? 'zh-CN', $frontSeoTitle, $frontSeoDescription, $frontSeoKeywords, $site['core_keyword'] ?? '', $site['industry'] ?? '');
}
$frontNewsArticlesForDate = loadNewsArticles($site['id'] ?? '');
$frontModifiedTimestamp = max(
    frontTemplateLastmodTimestamp($site, $templateFile),
    frontNewsLastmodTimestamp($site, $frontNewsArticlesForDate)
);
$frontModifiedDate = frontTimestampDate($frontModifiedTimestamp, date('Y-m-d'));
$frontLogoUrl = function_exists('templateSiteLogoUrlForSeo')
    ? templateSiteLogoUrlForSeo($site['id'] ?? '', $frontDisplayTitle, $site['domain'] ?? '', $site['industry'] ?? '', $site['core_keyword'] ?? '', $brandedHtml)
    : '';
if (function_exists('injectSeoIntoHtml')) {
    $brandedHtml = injectSeoIntoHtml(
        $brandedHtml,
        $frontSeoTitle,
        $frontSeoDescription,
        $site['domain'] ?? '',
        $frontSeoKeywords,
        $site['language'] ?? 'zh-CN',
        $frontDisplayTitle,
        $frontModifiedDate,
        $frontLogoUrl
    );
}
if (function_exists('enforceTemplateExactTitle')) {
    $brandedHtml = enforceTemplateExactTitle($brandedHtml, $frontSeoTitle);
}
if (function_exists('enforceTemplateExactH1')) {
    $brandedHtml = enforceTemplateExactH1($brandedHtml, $frontDisplayTitle);
}
if (function_exists('ensureTemplateGamblingCompliance')) {
    $brandedHtml = ensureTemplateGamblingCompliance(
        $brandedHtml,
        $frontDisplayTitle,
        $frontSeoDescription,
        $site['industry'] ?? '',
        $site['core_keyword'] ?? '',
        $frontSeoKeywords,
        $site['language'] ?? 'zh-CN'
    );
}
if (function_exists('removeTemplateHeadingEmojisForSensitiveSites')) {
    $brandedHtml = removeTemplateHeadingEmojisForSensitiveSites($brandedHtml, $frontDisplayTitle, $frontSeoDescription, $site['industry'] ?? '', $site['core_keyword'] ?? '', $frontSeoKeywords);
}
if (function_exists('normalizeTemplateFooterYear')) {
    $brandedHtml = normalizeTemplateFooterYear($brandedHtml);
}
if ($brandedHtml !== $html) {
    $html = $brandedHtml;
    if (@file_put_contents($templateFile, $html) === false) {
        appLog('template_write_error', '写入站点品牌资源失败', [
            'site_id' => $site['id'] ?? '',
            'path' => $templateFile,
        ]);
    }
}

$html = injectNewsSectionIntoHomepage($html, $site, 4);
$html = frontDiversifySubdomainHrefLinks($html, $site, $currentDomain);
frontOutputHtml($html, $previewDomain);

if (!$previewDomain) {
    triggerAutoNewsAfterResponse($site, 'front_visit');
}
exit;
