<?php
/**
 * CloakDebugPage — DEBUG 模式调试结果页
 */
class CloakDebugPage
{
    /** 判定逻辑所在文件（入口 index.php require 本文件，backtrace 无法可靠取行号） */
    const IP_CHECK_FILE = 'ip_check.php';

    /** @var array 完整判断流程步骤 */
    private static $flow = [];

    /** @var array check_result 写入轨迹 */
    private static $trail = [];

    /** @var array|null */
    private static $apiResponse = null;

    /** @var array */
    private static $flags = [];

    /** @var array|null */
    private static $exitPoint = null;

    /** @var float */
    private static $startedAt = 0;

    public static function init()
    {
        self::$flow        = [];
        self::$trail       = [];
        self::$apiResponse = null;
        self::$flags       = [];
        self::$exitPoint   = null;
        self::$startedAt   = microtime(true);
    }

    /**
     * 记录流程步骤（请从 ip_check.php 使用 cloak_dbg_step(__LINE__, ...) 调用）
     */
    public static function step($title, $status, $detail = '', array $extra = [])
    {
        $loc = self::callerLoc();
        self::stepAt($loc['line'], $title, $status, $detail, $extra);
    }

    /**
     * 记录流程步骤，指定 ip_check.php 行号
     */
    public static function stepAt($line, $title, $status, $detail = '', array $extra = [])
    {
        self::$flow[] = [
            'title'  => $title,
            'status' => $status,
            'detail' => $detail,
            'file'   => self::IP_CHECK_FILE,
            'line'   => (int)$line,
            'extra'  => $extra,
            'at'     => microtime(true),
        ];
    }

    /**
     * 记录 check_result 写入（请从 ip_check.php 使用 cloak_dbg_record(__LINE__, ...) 调用）
     */
    public static function record($result, $reason, array $extra = [])
    {
        $loc = self::callerLoc();
        self::recordAt($loc['line'], $result, $reason, $extra);
    }

    /**
     * 记录 check_result 写入，指定 ip_check.php 行号
     */
    public static function recordAt($line, $result, $reason, array $extra = [])
    {
        $stage = isset($extra['stage']) ? (string)$extra['stage'] : 'unknown';

        $entry = [
            'result' => $result,
            'reason' => $reason,
            'file'   => self::IP_CHECK_FILE,
            'line'   => (int)$line,
            'stage'  => $stage,
            'extra'  => $extra,
            'at'     => microtime(true),
        ];
        self::$trail[] = $entry;

        $decideDetail = $reason !== '' ? $reason : '(理由为空)';
        if ($result === 'true' || $result === 'false') {
            $decideDetail .= ' → check_result=' . $result;
            self::$flags['final'] = $entry;
        } elseif ($result === null) {
            $decideDetail = ($reason !== '' ? $reason : '(理由为空)') . '（未写入 check_result，流程继续）';
        }

        self::$flow[] = [
            'title'  => self::stageTitle($stage),
            'status' => ($result === 'true' || $result === 'false') ? 'decide' : 'warn',
            'detail' => $decideDetail,
            'file'   => self::IP_CHECK_FILE,
            'line'   => (int)$line,
            'extra'  => array_merge($extra, ['check_result' => $result]),
            'at'     => microtime(true),
        ];
    }

    public static function setApiResponse(array $response)
    {
        self::$apiResponse = $response;
    }

    public static function setFlag($key, $value)
    {
        self::$flags[$key] = $value;
    }

    public static function setExitPoint($line, $label, array $extra = [])
    {
        self::$exitPoint = [
            'file'  => self::IP_CHECK_FILE,
            'line'  => (int)$line,
            'label' => $label,
            'extra' => $extra,
        ];
    }

    public static function renderAndExit(array $ctx)
    {
        $result     = (string)($ctx['check_result'] ?? 'false');
        $reason     = (string)($ctx['reason'] ?? '');
        $final      = self::$flags['final'] ?? null;
        $isRealPage = ($result === 'true');
        $elapsed    = isset($ctx['started_at'])
            ? round((microtime(true) - $ctx['started_at']) * 1000)
            : round((microtime(true) - self::$startedAt) * 1000);

        $routeInfo   = self::buildRouteInfo($ctx, $result);
        $diagnosis   = self::buildReasonDiagnosis($reason, $result, $ctx);
        $flowHtml    = self::buildFlowHtml();
        $trailHtml   = self::buildTrailHtml();
        $sessionHtml = self::buildSessionHtml($ctx['session'] ?? []);

        $verdictLabel = $isRealPage ? '真实页 (DB_FP)' : '安全页 (DB_ZP)';
        $verdictClass = $isRealPage ? 'verdict-pass' : 'verdict-block';
        $verdictIcon  = $isRealPage ? '✓' : '✕';

        $finalLoc    = $final ? self::formatLoc($final['file'], $final['line']) : '—';
        $finalStage  = $final ? htmlspecialchars(self::stageTitle($final['stage'])) : '—';
        $finalReason = $final ? htmlspecialchars($final['reason'] !== '' ? $final['reason'] : '(空)') : '—';

        $exit = self::$exitPoint;
        $exitLoc   = $exit ? self::formatLoc($exit['file'], $exit['line']) : self::IP_CHECK_FILE . ':?';
        $exitLabel = $exit ? htmlspecialchars($exit['label']) : 'CloakDebugPage::renderAndExit()';

        header('Content-Type: text/html; charset=utf-8');
        header('X-Robots-Tag: noindex, nofollow');

        echo '<!DOCTYPE html><html lang="zh-CN"><head><meta charset="utf-8">';
        echo '<meta name="viewport" content="width=device-width,initial-scale=1">';
        echo '<title>Cloak DEBUG — ' . htmlspecialchars($ctx['config_name'] ?? '') . '</title>';
        echo self::styles();
        echo '</head><body><div class="wrap">';

        echo '<header class="hero">';
        echo '<div class="hero-badge">DEBUG MODE</div>';
        echo '<h1>流量判定调试报告</h1>';
        echo '<p class="hero-sub">配置 <code>' . htmlspecialchars($ctx['config_name'] ?? '') . '</code>';
        echo ' · 耗时 ' . (int)$elapsed . ' ms · ' . date('Y-m-d H:i:s') . '</p>';
        echo '</header>';

        // 流程终止位置（醒目）
        echo '<section class="card exit-card">';
        echo '<h2>流程终止位置</h2>';
        echo '<div class="exit-main"><code>' . htmlspecialchars($exitLoc) . '</code></div>';
        echo '<p class="exit-desc">DEBUG 在此截停执行（<strong>' . $exitLabel . '</strong>），未继续跳转真实页/安全页。</p>';
        echo '<dl class="kv kv-tight">';
        echo '<dt>未开启 DEBUG 时下一分支</dt><dd>' . htmlspecialchars($routeInfo['next_branch']) . '</dd>';
        echo '<dt>预计跳转目标</dt><dd class="url-break">' . htmlspecialchars($routeInfo['dest'] !== '' ? $routeInfo['dest'] : '—') . '</dd>';
        echo '<dt>路由条件</dt><dd><code>' . htmlspecialchars($routeInfo['condition']) . '</code></dd>';
        echo '</dl></section>';

        echo '<section class="card verdict-card ' . $verdictClass . '">';
        echo '<div class="verdict-icon">' . $verdictIcon . '</div>';
        echo '<div class="verdict-body">';
        echo '<div class="verdict-title">' . htmlspecialchars($verdictLabel) . '</div>';
        echo '<div class="verdict-meta">check_result = <code>' . htmlspecialchars($result) . '</code></div>';
        echo '</div></section>';

        // 判断理由 + 空理由诊断
        echo '<section class="card"><h2>判定结论</h2><dl class="kv">';
        echo '<dt>当前 $reason</dt><dd>';
        if ($reason !== '') {
            echo htmlspecialchars($reason);
        } else {
            echo '<span class="tag tag-warn">为空</span>';
        }
        echo '</dd>';
        echo '<dt>最终写入 check_result</dt><dd><code>' . htmlspecialchars($finalLoc) . '</code>（' . $finalStage . '）</dd>';
        echo '<dt>写入时 $reason</dt><dd>' . $finalReason . '</dd>';
        echo '</dl>';
        if ($diagnosis !== '') {
            echo '<div class="diagnosis-box">' . $diagnosis . '</div>';
        }
        echo '</section>';

        // 完整判断流程
        echo '<section class="card"><h2>完整判断流程</h2>';
        echo '<p class="hint">按 ip_check.php 实际执行顺序展示；<span class="legend-dot decide"></span> 表示写入 check_result，<span class="legend-dot fail"></span> 拦截，<span class="legend-dot skip"></span> 跳过。</p>';
        echo $flowHtml !== '' ? $flowHtml : '<p class="hint">暂无流程记录（请确认 CLOAK_DEBUG_MODE 已保存为 ON）。</p>';
        echo '</section>';

        if ($trailHtml !== '') {
            echo '<section class="card"><h2>check_result 写入轨迹</h2>';
            echo '<p class="hint">每次对 $_SESSION[\'check_result\'] 的赋值；最后一项为最终判定依据。</p>';
            echo $trailHtml . '</section>';
        }

        if (self::$apiResponse) {
            echo '<section class="card"><h2>API 判定接口 (cloak_check_curl)</h2>';
            echo self::buildApiHtml(self::$apiResponse);
            echo '</section>';
        }

        echo '<div class="grid-2">';
        echo '<section class="card"><h2>Session 状态</h2>';
        echo $sessionHtml . '</section>';
        echo '<section class="card"><h2>路由预览</h2><dl class="kv kv-tight">';
        echo '<dt>SHOW_SITE_MODE</dt><dd><code>' . htmlspecialchars($ctx['mode'] ?? '') . '</code></dd>';
        echo '<dt>visit_to_2</dt><dd><code>' . htmlspecialchars($ctx['session']['visit_to_2'] ?? '—') . '</code> <span class="hint-inline">=2 时跳过 API 块</span></dd>';
        echo '<dt>visit_to_3</dt><dd><code>' . htmlspecialchars($ctx['session']['visit_to_3'] ?? '—') . '</code></dd>';
        echo '</dl></section>';
        echo '</div>';

        echo '<section class="card"><h2>访客依据信息</h2>';
        echo '<table class="data-table"><tbody>' . self::buildVisitorRows($ctx) . '</tbody></table></section>';

        echo '<section class="card"><h2>请求参数</h2>';
        echo '<table class="data-table"><tbody>' . self::buildQueryRows() . '</tbody></table></section>';

        echo '<section class="card"><h2>配置快照</h2>';
        echo '<table class="data-table compact"><tbody>' . self::buildConfigRows() . '</tbody></table></section>';

        echo '<footer class="foot">DEBUG 模式 — 生产环境请关闭 CLOAK_DEBUG_MODE</footer>';
        echo '</div></body></html>';
        exit;
    }

    private static function callerLoc()
    {
        $bt = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 20);
        foreach ($bt as $frame) {
            if (empty($frame['file'])) {
                continue;
            }
            if (basename($frame['file']) === self::IP_CHECK_FILE) {
                return [
                    'file' => self::IP_CHECK_FILE,
                    'line' => (int)($frame['line'] ?? 0),
                ];
            }
        }
        return [
            'file' => self::IP_CHECK_FILE,
            'line' => 0,
        ];
    }

    private static function formatLoc($file, $line)
    {
        $file = ($file !== '' && $file !== '—') ? $file : self::IP_CHECK_FILE;
        if (strpos($file, 'ip_check') !== false) {
            $file = self::IP_CHECK_FILE;
        }
        return $file . ':' . (int)$line;
    }

    private static function stageTitle($stage)
    {
        $map = [
            'session_fast_path'      => 'Session 快速路径',
            'url_args_timeout'       => '临时链接参数',
            'blacklist'              => '黑名单',
            'whitelist_ip'           => 'IP 白名单',
            'whitelist_params'       => '链接参数白名单',
            'fingerprint_cookie'     => 'JS 指纹 Cookie',
            'api_cloak'              => 'API 远程判定',
            'no_referer_or_gcuid'    => '无 Referer/gcuid',
            'session_partial'        => 'Session 已有结果',
            'main_block_skipped'     => '主判定块跳过',
        ];
        return isset($map[$stage]) ? $map[$stage] : $stage;
    }

    private static function buildRouteInfo(array $ctx, $result)
    {
        $mode = $ctx['mode'] ?? '';
        $isReal = ($mode !== 'zp') && ((($mode === 'ip_check' && $result !== 'false') || $mode === 'fp'));
        $forceRisk = !empty($ctx['force_risk']);
        $visit3 = $ctx['session']['visit_to_3'] ?? '';

        if (!$isReal) {
            return [
                'next_branch' => '安全页分支 (ip_check.php ~859)',
                'dest'        => (string)($ctx['zp_url'] ?? ''),
                'condition'   => "mode={$mode}, check_result={$result} → false 或 mode=zp",
            ];
        }

        if ($forceRisk && $visit3 !== '3') {
            return [
                'next_branch' => '真实页 + 二次风控 (page_666.php / cloakjs)',
                'dest'        => self::firstUrl($ctx['fp_urls'] ?? []),
                'condition'   => 'check_result!=false 且 CLOAK_RISK_NUMBER>0',
            ];
        }

        return [
            'next_branch' => '真实页 (page_6.php)',
            'dest'        => self::firstUrl($ctx['fp_urls'] ?? []),
            'condition'   => "mode={$mode}, check_result={$result}",
        ];
    }

    private static function buildReasonDiagnosis($reason, $result, array $ctx)
    {
        if ($reason !== '') {
            return '';
        }

        $items = [];
        $final = self::$flags['final'] ?? null;

        if (!$final) {
            $items[] = '整个流程<strong>未记录</strong>到任何 <code>check_result</code> 写入点；最终 result 可能来自默认值或未赋值。';
        } elseif ($final['reason'] === '') {
            $items[] = '最终写入点 <code>' . htmlspecialchars(self::formatLoc($final['file'], $final['line'])) . '</code>（' . htmlspecialchars(self::stageTitle($final['stage'])) . '）写入时 <code>$reason</code> 即为空。';
        }

        if (self::$apiResponse) {
            $apiReason = self::$apiResponse['reason'] ?? '';
            $apiCalled = !empty(self::$apiResponse['called']);
            if ($apiCalled && $apiReason === '') {
                $items[] = 'API 接口 <code>cloak_check_curl</code> 已调用，但返回 JSON 中 <code>reason</code> 字段为空（见下方 API 详情）。';
            } elseif (!$apiCalled) {
                $items[] = 'API 判定<strong>未执行</strong>（可能缺少 ip/Browser/Accept-Language，或主判定块被跳过）。';
            }
        }

        $visit2 = $ctx['session']['visit_to_2'] ?? '';
        if ($visit2 === '2' && empty(self::$apiResponse['called'])) {
            $items[] = '<code>$_SESSION[\'visit_to_2\']=2</code> 导致主判定块（API 调用）被跳过；若同时 <code>check_result</code> 未重建，<code>$reason</code> 会保持初始空字符串。DEBUG 模式已重置 visit_to_*，若仍出现请检查是否在判定前被重新写入。';
        }

        if ($result === 'true') {
            $items[] = '生产环境中，进入真实页分支后 <code>ip_check.php:818</code> 会将 <code>$logs[\'reason\']</code> 清空（注释：访问仿品时没用跳转理由），日志里可能显示空理由，但 DEBUG 截停前应仍能看到写入时的 reason。';
        }

        if (empty($items)) {
            $items[] = '$reason 在流程开始时初始化为空字符串，且后续没有任何分支对其赋值。';
        }

        $html = '<h3 class="diag-title">⚠ $reason 为空 — 溯源分析</h3><ul class="diag-list">';
        foreach ($items as $item) {
            $html .= '<li>' . $item . '</li>';
        }
        $html .= '</ul>';
        return $html;
    }

    private static function buildFlowHtml()
    {
        if (empty(self::$flow)) {
            return '';
        }
        $html = '<div class="flow-timeline">';
        $final = self::$flags['final'] ?? null;
        foreach (self::$flow as $i => $s) {
            $isFinal = $final
                && isset($s['extra']['check_result'])
                && ($s['extra']['check_result'] === 'true' || $s['extra']['check_result'] === 'false')
                && $final['line'] === $s['line']
                && $final['stage'] === ($s['extra']['stage'] ?? '');
            $cls = 'flow-step status-' . htmlspecialchars($s['status']);
            if ($isFinal) {
                $cls .= ' flow-final';
            }
            $html .= '<div class="' . $cls . '">';
            $html .= '<div class="flow-marker">' . ($i + 1) . '</div>';
            $html .= '<div class="flow-body">';
            $html .= '<div class="flow-head">';
            $html .= '<span class="flow-title">' . htmlspecialchars($s['title']) . '</span>';
            $html .= '<span class="flow-badge badge-' . htmlspecialchars($s['status']) . '">' . htmlspecialchars(self::statusLabel($s['status'])) . '</span>';
            $html .= '<code class="flow-loc">' . htmlspecialchars(self::formatLoc($s['file'], $s['line'])) . '</code>';
            $html .= '</div>';
            if ($s['detail'] !== '') {
                $html .= '<div class="flow-detail">' . htmlspecialchars($s['detail']) . '</div>';
            }
            if (!empty($s['extra'])) {
                $show = $s['extra'];
                unset($show['stage'], $show['check_result']);
                if (!empty($show)) {
                    $html .= '<pre class="code-block sm">' . htmlspecialchars(json_encode($show, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)) . '</pre>';
                }
            }
            $html .= '</div></div>';
        }
        $html .= '</div>';
        return $html;
    }

    private static function statusLabel($status)
    {
        $map = [
            'pass' => '通过', 'fail' => '拦截', 'skip' => '跳过',
            'warn' => '可疑', 'info' => '信息', 'decide' => '写入结果',
        ];
        return isset($map[$status]) ? $map[$status] : $status;
    }

    private static function buildTrailHtml()
    {
        if (empty(self::$trail)) {
            return '';
        }
        $html = '<div class="trail">';
        $final = self::$flags['final'] ?? null;
        foreach (self::$trail as $e) {
            $isFinal = $final && $final['line'] === $e['line'] && $final['stage'] === $e['stage'];
            $cls = $isFinal ? ' trail-item final' : ' trail-item';
            $res = $e['result'] !== null ? $e['result'] : '—';
            $html .= '<div class="' . trim($cls) . '">';
            $html .= '<div class="trail-head">';
            $html .= '<span class="trail-loc"><code>' . htmlspecialchars(self::formatLoc($e['file'], $e['line'])) . '</code></span>';
            $html .= '<span class="trail-stage">' . htmlspecialchars(self::stageTitle($e['stage'])) . '</span>';
            if ($e['result'] !== null) {
                $html .= '<span class="trail-res">' . htmlspecialchars((string)$res) . '</span>';
            }
            $html .= '</div>';
            $html .= '<div class="trail-reason">' . htmlspecialchars($e['reason'] !== '' ? $e['reason'] : '(理由为空)') . '</div>';
            $html .= '</div>';
        }
        $html .= '</div>';
        return $html;
    }

    private static function buildApiHtml(array $api)
    {
        $html = '<dl class="kv">';
        $html .= '<dt>是否调用</dt><dd>' . (!empty($api['called']) ? '是' : '否') . '</dd>';
        if (!empty($api['skip_reason'])) {
            $html .= '<dt>未调用原因</dt><dd>' . htmlspecialchars($api['skip_reason']) . '</dd>';
        }
        $html .= '<dt>API result</dt><dd><code>' . htmlspecialchars((string)($api['result_raw'] ?? $api['check_result'] ?? '—')) . '</code></dd>';
        $html .= '<dt>API reason</dt><dd>' . htmlspecialchars((string)($api['reason'] ?? '—')) . '</dd>';
        $html .= '<dt>API country</dt><dd>' . htmlspecialchars((string)($api['country'] ?? '—')) . '</dd>';
        if (!empty($api['curl_error'])) {
            $html .= '<dt>Curl 错误</dt><dd class="tag tag-warn">' . htmlspecialchars($api['curl_error']) . '</dd>';
        }
        if (!empty($api['raw'])) {
            $html .= '<dt>原始响应</dt><dd><pre class="code-block">' . htmlspecialchars(is_string($api['raw']) ? $api['raw'] : json_encode($api['raw'], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)) . '</pre></dd>';
        }
        $html .= '</dl>';
        return $html;
    }

    private static function buildSessionHtml(array $session)
    {
        if (empty($session)) {
            return '<p class="hint">无 Session 快照</p>';
        }
        $html = '<table class="data-table compact"><tbody>';
        foreach ($session as $k => $v) {
            $html .= '<tr><th>' . htmlspecialchars((string)$k) . '</th><td><code>' . htmlspecialchars(is_scalar($v) ? (string)$v : json_encode($v, JSON_UNESCAPED_UNICODE)) . '</code></td></tr>';
        }
        $html .= '</tbody></table>';
        return $html;
    }

    private static function buildVisitorRows(array $ctx)
    {
        $v = $ctx['visitor'] ?? null;
        $rows = [
            'IP 地址'         => $v ? $v->v_ip : ($ctx['logs']['customers_ip'] ?? ''),
            '国家/地区'       => $v ? $v->v_country : '',
            '浏览器'          => $v ? $v->v_Browser : ($ctx['logs']['v_Browser'] ?? ''),
            '客户端类型'      => $v ? $v->v_Client : ($ctx['logs']['Client'] ?? ''),
            'Referer'         => $v ? $v->v_referer : ($ctx['logs']['v_referer'] ?? ''),
            '当前 URL'        => $v ? $v->v_curPageURL : ($ctx['logs']['v_PageURL'] ?? ''),
            'Accept-Language' => $v ? $v->accept_language : ($ctx['logs']['accept_language'] ?? ''),
            'User-Agent'      => isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '',
            'visit_md5'       => $v ? $v->visit_md5_code : ($ctx['logs']['visit_md5_code'] ?? ''),
            '设备型号'        => $ctx['device'] ?? '',
            'gcuid'           => $_SESSION['gcuid'] ?? '',
        ];
        return self::rowsHtml($rows);
    }

    private static function buildQueryRows()
    {
        $keys = ['fbclid', 'gclid', 'wbraid', 'gbraid', 'ttclid', 'ad_spm_id'];
        $rows = [];
        foreach ($keys as $k) {
            $rows[$k] = isset($_GET[$k]) ? $_GET[$k] : '(未携带)';
        }
        return self::rowsHtml($rows);
    }

    private static function buildConfigRows()
    {
        $map = [
            'SHOW_SITE_MODE_SWITCH'      => defined('SHOW_SITE_MODE_SWITCH') ? SHOW_SITE_MODE_SWITCH : '',
            'SHOW_SITE_COUNTRY'          => defined('SHOW_SITE_COUNTRY') ? SHOW_SITE_COUNTRY : '',
            'CLOAK_DEBUG_MODE'           => defined('CLOAK_DEBUG_MODE') ? CLOAK_DEBUG_MODE : '',
            'CLOAK_RISK_NUMBER'          => defined('CLOAK_RISK_NUMBER') ? CLOAK_RISK_NUMBER : '',
            'CLOAK_RISK_ENHANCED'        => defined('CLOAK_RISK_ENHANCED') ? CLOAK_RISK_ENHANCED : '',
            'CLOAK_URL_ARGS_TIMEOUT'     => defined('CLOAK_URL_ARGS_TIMEOUT') ? CLOAK_URL_ARGS_TIMEOUT : '',
        ];
        return self::rowsHtml($map);
    }

    private static function rowsHtml(array $rows)
    {
        $html = '';
        foreach ($rows as $label => $val) {
            $html .= '<tr><th>' . htmlspecialchars((string)$label) . '</th><td>' . htmlspecialchars((string)$val) . '</td></tr>';
        }
        return $html;
    }

    private static function firstUrl($urls)
    {
        if (is_array($urls) && !empty($urls)) {
            return (string)$urls[0];
        }
        return is_string($urls) ? $urls : '';
    }

    private static function styles()
    {
        return <<<'CSS'
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;background:linear-gradient(145deg,#0f172a,#1e293b 50%,#0f172a);color:#e2e8f0;min-height:100vh;line-height:1.55}
.wrap{max-width:980px;margin:0 auto;padding:24px 16px 48px}
.hero{text-align:center;margin-bottom:24px}
.hero-badge{display:inline-block;background:#f59e0b;color:#1e293b;font-size:.72rem;font-weight:700;letter-spacing:.08em;padding:4px 10px;border-radius:999px;margin-bottom:10px}
.hero h1{font-size:1.5rem;margin-bottom:6px}
.hero-sub{font-size:.85rem;color:#94a3b8}
.hero-sub code{background:#334155;padding:2px 6px;border-radius:4px}
.card{background:#1e293b;border:1px solid #334155;border-radius:12px;padding:20px;margin-bottom:16px}
.card h2{font-size:.92rem;font-weight:600;color:#94a3b8;text-transform:uppercase;letter-spacing:.04em;margin-bottom:14px}
.grid-2{display:grid;grid-template-columns:1fr 1fr;gap:16px}
@media(max-width:720px){.grid-2{grid-template-columns:1fr}}
.exit-card{border-color:#f59e0b;background:linear-gradient(135deg,#422006 0%,#1e293b 40%)}
.exit-main{font-size:1.4rem;font-weight:700;margin-bottom:8px}
.exit-main code{color:#fcd34d}
.exit-desc{font-size:.88rem;color:#cbd5e1;margin-bottom:12px}
.verdict-card{display:flex;align-items:center;gap:20px;padding:22px}
.verdict-pass{border-color:#059669;background:linear-gradient(135deg,#064e3b,#1e293b)}
.verdict-block{border-color:#dc2626;background:linear-gradient(135deg,#7f1d1d,#1e293b)}
.verdict-icon{width:52px;height:52px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:1.5rem;font-weight:700;flex-shrink:0}
.verdict-pass .verdict-icon{background:#059669;color:#fff}
.verdict-block .verdict-icon{background:#dc2626;color:#fff}
.verdict-title{font-size:1.25rem;font-weight:700}
.verdict-meta{font-size:.85rem;color:#94a3b8;margin-top:4px}
.kv dt{font-size:.76rem;color:#64748b;margin-top:10px}
.kv dt:first-child{margin-top:0}
.kv dd{font-size:.9rem;margin-top:2px;word-break:break-all}
.kv-tight dt{margin-top:6px}
.diagnosis-box{margin-top:14px;padding:14px;background:#422006;border:1px solid #b45309;border-radius:8px;font-size:.85rem}
.diag-title{font-size:.88rem;color:#fcd34d;margin-bottom:8px}
.diag-list{padding-left:18px;color:#fde68a}
.diag-list li{margin-bottom:6px}
.flow-timeline{display:flex;flex-direction:column;gap:0;position:relative;padding-left:8px}
.flow-step{display:flex;gap:14px;padding:12px 0;border-bottom:1px solid #334155;position:relative}
.flow-step:last-child{border-bottom:none}
.flow-step.flow-final .flow-body{border-left:3px solid #f59e0b;padding-left:12px;margin-left:-12px}
.flow-marker{width:28px;height:28px;border-radius:50%;background:#334155;color:#94a3b8;font-size:.75rem;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0;margin-top:2px}
.flow-step.status-decide .flow-marker{background:#b45309;color:#fff}
.flow-step.status-fail .flow-marker{background:#dc2626;color:#fff}
.flow-step.status-pass .flow-marker{background:#059669;color:#fff}
.flow-step.status-skip .flow-marker{background:#475569;color:#cbd5e1}
.flow-body{flex:1;min-width:0}
.flow-head{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin-bottom:4px}
.flow-title{font-weight:600;font-size:.92rem}
.flow-badge{font-size:.68rem;padding:2px 7px;border-radius:4px;font-weight:600}
.badge-pass{background:#064e3b;color:#6ee7b7}
.badge-fail{background:#7f1d1d;color:#fca5a5}
.badge-skip{background:#334155;color:#94a3b8}
.badge-warn{background:#78350f;color:#fcd34d}
.badge-info{background:#1e3a5f;color:#7dd3fc}
.badge-decide{background:#92400e;color:#fde68a}
.flow-loc{font-size:.72rem;color:#38bdf8}
.flow-detail{font-size:.85rem;color:#cbd5e1}
.legend-dot{display:inline-block;width:8px;height:8px;border-radius:50%;margin:0 2px}
.legend-dot.decide{background:#f59e0b}
.legend-dot.fail{background:#dc2626}
.legend-dot.skip{background:#64748b}
.hint{font-size:.8rem;color:#64748b;margin-bottom:12px}
.hint-inline{font-size:.72rem;color:#64748b}
.tag{display:inline-block;font-size:.72rem;padding:2px 8px;border-radius:4px}
.tag-warn{background:#78350f;color:#fcd34d}
.tag-info{background:#1e3a5f;color:#7dd3fc}
.url-break{word-break:break-all;color:#38bdf8;font-size:.85rem}
.data-table{width:100%;border-collapse:collapse;font-size:.84rem}
.data-table th{text-align:left;color:#64748b;font-weight:500;width:130px;padding:7px 10px 7px 0;vertical-align:top;border-bottom:1px solid #334155}
.data-table td{padding:7px 0;word-break:break-all;border-bottom:1px solid #334155}
.data-table.compact th{width:180px}
.data-table tr:last-child th,.data-table tr:last-child td{border-bottom:none}
.code-block{background:#0f172a;border:1px solid #334155;border-radius:6px;padding:10px;font-size:.76rem;overflow-x:auto;margin-top:6px;white-space:pre-wrap}
.code-block.sm{font-size:.7rem;padding:6px 8px}
.trail{display:flex;flex-direction:column;gap:8px}
.trail-item{background:#0f172a;border:1px solid #334155;border-radius:8px;padding:10px 12px}
.trail-item.final{border-color:#f59e0b}
.trail-head{display:flex;flex-wrap:wrap;gap:8px;align-items:center;margin-bottom:4px}
.trail-loc code{font-size:.76rem;color:#38bdf8}
.trail-stage{font-size:.72rem;color:#64748b}
.trail-res{font-size:.72rem;background:#334155;padding:2px 7px;border-radius:4px;font-family:monospace}
.trail-reason{font-size:.85rem;color:#cbd5e1}
.foot{text-align:center;font-size:.76rem;color:#64748b;margin-top:20px}
</style>
CSS;
    }
}
