"""
灵感点分析结果可视化脚本

读取 how/灵感点 目录下的分析结果，结合作者历史帖子详情，生成可视化HTML页面
"""

import json
from pathlib import Path
from typing import Dict, Any, List, Optional
from datetime import datetime
import html as html_module


def load_inspiration_points_data(inspiration_dir: str) -> List[Dict[str, Any]]:
    """
    加载所有灵感点的分析结果

    Args:
        inspiration_dir: 灵感点目录路径

    Returns:
        灵感点分析结果列表
    """
    inspiration_path = Path(inspiration_dir)
    results = []

    # 遍历所有子目录
    for subdir in inspiration_path.iterdir():
        if subdir.is_dir():
            # 查找 all_summary 文件
            summary_files = list(subdir.glob("all_summary_*.json"))
            if summary_files:
                summary_file = summary_files[0]
                try:
                    with open(summary_file, 'r', encoding='utf-8') as f:
                        data = json.load(f)

                    # 加载完整的 step1 和 step2 数据
                    step1_data = None
                    step2_data = None

                    # 直接从当前子目录查找 step1 和 step2 文件
                    step1_files = list(subdir.glob("all_step1_*.json"))
                    step2_files = list(subdir.glob("all_step2_*.json"))

                    if step1_files:
                        try:
                            with open(step1_files[0], 'r', encoding='utf-8') as f:
                                step1_data = json.load(f)
                        except Exception as e:
                            print(f"警告: 读取 {step1_files[0]} 失败: {e}")

                    if step2_files:
                        try:
                            with open(step2_files[0], 'r', encoding='utf-8') as f:
                                step2_data = json.load(f)
                        except Exception as e:
                            print(f"警告: 读取 {step2_files[0]} 失败: {e}")

                    # 加载搜索结果和匹配分数
                    search_results = {}
                    search_dir = subdir / "search"
                    if search_dir.exists() and search_dir.is_dir():
                        search_files = list(search_dir.glob("all_search_*.json"))
                        for search_file in search_files:
                            try:
                                with open(search_file, 'r', encoding='utf-8') as f:
                                    search_data = json.load(f)
                                    # 从JSON内容中读取真实的keyword，而不是从文件名提取
                                    keyword = search_data.get("search_params", {}).get("keyword", "")
                                    if keyword:
                                        # 尝试加载对应的匹配结果文件
                                        match_file = search_dir / "all_step4_搜索结果匹配_gemini-2.5-pro.json"
                                        match_data = None
                                        if match_file.exists():
                                            try:
                                                with open(match_file, 'r', encoding='utf-8') as mf:
                                                    match_data = json.load(mf)
                                            except Exception as e:
                                                print(f"警告: 读取匹配文件 {match_file} 失败: {e}")

                                        search_results[keyword] = {
                                            "search_data": search_data,
                                            "match_data": match_data
                                        }
                                    else:
                                        # 如果JSON中没有keyword，则从文件名提取
                                        keyword = search_file.stem.replace("all_search_", "")
                                        search_results[keyword] = {
                                            "search_data": search_data,
                                            "match_data": None
                                        }
                            except Exception as e:
                                print(f"警告: 读取 {search_file} 失败: {e}")

                    results.append({
                        "summary": data,
                        "step1": step1_data,
                        "step2": step2_data,
                        "search_results": search_results,
                        "inspiration_name": subdir.name
                    })
                except Exception as e:
                    print(f"警告: 读取 {summary_file} 失败: {e}")

    return results


def load_posts_data(posts_dir: str) -> Dict[str, Dict[str, Any]]:
    """
    加载所有帖子详情数据

    Args:
        posts_dir: 帖子目录路径

    Returns:
        帖子ID到帖子详情的映射
    """
    posts_path = Path(posts_dir)
    posts_map = {}

    for post_file in posts_path.glob("*.json"):
        try:
            with open(post_file, 'r', encoding='utf-8') as f:
                post_data = json.load(f)
                post_id = post_data.get("channel_content_id")
                if post_id:
                    posts_map[post_id] = post_data
        except Exception as e:
            print(f"警告: 读取 {post_file} 失败: {e}")

    return posts_map


def generate_post_card_html(post: Dict[str, Any], note_id_prefix: str = "info-post", post_to_mapping_data: Dict[str, Any] = None) -> str:
    """
    生成单个帖子卡片HTML（与搜索结果样式一致）

    Args:
        post: 帖子数据
        note_id_prefix: 帖子ID前缀，用于区分不同区域的帖子
        post_to_mapping_data: 帖子到分类和点映射数据

    Returns:
        HTML字符串
    """
    import html as html_module
    import random

    title = post.get("title", "")
    desc = post.get("body_text", "")
    images = post.get("images", [])
    like_count = post.get("like_count", 0)
    comment_count = post.get("comment_count", 0)
    link = post.get("link", "")
    author = post.get("channel_account_name", "")
    post_id = post.get("channel_content_id", "")
    publish_time = post.get("publish_time", "")

    # 生成唯一的note_id
    note_id = f"{note_id_prefix}-{random.randint(10000, 99999)}"

    # 生成图片轮播HTML
    images_html = ""
    if images and len(images) > 0:
        images_track = "".join([f'<img src="{img}" class="note-image" alt="图片{i+1}">' for i, img in enumerate(images)])

        # 图片导航按钮
        nav_buttons = ""
        if len(images) > 1:
            nav_buttons = f'''
            <button class="note-carousel-button prev" onclick="event.stopPropagation(); moveNoteImage('{note_id}', -1)">‹</button>
            <button class="note-carousel-button next" onclick="event.stopPropagation(); moveNoteImage('{note_id}', 1)">›</button>
            '''

        images_html = f'''
        <div class="note-image-carousel" data-note-id="{note_id}" data-total-images="{len(images)}">
            <div class="note-images-track" id="{note_id}-track">
                {images_track}
            </div>
            {nav_buttons}
        </div>
        '''
    else:
        # 无图片时显示占位符
        images_html = f'''
        <div class="note-image-carousel">
            <div class="note-images-track">
                <div class="note-image" style="display: flex; align-items: center; justify-content: center; background: #f3f4f6; color: #9ca3af;">
                    暂无图片
                </div>
            </div>
        </div>
        '''

    # 准备详情数据
    note_data = {
        "title": title,
        "desc": desc,
        "images": images,
        "link": link,
        "author": author,
        "like_count": like_count,
        "comment_count": comment_count
    }

    # 添加灵感点、关键点、目的点到note_data
    if post_to_mapping_data and post_id and post_id in post_to_mapping_data:
        mapping = post_to_mapping_data[post_id]
        note_data["inspiration_points"] = mapping.get("灵感点列表", [])
        note_data["key_points"] = mapping.get("关键点列表", [])
        note_data["purpose_points"] = mapping.get("目的点列表", [])

    import json
    note_data_json = json.dumps(note_data, ensure_ascii=False)
    note_data_json_escaped = html_module.escape(note_data_json)

    # 获取帖子的灵感点、关键点、目的点
    points_html = ""
    if post_to_mapping_data and post_id and post_id in post_to_mapping_data:
        mapping = post_to_mapping_data[post_id]

        inspiration_points = mapping.get("灵感点列表", [])
        key_points = mapping.get("关键点列表", [])
        purpose_points = mapping.get("目的点列表", [])

        points_sections = []

        # 灵感点
        if inspiration_points:
            insp_items = "".join([
                f'<div class="point-item"><span class="point-name">{html_module.escape(p.get("灵感点", ""))}</span></div>'
                for p in inspiration_points[:3]  # 最多显示3个
            ])
            points_sections.append(f'<div class="points-section"><div class="points-label">灵感点</div><div class="points-list">{insp_items}</div></div>')

        # 关键点
        if key_points:
            key_items = "".join([
                f'<div class="point-item"><span class="point-name">{html_module.escape(k.get("关键点", ""))}</span></div>'
                for k in key_points[:3]  # 最多显示3个
            ])
            points_sections.append(f'<div class="points-section"><div class="points-label">关键点</div><div class="points-list">{key_items}</div></div>')

        # 目的点
        if purpose_points:
            purpose_items = "".join([
                f'<div class="point-item"><span class="point-name">{html_module.escape(p.get("目的点", ""))}</span></div>'
                for p in purpose_points[:3]  # 最多显示3个
            ])
            points_sections.append(f'<div class="points-section"><div class="points-label">目的点</div><div class="points-list">{purpose_items}</div></div>')

        if points_sections:
            points_html = f'<div class="note-points">{"".join(points_sections)}</div>'

    # 生成发布日期HTML
    publish_date_html = ""
    if publish_time:
        publish_date_html = f'<div class="note-publish-date">📅 {html_module.escape(publish_time)}</div>'

    card_html = f'''
    <div class="search-note-item" data-note-data='{note_data_json_escaped}' onclick="showNoteDetail(this)">
        {images_html}
        <div class="note-content">
            <div class="note-title">{html_module.escape(title) if title else "无标题"}</div>
            <div class="note-desc">{html_module.escape(desc) if desc else "暂无描述"}</div>
            {publish_date_html}
            <div class="note-footer">
                <div class="note-author">@{html_module.escape(author) if author else "匿名"}</div>
                <div class="note-stats">
                    <span>👍 {like_count if like_count else 0}</span>
                    {f'<span>💬 {comment_count}</span>' if comment_count else ''}
                </div>
            </div>
        </div>
        {f'<div class="note-points-hover">{points_html}</div>' if points_html else ''}
    </div>
    '''

    return card_html


def generate_inspiration_card_html(
    inspiration_data: Dict[str, Any],
    inspiration_to_post_data: Dict[str, Any] = None,
    category_index_data: Dict[str, Any] = None,
    post_to_mapping_data: Dict[str, Any] = None
) -> str:
    """
    生成单个灵感点的卡片HTML

    Args:
        inspiration_data: 灵感点数据
        inspiration_to_post_data: 点到帖子映射数据
        category_index_data: 分类索引数据

    Returns:
        HTML字符串
    """
    summary = inspiration_data.get("summary", {})
    step1 = inspiration_data.get("step1", {})
    inspiration_name = inspiration_data.get("inspiration_name", "未知灵感")
    search_results = inspiration_data.get("search_results", {})

    # 提取关键指标
    metrics = summary.get("关键指标", {})
    step1_score = metrics.get("step1_top1_score", 0)
    step1_match_element = metrics.get("step1_top1_匹配要素", "")

    # 确定卡片颜色（基于Step1分数）
    if step1_score >= 0.7:
        border_color = "#10b981"
        step1_color = "#10b981"
    elif step1_score >= 0.5:
        border_color = "#f59e0b"
        step1_color = "#f59e0b"
    elif step1_score >= 0.3:
        border_color = "#3b82f6"
        step1_color = "#3b82f6"
    else:
        border_color = "#ef4444"
        step1_color = "#ef4444"

    # 转义HTML
    inspiration_name_escaped = html_module.escape(inspiration_name)
    step1_match_element_escaped = html_module.escape(step1_match_element)

    # 获取Step1匹配结果（简要展示）
    step1_matches = step1.get("匹配结果列表", []) if step1 else []
    step1_match_preview = ""
    if step1_matches:
        top_match = step1_matches[0]
        # 从新的数据结构中提取信息
        input_info = top_match.get("输入信息", {})
        match_result = top_match.get("匹配结果", {})
        element_name = input_info.get("A", "")
        match_score = match_result.get("score", 0)
        same_parts = match_result.get("相同部分", {}) or {}
        increment_parts = match_result.get("增量部分", {}) or {}

        # 生成相同部分和增量部分的HTML
        parts_html = ""
        if same_parts:
            same_items = [f"{html_module.escape(k)}" for k in same_parts.keys()]
            parts_html += f'<div class="preview-parts same"><strong>相同:</strong> {", ".join(same_items)}</div>'

        if increment_parts:
            inc_items = [f"{html_module.escape(k)}" for k in increment_parts.keys()]
            parts_html += f'<div class="preview-parts increment"><strong>增量:</strong> {", ".join(inc_items)}</div>'

        step1_match_preview = f'''
        <div class="match-preview">
            <div class="match-preview-header">🎯 Step1 Top1匹配</div>
            <div class="match-preview-content">
                <span class="match-preview-name">{html_module.escape(element_name)}</span>
                <span class="match-preview-score" style="color: {step1_color};">{match_score:.2f}</span>
            </div>
            {parts_html}
        </div>
        '''

    # 提取 top3 匹配信息
    top3_matches = []
    if step1:
        matches = step1.get("匹配结果列表", [])
        for i, match in enumerate(matches[:3]):
            input_info = match.get("输入信息", {})
            match_result = match.get("匹配结果", {})
            element_name = input_info.get("A", "")
            match_score = match_result.get("score", 0)
            context = input_info.get("A_Context", "")

            # 解析层级关系
            hierarchy = []
            if context:
                lines = context.split("\n")
                for line in lines:
                    if ":" in line:
                        key, value = line.split(":", 1)
                        key = key.strip()
                        value = value.strip()
                        if key in ["所属视角", "一级分类", "二级分类"]:
                            hierarchy.append(value)

            top3_matches.append({
                "rank": i + 1,
                "name": element_name,
                "score": match_score,
                "hierarchy": hierarchy
            })

    # 准备详细数据用于弹窗
    detail_data_json = json.dumps(inspiration_data, ensure_ascii=False)
    detail_data_json_escaped = html_module.escape(detail_data_json)

    # top3 匹配数据
    top3_json = json.dumps(top3_matches, ensure_ascii=False)
    top3_json_escaped = html_module.escape(top3_json)

    # 生成详细HTML并进行HTML转义
    detail_html = generate_detail_html(inspiration_data)
    detail_html_escaped = html_module.escape(detail_html)

    # 生成匹配列表HTML
    matches_html = ""
    if step1:
        step1_matches = step1.get("匹配结果列表", [])
        for idx, match in enumerate(step1_matches):
            input_info = match.get("输入信息", {})
            match_result = match.get("匹配结果", {})

            element_name = input_info.get("A", "")
            context = input_info.get("A_Context", "")
            score = match_result.get("score", 0)
            score_explain = match_result.get("score说明", "") or ""
            same_parts = match_result.get("相同部分", {}) or {}
            increment_parts = match_result.get("增量部分", {}) or {}

            # 为搜索结果容器生成唯一ID
            safe_insp_name = ''.join(c if c.isalnum() else '_' for c in inspiration_name)
            unique_match_id = f"{safe_insp_name}-match-{idx}"

            # 解析层级
            hierarchy = []
            if context:
                lines = context.split("\n")
                for line in lines:
                    if ":" in line:
                        key, value = line.split(":", 1)
                        key = key.strip()
                        value = value.strip()
                        if key in ["所属视角", "一级分类", "二级分类"]:
                            hierarchy.append(value)

            hierarchy_html = " › ".join([html_module.escape(h) for h in hierarchy]) if hierarchy else ""

            rank_class = f"rank-{idx + 1}" if idx < 3 else ""

            # 相同部分HTML
            same_parts_html = ""
            if same_parts:
                same_items = "".join([f'<div class="part-item"><span class="part-key">{html_module.escape(k)}:</span><span class="part-value">{html_module.escape(v)}</span></div>' for k, v in same_parts.items()])
                same_parts_html = f'''
                <div class="match-parts same-parts">
                    <div class="match-parts-title">✅ 相同部分</div>
                    {same_items}
                </div>
                '''

            # 增量部分HTML
            increment_parts_html = ""
            if increment_parts:
                inc_items = "".join([f'<div class="part-item"><span class="part-key">{html_module.escape(k)}:</span><span class="part-value">{html_module.escape(v)}</span></div>' for k, v in increment_parts.items()])
                increment_parts_html = f'''
                <div class="match-parts increment-parts">
                    <div class="match-parts-title">➕ 增量部分</div>
                    {inc_items}
                </div>
                '''

            # 生成搜索结果HTML（网格展示，图片轮播）
            search_html = ""
            if element_name in search_results:
                result_obj = search_results[element_name]
                search_data = result_obj.get("search_data", {})
                match_data = result_obj.get("match_data", None)

                search_params = search_data.get("search_params", {})
                notes = search_data.get("notes", [])
                notes_count = len(notes)

                # 构建匹配分数字典 {channel_content_id: match_info}
                match_scores = {}
                if match_data and "匹配结果列表" in match_data:
                    for match_item in match_data["匹配结果列表"]:
                        business_info = match_item.get("业务信息", {})
                        input_info = match_item.get("输入信息", {})
                        content_id = business_info.get("channel_content_id", "")
                        if content_id:
                            match_scores[content_id] = {
                                "score": match_item.get("匹配结果", {}).get("score", 0),
                                "score说明": match_item.get("匹配结果", {}).get("score说明", "") or "",
                                "相同部分": match_item.get("匹配结果", {}).get("相同部分", {}) or {},
                                "增量部分": match_item.get("匹配结果", {}).get("增量部分", {}) or {},
                                "输入B": input_info.get("B", "") or "",
                                "输入A": input_info.get("A", "") or "",
                                "B_Context": input_info.get("B_Context", "") or "",
                                "A_Context": input_info.get("A_Context", "") or ""
                            }

                # 为notes添加匹配分数、原始索引，并准备排序
                notes_with_scores = []
                for original_idx, note in enumerate(notes):
                    note_id = note.get("channel_content_id", "")
                    score_info = match_scores.get(note_id, None)
                    notes_with_scores.append({
                        "note": note,
                        "score_info": score_info,
                        "original_index": original_idx,  # 原始搜索结果位置（0-based）
                        "page": (original_idx // 20) + 1,  # 第几页（假设每页20条）
                        "position_in_page": (original_idx % 20) + 1  # 页内位置
                    })

                # 默认按分数降序排序（没有分数的放到最后）
                notes_with_scores.sort(key=lambda x: x["score_info"]["score"] if x["score_info"] else -1, reverse=True)

                # 生成搜索参数HTML
                search_params_html = ""
                if search_params:
                    keyword = search_params.get("keyword", "")
                    content_type = search_params.get("content_type", "不限")
                    sort_type = search_params.get("sort_type", "综合")
                    publish_time = search_params.get("publish_time", "不限")

                    search_params_html = f'''
                    <div class="search-params-section">
                        <div class="search-params-title">🔍 搜索参数</div>
                        <div class="search-params-grid">
                            <div class="search-param-item">
                                <span class="search-param-label">关键词：</span>
                                <span class="search-param-value">{html_module.escape(keyword)}</span>
                            </div>
                            <div class="search-param-item">
                                <span class="search-param-label">内容类型：</span>
                                <span class="search-param-value">{html_module.escape(content_type)}</span>
                            </div>
                            <div class="search-param-item">
                                <span class="search-param-label">排序方式：</span>
                                <span class="search-param-value">{html_module.escape(sort_type)}</span>
                            </div>
                            <div class="search-param-item">
                                <span class="search-param-label">发布时间：</span>
                                <span class="search-param-value">{html_module.escape(publish_time)}</span>
                            </div>
                        </div>
                    </div>
                    '''

                # 生成搜索结果统计HTML（带排序按钮）
                search_summary_html = f'''
                <div class="search-summary-section">
                    <div class="search-summary-header">
                        <div class="search-summary-left">
                            <div class="search-summary-title">📊 搜索结果</div>
                            <div class="search-summary-content">
                                共找到 <span class="search-result-count">{notes_count}</span> 条相关内容
                            </div>
                        </div>
                        <div class="search-sort-buttons">
                            <span class="search-sort-label">排序：</span>
                            <button class="search-sort-btn active" data-sort="score" onclick="sortSearchResults(this, '{unique_match_id}-search-results')">匹配分数</button>
                            <button class="search-sort-btn" data-sort="original" onclick="sortSearchResults(this, '{unique_match_id}-search-results')">原始顺序</button>
                            <button class="search-sort-btn" data-sort="likes" onclick="sortSearchResults(this, '{unique_match_id}-search-results')">点赞数</button>
                        </div>
                    </div>
                </div>
                '''

                if notes_count > 0:
                    notes_items = ""
                    for note_idx, item in enumerate(notes_with_scores):  # 使用包含元数据的列表
                        note = item["note"]
                        score_info = item["score_info"]
                        original_index = item["original_index"]
                        page = item["page"]
                        position_in_page = item["position_in_page"]

                        title = note.get("title", "")
                        desc = note.get("desc", "")
                        link = note.get("link", "")
                        author = note.get("channel_account_name", "")
                        like_count = note.get("like_count", 0)
                        comment_count = note.get("comment_count", 0)
                        images = note.get("images", [])
                        content_id = note.get("channel_content_id", "")
                        publish_time = note.get("publish_time", "")

                        note_id = f"note-{idx}-{note_idx}"

                        # 生成图片轮播HTML
                        images_html = ""
                        if images and len(images) > 0:
                            images_track = "".join([f'<img src="{img}" class="note-image" alt="图片{i+1}">' for i, img in enumerate(images)])

                            # 图片导航按钮和指示点
                            nav_buttons = ""
                            dots_html = ""
                            if len(images) > 1:
                                nav_buttons = f'''
                                <button class="note-carousel-button prev" onclick="event.stopPropagation(); moveNoteImage('{note_id}', -1)">‹</button>
                                <button class="note-carousel-button next" onclick="event.stopPropagation(); moveNoteImage('{note_id}', 1)">›</button>
                                '''
                                dots = "".join([f'<div class="note-image-dot{" active" if i == 0 else ""}"></div>' for i in range(len(images))])
                                dots_html = f'<div class="note-image-dots" id="{note_id}-dots">{dots}</div>'

                            images_html = f'''
                            <div class="note-image-carousel" data-note-id="{note_id}" data-total-images="{len(images)}">
                                <div class="note-images-track" id="{note_id}-track">
                                    {images_track}
                                </div>
                                {nav_buttons}
                                {dots_html}
                            </div>
                            '''
                        else:
                            # 无图片时显示占位符
                            images_html = f'''
                            <div class="note-image-carousel">
                                <div class="note-images-track">
                                    <div class="note-image" style="display: flex; align-items: center; justify-content: center; background: #f3f4f6; color: #9ca3af;">
                                        暂无图片
                                    </div>
                                </div>
                            </div>
                            '''

                        # 准备详情数据
                        note_data = {
                            "title": title,
                            "desc": desc,
                            "link": link,
                            "author": author,
                            "like_count": like_count,
                            "comment_count": comment_count,
                            "images": images
                        }
                        note_data_json = json.dumps(note_data, ensure_ascii=False)
                        note_data_escaped = html_module.escape(note_data_json)

                        # 生成匹配分数HTML
                        score_badge_html = ""
                        score_detail_html = ""
                        if score_info:
                            note_score = score_info["score"]
                            note_score_explain = score_info.get("score说明", "") or ""
                            note_same_parts = score_info.get("相同部分", {}) or {}
                            note_increment_parts = score_info.get("增量部分", {}) or {}
                            input_b = score_info.get("输入B", "") or ""
                            input_a = score_info.get("输入A", "") or ""
                            b_context = score_info.get("B_Context", "") or ""
                            a_context = score_info.get("A_Context", "") or ""

                            # 分数详情JSON
                            score_detail_data = {
                                "score": note_score,
                                "score说明": note_score_explain,
                                "相同部分": note_same_parts,
                                "增量部分": note_increment_parts,
                                "输入B": input_b,
                                "输入A": input_a,
                                "B_Context": b_context,
                                "A_Context": a_context
                            }
                            score_detail_json = json.dumps(score_detail_data, ensure_ascii=False)
                            score_detail_escaped = html_module.escape(score_detail_json)

                            score_badge_html = f'''
                            <div class="note-score-badge" onclick="event.stopPropagation(); showScoreDetail(this)" data-score-detail='{score_detail_escaped}'>
                                <span class="score-label">匹配分数</span>
                                <span class="score-value">{note_score:.2f}</span>
                            </div>
                            '''

                        # 生成发布日期HTML
                        publish_date_html = ""
                        if publish_time:
                            publish_date_html = f'<div class="note-publish-date">📅 {html_module.escape(publish_time)}</div>'

                        # 计算匹配分数（用于排序）
                        sort_score = score_info["score"] if score_info else -1

                        notes_items += f'''
                        <div class="search-note-item"
                             data-note-data='{note_data_escaped}'
                             data-original-index="{original_index}"
                             data-score="{sort_score}"
                             data-likes="{like_count}"
                             onclick="showNoteDetail(this)">
                            {score_badge_html}
                            {images_html}
                            <div class="note-content">
                                <div class="note-title">{html_module.escape(title) if title else "无标题"}</div>
                                <div class="note-desc">{html_module.escape(desc) if desc else "暂无描述"}</div>
                                {publish_date_html}
                                <div class="note-footer">
                                    <div class="note-author">@{html_module.escape(author)}</div>
                                    <div class="note-position">P{page}-{position_in_page}</div>
                                    <div class="note-stats">
                                        <span>👍 {like_count}</span>
                                        <span>💬 {comment_count}</span>
                                    </div>
                                </div>
                            </div>
                        </div>
                        '''

                    search_html = f'''
                    <div class="search-results-section">
                        {search_params_html}
                        {search_summary_html}
                        <div class="search-notes-list" id="{unique_match_id}-search-results">
                            {notes_items}
                        </div>
                    </div>
                    '''
                else:
                    # 没有搜索结果时也显示参数
                    search_html = f'''
                    <div class="search-results-section">
                        {search_params_html}
                        {search_summary_html}
                    </div>
                    '''

            # 只有第一个匹配项默认展开
            expanded_class = " expanded" if idx == 0 else ""

            # 生成当前匹配项的灵感点和灵感分类详情
            match_info_section = ""
            if inspiration_to_post_data and category_index_data:
                # 获取灵感点数据
                inspiration_points = inspiration_to_post_data.get("点到帖子映射", {}).get("灵感点", {})
                inspiration_info = inspiration_points.get(inspiration_name, {})

                # 获取当前匹配的灵感分类数据
                categories = category_index_data.get("灵感分类", {})
                category_info = categories.get(element_name, {})

                # 生成灵感点详情HTML（左栏）
                insp_detail_html = ""
                if inspiration_info:
                    insp_dimension = inspiration_info.get("维度", "")
                    insp_desc = inspiration_info.get("描述", "")
                    insp_posts = inspiration_info.get("帖子详情列表", [])

                    # 生成帖子卡片
                    insp_posts_html = ""
                    for post in insp_posts[:6]:  # 最多显示6个
                        insp_posts_html += generate_post_card_html(post, f"{unique_match_id}-insp-post", post_to_mapping_data)

                    insp_detail_html = f'''
                    <div class="info-detail-column">
                        <div class="info-header">
                            <span class="info-label">[灵感点]</span>
                            <span class="info-name">{html_module.escape(inspiration_name)}</span>
                        </div>
                        <div class="info-content">
                            {f'<div class="info-field"><span class="info-field-label">维度：</span><span class="info-field-value">{html_module.escape(insp_dimension)}</span></div>' if insp_dimension else ''}
                            {f'<div class="info-field"><span class="info-field-label">描述：</span><span class="info-field-value">{html_module.escape(insp_desc)}</span></div>' if insp_desc else ''}
                            {f'<div class="info-posts"><div class="info-posts-title">相关帖子</div><div class="info-posts-grid">{insp_posts_html}</div></div>' if insp_posts_html else ''}
                        </div>
                    </div>
                    '''

                # 生成灵感分类详情HTML（右栏）
                cat_detail_html = ""
                if category_info:
                    cat_level = category_info.get("分类层级", "")
                    cat_definition = category_info.get("分类定义", "")
                    cat_posts = category_info.get("帖子详情列表", [])

                    # 生成帖子卡片
                    cat_posts_html = ""
                    for post in cat_posts[:6]:  # 最多显示6个
                        cat_posts_html += generate_post_card_html(post, f"{unique_match_id}-cat-post", post_to_mapping_data)

                    cat_detail_html = f'''
                    <div class="info-detail-column">
                        <div class="info-header">
                            <span class="info-label">[灵感分类]</span>
                            <span class="info-name">{html_module.escape(element_name)}</span>
                        </div>
                        <div class="info-content">
                            {f'<div class="info-field"><span class="info-field-label">层级：</span><span class="info-field-value">{html_module.escape(cat_level)}</span></div>' if cat_level else ''}
                            {f'<div class="info-field"><span class="info-field-label">定义：</span><span class="info-field-value">{html_module.escape(cat_definition)}</span></div>' if cat_definition else ''}
                            {f'<div class="info-posts"><div class="info-posts-title">相关帖子</div><div class="info-posts-grid">{cat_posts_html}</div></div>' if cat_posts_html else ''}
                        </div>
                    </div>
                    '''

                if insp_detail_html or cat_detail_html:
                    match_info_section = f'''
                    <div class="inspiration-category-section">
                        {insp_detail_html}
                        {cat_detail_html}
                    </div>
                    '''

            # 步骤1：灵感点匹配灵感分类
            step1_html = f'''
            <div class="step-section-wrapper step-1-wrapper expanded">
                <div class="step-header" onclick="toggleStepWrapper(this)">
                    <div class="step-header-content">
                        <span class="step-number-badge">步骤 1</span>
                        <span class="step-title">灵感点匹配灵感分类</span>
                    </div>
                    <div class="step-toggle">▼</div>
                </div>
                <div class="step-wrapper-content">
                    <div class="match-analysis-section" id="{unique_match_id}-step1" data-step-name="灵感点匹配灵感分类">
                        <div class="match-parts-container">
                            <div class="match-parts-column">
                                {same_parts_html}
                            </div>
                            <div class="match-parts-column">
                                {increment_parts_html}
                            </div>
                        </div>
                        {f'<div class="match-explain"><div class="match-explain-title">💡 分数说明</div><div class="match-explain-text">{html_module.escape(score_explain)}</div></div>' if score_explain else ''}
                    </div>
                </div>
            </div>
            '''

            # 步骤2：搜索结果（如果有）
            step2_html = ""
            if search_html:
                step2_html = f'''
                <div class="step-section-wrapper step-2-wrapper expanded">
                    <div class="step-header" onclick="toggleStepWrapper(this)">
                        <div class="step-header-content">
                            <span class="step-number-badge">步骤 2</span>
                            <span class="step-title">搜索</span>
                        </div>
                        <div class="step-toggle">▼</div>
                    </div>
                    <div class="step-wrapper-content">
                        <div class="step-section expanded" data-step="2" id="{unique_match_id}-step2" data-step-name="灵感分类搜索">
                            <div class="step-section-header" onclick="toggleStep(this)">
                                <div class="step-section-title">
                                    <span class="step-sub-number">2.1</span>
                                    <span>直接搜索灵感分类</span>
                                </div>
                                <div class="step-toggle">▼</div>
                            </div>
                            <div class="step-section-content">
                                {search_html}
                            </div>
                        </div>
                    </div>
                </div>
                '''

            # 创建安全的ID（移除特殊字符）
            safe_element_id = ''.join(c if c.isalnum() or c in '_-' else '_' for c in element_name)

            matches_html += f'''
            <div class="match-item{expanded_class}" data-index="{idx}" id="{unique_match_id}" data-match-name="{html_module.escape(element_name)}">
                <div class="match-main-header" onclick="toggleMainMatch(this)">
                    <div class="match-header-row">
                        <div class="match-header-left">
                            <span class="match-rank {rank_class}">Top {idx + 1}</span>
                            <span class="detail-label">[灵感点]</span>
                            <span class="match-title">{html_module.escape(inspiration_name)}</span>
                        </div>
                        <div class="match-header-center">
                            <span class="match-score-label">匹配分数</span>
                            <span class="match-score-value">{score:.2f}</span>
                        </div>
                        <div class="match-header-right">
                            <span class="detail-label">[灵感分类]</span>
                            <span class="match-category">{html_module.escape(element_name)}</span>
                        </div>
                    </div>
                    <div class="match-toggle-main">▼</div>
                </div>
                <div class="match-main-content">
                    {match_info_section}
                    {step1_html}
                    {step2_html}
                </div>
            </div>
            '''

    # 获取top1匹配的灵感分类名称（用于顶部标题）
    top1_category_name = ""
    if step1:
        step1_matches = step1.get("匹配结果列表", [])
        if step1_matches:
            top_match = step1_matches[0]
            input_info = top_match.get("输入信息", {})
            top1_category_name = input_info.get("A", "")

    html = f'''
    <div class="inspiration-detail"
         data-inspiration-name="{inspiration_name_escaped}"
         data-step1-score="{step1_score}"
         data-top3-matches="{top3_json_escaped}">
        <div class="breadcrumb-container">
            <div class="breadcrumb" id="dynamicBreadcrumb">
                <span class="breadcrumb-item"><span class="breadcrumb-label">[灵感点]</span> {inspiration_name_escaped}</span>
            </div>
        </div>
        <div class="inspiration-content-wrapper">
            <div class="matches-list">
                {matches_html}
            </div>
        </div>
    </div>
    '''

    return html


def generate_detail_html(inspiration_data: Dict[str, Any]) -> str:
    """
    生成灵感点的详细信息HTML

    Args:
        inspiration_data: 灵感点数据

    Returns:
        详细信息的HTML字符串
    """
    import html as html_module

    summary = inspiration_data.get("summary", {})
    step1 = inspiration_data.get("step1", {})
    step2 = inspiration_data.get("step2", {})
    inspiration_name = inspiration_data.get("inspiration_name", "未知灵感")

    content = f'''
        <div class="modal-header">
            <h2 class="modal-title">{html_module.escape(inspiration_name)}</h2>
        </div>
    '''

    # 获取元数据，用于后面的日志链接
    metadata = summary.get("元数据", {})

    # Step1 详细信息
    if step1 and step1.get("灵感"):
        inspiration = step1.get("灵感", "")
        matches = step1.get("匹配结果列表", [])

        content += f'''
            <div class="modal-section">
                <h3>🎯 Step1: 灵感人设匹配</h3>
                <div class="step-content">
                    <div class="step-field">
                        <span class="step-field-label">灵感内容:</span>
                        <span class="step-field-value">{html_module.escape(inspiration)}</span>
                    </div>
        '''

        # 显示匹配结果（只显示Top1）
        if matches:
            content += f'''
                <div class="step-field">
                    <span class="step-field-label">Top1匹配结果:</span>
                    <div class="matches-list">
            '''

            for index, match in enumerate(matches[:1]):
                input_info = match.get("输入信息", {})
                match_result = match.get("匹配结果", {})

                element_a = input_info.get("A", "")
                context_a = input_info.get("A_Context", "")
                score = match_result.get("score", 0)
                score_explain = match_result.get("score说明", "") or ""
                same_parts = match_result.get("相同部分", {}) or {}
                increment_parts = match_result.get("增量部分", {}) or {}

                content += f'''
                    <div class="match-item">
                        <div class="match-header">
                            <span class="match-element-name">{html_module.escape(element_a)}</span>
                            <span class="match-score">{score:.2f}</span>
                        </div>
                '''

                if context_a:
                    content += f'<div class="match-context"><strong>📍 所属分类:</strong> {html_module.escape(context_a).replace(chr(10), "<br>")}</div>'

                if score_explain:
                    content += f'<div class="match-explain"><strong>💡 分数说明:</strong> {html_module.escape(score_explain)}</div>'

                # 相同部分
                if same_parts:
                    content += '''
                        <div class="match-parts same-parts">
                            <div class="parts-header">✅ 相同部分</div>
                            <div class="parts-content">
                    '''
                    for key, value in same_parts.items():
                        content += f'''
                            <div class="part-item">
                                <span class="part-key">{html_module.escape(key)}:</span>
                                <span class="part-value">{html_module.escape(value)}</span>
                            </div>
                        '''
                    content += '''
                            </div>
                        </div>
                    '''

                # 增量部分
                if increment_parts:
                    content += '''
                        <div class="match-parts increment-parts">
                            <div class="parts-header">➕ 增量部分</div>
                            <div class="parts-content">
                    '''
                    for key, value in increment_parts.items():
                        content += f'''
                            <div class="part-item">
                                <span class="part-key">{html_module.escape(key)}:</span>
                                <span class="part-value">{html_module.escape(value)}</span>
                            </div>
                        '''
                    content += '''
                            </div>
                        </div>
                    '''

                content += '''
                    </div>
                '''

            content += '''
                    </div>
                </div>
            '''

        content += '''
                </div>
            </div>
        '''

    # 日志链接
    if metadata.get("log_url"):
        content += f'''
            <div class="modal-link">
                <a href="{metadata["log_url"]}" target="_blank" class="modal-link-btn">
                    🔗 查看详细日志
                </a>
            </div>
        '''

    return content


def generate_detail_modal_content_js() -> str:
    """
    生成详情弹窗内容的JavaScript函数

    Returns:
        JavaScript代码字符串
    """
    return '''
    // 笔记图片当前索引管理
    const noteImageStates = {};

    // 移动笔记图片
    function moveNoteImage(noteId, direction) {
        if (!noteImageStates[noteId]) {
            noteImageStates[noteId] = 0;
        }

        const carousel = document.querySelector(`[data-note-id="${noteId}"]`);
        if (!carousel) return;

        const totalImages = parseInt(carousel.dataset.totalImages);
        const track = document.getElementById(noteId + '-track');
        if (!track) return;

        let newIndex = noteImageStates[noteId] + direction;
        if (newIndex < 0) newIndex = 0;
        if (newIndex >= totalImages) newIndex = totalImages - 1;

        noteImageStates[noteId] = newIndex;

        // 移动轨道
        track.style.transform = `translateX(-${newIndex * 100}%)`;

        // 更新指示点
        const dots = document.querySelectorAll(`#${noteId}-dots .note-image-dot`);
        dots.forEach((dot, i) => {
            dot.classList.toggle('active', i === newIndex);
        });

        // 更新按钮状态
        const prevBtn = carousel.querySelector('.note-carousel-button.prev');
        const nextBtn = carousel.querySelector('.note-carousel-button.next');
        if (prevBtn) {
            prevBtn.classList.toggle('disabled', newIndex === 0);
        }
        if (nextBtn) {
            nextBtn.classList.toggle('disabled', newIndex >= totalImages - 1);
        }
    }

    // 显示笔记详情
    function showNoteDetail(element) {
        const noteDataStr = element.dataset.noteData;
        if (!noteDataStr) return;

        try {
            const noteData = JSON.parse(noteDataStr);

            // 生成图片HTML
            let imagesHtml = '';
            if (noteData.images && noteData.images.length > 0) {
                imagesHtml = noteData.images.map(img =>
                    `<img src="${img}" class="note-detail-image" alt="图片">`
                ).join('');
            } else {
                imagesHtml = '<div style="text-align: center; color: #9ca3af; padding: 40px;">暂无图片</div>';
            }

            // 生成灵感点、关键点、目的点HTML
            let pointsDetailHtml = '';
            const hasPoints = (noteData.inspiration_points && noteData.inspiration_points.length > 0) ||
                            (noteData.key_points && noteData.key_points.length > 0) ||
                            (noteData.purpose_points && noteData.purpose_points.length > 0);

            if (hasPoints) {
                let sections = [];

                // 灵感点
                if (noteData.inspiration_points && noteData.inspiration_points.length > 0) {
                    const items = noteData.inspiration_points.map(p =>
                        `<div class="detail-point-item">
                            <div class="detail-point-name">${p.灵感点 || ''}</div>
                            ${p.描述 ? `<div class="detail-point-desc">${p.描述}</div>` : ''}
                        </div>`
                    ).join('');
                    sections.push(`<div class="detail-points-section">
                        <div class="detail-points-title">💡 灵感点</div>
                        ${items}
                    </div>`);
                }

                // 关键点
                if (noteData.key_points && noteData.key_points.length > 0) {
                    const items = noteData.key_points.map(k =>
                        `<div class="detail-point-item">
                            <div class="detail-point-name">${k.关键点 || ''}</div>
                            ${k.描述 ? `<div class="detail-point-desc">${k.描述}</div>` : ''}
                        </div>`
                    ).join('');
                    sections.push(`<div class="detail-points-section">
                        <div class="detail-points-title">🔑 关键点</div>
                        ${items}
                    </div>`);
                }

                // 目的点
                if (noteData.purpose_points && noteData.purpose_points.length > 0) {
                    const items = noteData.purpose_points.map(p =>
                        `<div class="detail-point-item">
                            <div class="detail-point-name">${p.目的点 || ''}</div>
                            ${p.描述 ? `<div class="detail-point-desc">${p.描述}</div>` : ''}
                        </div>`
                    ).join('');
                    sections.push(`<div class="detail-points-section">
                        <div class="detail-points-title">🎯 目的点</div>
                        ${items}
                    </div>`);
                }

                pointsDetailHtml = `<div class="note-detail-points">${sections.join('')}</div>`;
            }

            const modalHtml = `
                <div class="note-detail-content">
                    <button class="note-detail-close" onclick="closeNoteDetail()">×</button>
                    <div class="note-detail-header">
                        <div class="note-detail-title">${noteData.title || '无标题'}</div>
                        <div class="note-detail-meta">
                            <span class="note-detail-author">@${noteData.author}</span>
                            <div class="note-detail-stats">
                                <span>👍 ${noteData.like_count}</span>
                                <span>💬 ${noteData.comment_count}</span>
                            </div>
                        </div>
                    </div>
                    <div class="note-detail-body">
                        ${noteData.desc ? `<div class="note-detail-desc">${noteData.desc}</div>` : ''}
                        ${pointsDetailHtml}
                        <div class="note-detail-images">
                            ${imagesHtml}
                        </div>
                    </div>
                    <div class="note-detail-footer">
                        <a href="${noteData.link}" target="_blank" class="note-detail-link">
                            在小红书查看完整内容 →
                        </a>
                    </div>
                </div>
            `;

            let modal = document.getElementById('noteDetailModal');
            if (!modal) {
                modal = document.createElement('div');
                modal.id = 'noteDetailModal';
                modal.className = 'note-detail-modal';
                modal.onclick = (e) => {
                    if (e.target === modal) closeNoteDetail();
                };
                document.body.appendChild(modal);
            }

            modal.innerHTML = modalHtml;
            modal.classList.add('active');
            document.body.style.overflow = 'hidden';
        } catch (e) {
            console.error('Error parsing note data:', e);
        }
    }

    // 关闭笔记详情
    function closeNoteDetail() {
        const modal = document.getElementById('noteDetailModal');
        if (modal) {
            modal.classList.remove('active');
            document.body.style.overflow = '';
        }
    }

    // 显示分数详情
    function showScoreDetail(element) {
        const scoreDetailStr = element.dataset.scoreDetail;
        if (!scoreDetailStr) return;

        try {
            const scoreData = JSON.parse(scoreDetailStr);
            const modal = document.getElementById('scoreDetailModal');
            const modalBody = document.getElementById('scoreModalBody');

            // 生成相同部分HTML
            let samePartsHTML = '';
            if (scoreData.相同部分 && Object.keys(scoreData.相同部分).length > 0) {
                const sameItems = Object.entries(scoreData.相同部分).map(([key, value]) =>
                    `<div class="score-part-item"><span class="score-part-key">${key}:</span><span class="score-part-value">${value}</span></div>`
                ).join('');
                samePartsHTML = `
                    <div class="score-parts same-parts">
                        <div class="score-parts-title">✅ 相同部分</div>
                        ${sameItems}
                    </div>
                `;
            }

            // 生成增量部分HTML
            let incrementPartsHTML = '';
            if (scoreData.增量部分 && Object.keys(scoreData.增量部分).length > 0) {
                const incItems = Object.entries(scoreData.增量部分).map(([key, value]) =>
                    `<div class="score-part-item"><span class="score-part-key">${key}:</span><span class="score-part-value">${value}</span></div>`
                ).join('');
                incrementPartsHTML = `
                    <div class="score-parts increment-parts">
                        <div class="score-parts-title">➕ 增量部分</div>
                        ${incItems}
                    </div>
                `;
            }

            // 生成分数说明HTML
            let explainHTML = '';
            if (scoreData.score说明) {
                explainHTML = `
                    <div class="score-detail-explain">
                        <div class="score-explain-title">💡 分数说明</div>
                        <div class="score-explain-text">${scoreData.score说明}</div>
                    </div>
                `;
            }

            // 生成输入信息HTML
            let inputInfoHTML = '';
            if (scoreData.输入B || scoreData.输入A) {
                inputInfoHTML = `
                    <div class="score-input-info">
                        <div class="score-input-title">📝 输入信息</div>
                        ${scoreData.输入B ? `
                            <div class="score-input-item">
                                <div class="score-input-label">输入B（灵感点）:</div>
                                <div class="score-input-value">${scoreData.输入B}</div>
                            </div>
                        ` : ''}
                        ${scoreData.B_Context ? `
                            <div class="score-input-item">
                                <div class="score-input-label">B_Context:</div>
                                <div class="score-input-value">${scoreData.B_Context}</div>
                            </div>
                        ` : ''}
                        ${scoreData.输入A ? `
                            <div class="score-input-item">
                                <div class="score-input-label">输入A（帖子标题）:</div>
                                <div class="score-input-value">${scoreData.输入A}</div>
                            </div>
                        ` : ''}
                        ${scoreData.A_Context ? `
                            <div class="score-input-item">
                                <div class="score-input-label">A_Context（帖子内容摘要）:</div>
                                <div class="score-input-value">${scoreData.A_Context}</div>
                            </div>
                        ` : ''}
                    </div>
                `;
            }

            modalBody.innerHTML = `
                <div class="score-detail-container">
                    <h2 class="score-detail-title">匹配分数详情</h2>
                    <div class="score-detail-score">
                        <span class="score-detail-label">匹配分数：</span>
                        <span class="score-detail-value">${scoreData.score.toFixed(2)}</span>
                    </div>
                    ${inputInfoHTML}
                    ${explainHTML}
                    <div class="score-parts-container">
                        ${samePartsHTML}
                        ${incrementPartsHTML}
                    </div>
                </div>
            `;

            modal.classList.add('active');
            document.body.style.overflow = 'hidden';
        } catch (e) {
            console.error('Failed to parse score detail:', e);
        }
    }

    // 关闭分数详情
    function closeScoreDetail() {
        const modal = document.getElementById('scoreDetailModal');
        if (modal) {
            modal.classList.remove('active');
            document.body.style.overflow = '';
        }
    }

    // 点击Modal背景关闭分数详情
    function closeScoreDetailModal(event) {
        if (event.target.id === 'scoreDetailModal') {
            closeScoreDetail();
        }
    }

    // 搜索结果排序
    function sortSearchResults(button, containerId) {
        const sortType = button.dataset.sort;
        const container = document.getElementById(containerId);
        if (!container) return;

        // 更新按钮状态
        const allButtons = button.parentElement.querySelectorAll('.search-sort-btn');
        allButtons.forEach(btn => btn.classList.remove('active'));
        button.classList.add('active');

        // 获取所有搜索结果卡片
        const items = Array.from(container.querySelectorAll('.search-note-item'));

        // 根据排序类型排序
        items.sort((a, b) => {
            if (sortType === 'score') {
                const scoreA = parseFloat(a.dataset.score) || -1;
                const scoreB = parseFloat(b.dataset.score) || -1;
                return scoreB - scoreA; // 降序
            } else if (sortType === 'original') {
                const indexA = parseInt(a.dataset.originalIndex) || 0;
                const indexB = parseInt(b.dataset.originalIndex) || 0;
                return indexA - indexB; // 升序
            } else if (sortType === 'likes') {
                const likesA = parseInt(a.dataset.likes) || 0;
                const likesB = parseInt(b.dataset.likes) || 0;
                return likesB - likesA; // 降序
            }
            return 0;
        });

        // 重新排列DOM
        items.forEach(item => container.appendChild(item));
    }

    // ESC键关闭详情
    document.addEventListener('keydown', function(event) {
        if (event.key === 'Escape') {
            closeNoteDetail();
            closeScoreDetail();
        }
    });

    // 切换主匹配项的展开/折叠
    function toggleMainMatch(element) {
        const matchItem = element.closest('.match-item');
        matchItem.classList.toggle('expanded');
    }

    // 切换步骤wrapper的展开/折叠
    function toggleStepWrapper(element) {
        const stepWrapper = element.closest('.step-section-wrapper');
        stepWrapper.classList.toggle('expanded');
    }

    // 切换步骤的展开/折叠
    function toggleStep(element) {
        const stepSection = element.closest('.step-section');
        stepSection.classList.toggle('expanded');
    }

    // 切换匹配详情的展开/折叠
    function toggleMatchSection(element) {
        const matchSection = element.closest('.match-section');
        matchSection.classList.toggle('expanded');
    }

    // 显示指定的灵感详情
    function showDetail(index) {
        const details = document.querySelectorAll('.inspiration-detail');
        details.forEach((detail, i) => {
            if (i === index) {
                detail.classList.add('active');
                // 滚动到顶部
                const section = document.querySelector('.inspirations-section');
                if (section) {
                    section.scrollTop = 0;
                }
            } else {
                detail.classList.remove('active');
            }
        });
    }

    // 生成导航目录
    function generateNavigation() {
        const details = document.querySelectorAll('.inspiration-detail');
        const navList = document.getElementById('navList');
        navList.innerHTML = '';

        details.forEach((detail, index) => {
            const name = detail.dataset.inspirationName;
            const score = parseFloat(detail.dataset.step1Score) || 0;
            const top3MatchesStr = detail.dataset.top3Matches;
            let top3Matches = [];

            try {
                top3Matches = JSON.parse(top3MatchesStr);
            } catch(e) {
                console.error('Error parsing top3 matches:', e);
            }

            const navItem = document.createElement('div');
            navItem.className = 'nav-item';
            if (index === 0) navItem.classList.add('active');
            navItem.dataset.cardIndex = index;

            // 生成匹配列表HTML - 精简版：只显示[灵感分类]名称 score
            let matchesHtml = '';
            if (top3Matches && top3Matches.length > 0) {
                matchesHtml = '<div class="nav-item-matches">';
                top3Matches.forEach((match, i) => {
                    matchesHtml += `
                        <div class="nav-match-item-compact">
                            <span class="nav-match-label">[灵感分类]</span>
                            <span class="nav-match-name-compact">${match.name}</span>
                            <span class="nav-match-score-compact">${match.score.toFixed(2)}</span>
                        </div>
                    `;
                });
                matchesHtml += '</div>';
            }

            navItem.innerHTML = `
                <div class="nav-item-header">
                    <span class="nav-item-name" title="${name}">
                        <span class="nav-label">[灵感点]</span>${name}
                    </span>
                    <span class="nav-item-score">${score.toFixed(2)}</span>
                </div>
                ${matchesHtml}
            `;

            navItem.addEventListener('click', () => {
                // 移除所有active状态
                document.querySelectorAll('.nav-item').forEach(item => {
                    item.classList.remove('active');
                });

                // 添加当前active状态
                navItem.classList.add('active');

                // 显示对应详情
                showDetail(index);
            });

            navList.appendChild(navItem);
        });

        // 默认显示第一个详情
        if (details.length > 0) {
            showDetail(0);
        }
    }

    // 更新面包屑
    function updateBreadcrumb(matchName, stepName) {
        const breadcrumb = document.querySelector('.inspiration-detail.active #dynamicBreadcrumb');
        if (!breadcrumb) return;

        const inspirationName = document.querySelector('.inspiration-detail.active').dataset.inspirationName;

        let breadcrumbHtml = `
            <span class="breadcrumb-item"><span class="breadcrumb-label">[灵感点]</span> ${inspirationName}</span>
        `;

        if (matchName) {
            breadcrumbHtml += `
                <span class="breadcrumb-separator">›</span>
                <span class="breadcrumb-item"><span class="breadcrumb-label">[灵感分类]</span> ${matchName}</span>
            `;
        }

        if (stepName) {
            breadcrumbHtml += `
                <span class="breadcrumb-separator">›</span>
                <span class="breadcrumb-item breadcrumb-current">${stepName}</span>
            `;
        }

        breadcrumb.innerHTML = breadcrumbHtml;
    }

    // 监听滚动，更新面包屑
    function setupBreadcrumbObserver() {
        const activeDetail = document.querySelector('.inspiration-detail.active');
        if (!activeDetail) return;

        const contentWrapper = activeDetail.querySelector('.inspiration-content-wrapper');
        if (!contentWrapper) return;

        // 获取所有需要监听的section
        const sections = activeDetail.querySelectorAll('.step-section');

        if (sections.length === 0) return;

        // 创建Intersection Observer
        const observerOptions = {
            root: null,
            rootMargin: '-100px 0px -50% 0px',
            threshold: 0
        };

        const observer = new IntersectionObserver((entries) => {
            entries.forEach(entry => {
                if (entry.isIntersecting) {
                    const section = entry.target;
                    const matchItem = section.closest('.match-item');
                    const matchName = matchItem ? matchItem.dataset.matchName : '';
                    const stepName = section.dataset.stepName || '';

                    updateBreadcrumb(matchName, stepName);
                }
            });
        }, observerOptions);

        // 观察所有section
        sections.forEach(section => observer.observe(section));

        // 存储observer以便清理
        if (!window.breadcrumbObservers) {
            window.breadcrumbObservers = [];
        }
        window.breadcrumbObservers.push(observer);
    }

    // 页面加载时生成导航
    document.addEventListener('DOMContentLoaded', () => {
        generateNavigation();
        setupBreadcrumbObserver();
    });

    // 当切换灵感点时，重新设置observer
    const originalShowDetail = showDetail;
    showDetail = function(index) {
        // 清理旧的observers
        if (window.breadcrumbObservers) {
            window.breadcrumbObservers.forEach(obs => obs.disconnect());
            window.breadcrumbObservers = [];
        }

        // 调用原始函数
        originalShowDetail(index);

        // 设置新的observer
        setTimeout(() => {
            setupBreadcrumbObserver();
        }, 100);
    };
    '''


def generate_persona_structure_html(persona_data: Dict[str, Any]) -> str:
    """
    生成人设结构的树状HTML

    Args:
        persona_data: 人设数据

    Returns:
        人设结构的HTML字符串
    """
    if not persona_data:
        return '<div class="empty-state">暂无人设数据</div>'

    inspiration_list = persona_data.get("灵感点列表", [])
    if not inspiration_list:
        return '<div class="empty-state">暂无灵感点列表数据</div>'

    html_parts = ['<div class="tree">']

    for perspective_idx, perspective in enumerate(inspiration_list):
        perspective_name = perspective.get("视角名称", "未知视角")
        perspective_desc = perspective.get("视角描述", "")
        pattern_list = perspective.get("模式列表", [])

        # 一级节点：视角
        html_parts.append(f'''
            <ul>
                <li>
                    <div class="tree-node level-1">
                        <span class="node-icon">📁</span>
                        <span class="node-name">{html_module.escape(perspective_name)}</span>
                        <span class="node-count">{len(pattern_list)}个分类</span>
                    </div>
        ''')

        if perspective_desc:
            html_parts.append(f'''
                    <div class="node-desc">{html_module.escape(perspective_desc)}</div>
            ''')

        # 二级节点：分类
        if pattern_list:
            html_parts.append('<ul>')
            for pattern in pattern_list:
                category_name = pattern.get("分类名称", "未知分类")
                core_definition = pattern.get("核心定义", "")
                subcategories = pattern.get("二级细分", [])
                total_posts = sum(len(sub.get("帖子ID列表", [])) for sub in subcategories)

                html_parts.append(f'''
                    <li>
                        <div class="tree-node level-2">
                            <span class="node-icon">📂</span>
                            <span class="node-name">{html_module.escape(category_name)}</span>
                            <span class="node-count">{total_posts}个帖子</span>
                        </div>
                ''')

                if core_definition:
                    html_parts.append(f'''
                        <div class="node-desc">{html_module.escape(core_definition)}</div>
                    ''')

                # 三级节点：细分
                if subcategories:
                    html_parts.append('<ul>')
                    for subcategory in subcategories:
                        sub_name = subcategory.get("分类名称", "未知细分")
                        sub_definition = subcategory.get("分类定义", "")
                        post_ids = subcategory.get("帖子ID列表", [])

                        html_parts.append(f'''
                            <li>
                                <div class="tree-node level-3">
                                    <span class="node-icon">📄</span>
                                    <span class="node-name">{html_module.escape(sub_name)}</span>
                                    <span class="node-count">{len(post_ids)}个帖子</span>
                                </div>
                        ''')

                        if sub_definition:
                            html_parts.append(f'''
                                <div class="node-desc">{html_module.escape(sub_definition)}</div>
                            ''')

                        if post_ids:
                            html_parts.append(f'''
                                <div class="node-posts">
                                    <span class="posts-label">📋 帖子ID:</span>
                                    <span class="posts-ids">{", ".join([html_module.escape(str(pid)) for pid in post_ids[:5]])}</span>
                                    {f'<span class="posts-more">... 等{len(post_ids)}个</span>' if len(post_ids) > 5 else ''}
                                </div>
                            ''')

                        html_parts.append('</li>')
                    html_parts.append('</ul>')

                html_parts.append('</li>')
            html_parts.append('</ul>')

        html_parts.append('</li>')
        html_parts.append('</ul>')

    html_parts.append('</div>')
    return ''.join(html_parts)


def generate_html(
    inspirations_data: List[Dict[str, Any]],
    posts_map: Dict[str, Dict[str, Any]],
    persona_data: Dict[str, Any],
    output_path: str,
    inspiration_to_post_data: Dict[str, Any] = None,
    category_index_data: Dict[str, Any] = None,
    post_to_mapping_data: Dict[str, Any] = None
) -> str:
    """
    生成完整的可视化HTML

    Args:
        inspirations_data: 灵感点数据列表
        posts_map: 帖子数据映射
        persona_data: 人设数据
        output_path: 输出文件路径

    Returns:
        输出文件路径
    """
    timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")

    # 统计信息
    total_count = len(inspirations_data)

    # Step1 统计
    step1_excellent_count = sum(1 for d in inspirations_data
                               if d["summary"].get("关键指标", {}).get("step1_top1_score", 0) >= 0.7)
    step1_good_count = sum(1 for d in inspirations_data
                          if 0.5 <= d["summary"].get("关键指标", {}).get("step1_top1_score", 0) < 0.7)
    step1_normal_count = sum(1 for d in inspirations_data
                            if 0.3 <= d["summary"].get("关键指标", {}).get("step1_top1_score", 0) < 0.5)
    step1_need_opt_count = sum(1 for d in inspirations_data
                              if d["summary"].get("关键指标", {}).get("step1_top1_score", 0) < 0.3)

    # 平均分数
    total_step1_score = sum(d["summary"].get("关键指标", {}).get("step1_top1_score", 0)
                           for d in inspirations_data)
    avg_step1_score = total_step1_score / total_count if total_count > 0 else 0

    # 按Step1分数排序
    inspirations_data_sorted = sorted(
        inspirations_data,
        key=lambda x: x["summary"].get("关键指标", {}).get("step1_top1_score", 0),
        reverse=True
    )

    # 生成卡片HTML
    cards_html = [
        generate_inspiration_card_html(data, inspiration_to_post_data, category_index_data, post_to_mapping_data)
        for data in inspirations_data_sorted
    ]
    cards_html_str = '\n'.join(cards_html)

    # 生成人设结构HTML
    persona_structure_html = generate_persona_structure_html(persona_data)

    # 生成JavaScript
    detail_modal_js = generate_detail_modal_content_js()

    # 完整HTML
    html_content = f'''<!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 {{
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
            background: #f5f7fa;
            color: #333;
            line-height: 1.6;
            margin: 0;
            padding: 0;
            overflow: hidden;
        }}

        .container {{
            max-width: 100%;
            height: 100vh;
            margin: 0;
            padding: 0;
            display: flex;
        }}


        .main-content-layout {{
            display: flex;
            width: 100%;
            height: 100vh;
            overflow: hidden;
        }}

        .sidebar-nav {{
            width: 400px;
            background: white;
            padding: 30px 20px;
            box-shadow: 2px 0 10px rgba(0,0,0,0.1);
            overflow-y: auto;
            flex-shrink: 0;
            height: 100vh;
        }}

        .nav-title {{
            font-size: 18px;
            font-weight: 700;
            color: #1a1a1a;
            margin-bottom: 15px;
            padding-bottom: 10px;
            border-bottom: 2px solid #e5e7eb;
        }}

        .nav-list {{
            display: flex;
            flex-direction: column;
            gap: 12px;
        }}

        .nav-item {{
            border-radius: 10px;
            cursor: pointer;
            transition: all 0.3s;
            border: 2px solid #e5e7eb;
            overflow: hidden;
            background: white;
        }}

        .nav-item:hover {{
            border-color: #667eea;
            box-shadow: 0 4px 12px rgba(102, 126, 234, 0.15);
        }}

        .nav-item.active {{
            border-color: #667eea;
            box-shadow: 0 4px 16px rgba(102, 126, 234, 0.25);
        }}

        .nav-item-header {{
            padding: 12px 14px;
            background: #f9fafb;
            display: flex;
            justify-content: space-between;
            align-items: center;
            gap: 8px;
        }}

        .nav-item.active .nav-item-header {{
            background: rgba(102, 126, 234, 0.1);
        }}

        .nav-item-name {{
            flex: 1;
            font-weight: 600;
            font-size: 14px;
            color: #1f2937;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
            display: flex;
            align-items: center;
            gap: 6px;
        }}

        .nav-item.active .nav-item-name {{
            color: #667eea;
        }}

        .nav-label {{
            display: inline-block;
            font-size: 11px;
            color: #8b5cf6;
            background: #f5f3ff;
            padding: 2px 6px;
            border-radius: 3px;
            font-weight: 500;
            white-space: nowrap;
            flex-shrink: 0;
        }}

        .nav-item-score {{
            font-size: 13px;
            font-weight: 700;
            padding: 4px 10px;
            border-radius: 6px;
            background: #e5e7eb;
            color: #6b7280;
        }}

        .nav-item.active .nav-item-score {{
            background: #667eea;
            color: white;
        }}

        .nav-item-matches {{
            padding: 10px 14px;
            background: white;
            border-top: 1px solid #e5e7eb;
        }}

        .nav-match-item-compact {{
            padding: 6px 0;
            font-size: 12px;
            display: flex;
            align-items: center;
            gap: 6px;
            line-height: 1.4;
        }}

        .nav-match-item-compact:not(:last-child) {{
            border-bottom: 1px dashed #e5e7eb;
        }}

        .nav-match-label {{
            color: #10b981;
            font-weight: 600;
            font-size: 11px;
            flex-shrink: 0;
        }}

        .nav-match-name-compact {{
            color: #1f2937;
            font-weight: 500;
            flex: 1;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
        }}

        .nav-match-score-compact {{
            color: #667eea;
            font-weight: 700;
            font-size: 12px;
            flex-shrink: 0;
        }}

        .inspirations-section {{
            flex: 1;
            height: 100vh;
            overflow-y: auto;
            background: #f5f7fa;
            position: relative;
        }}

        .breadcrumb-container {{
            position: sticky;
            top: 0;
            left: 0;
            right: 0;
            background: white;
            border-bottom: 1px solid #e5e7eb;
            padding: 15px 30px;
            z-index: 100;
            box-shadow: 0 1px 3px rgba(0,0,0,0.05);
        }}

        .breadcrumb {{
            display: flex;
            align-items: center;
            gap: 8px;
            font-size: 14px;
            color: #6b7280;
        }}

        .breadcrumb-item {{
            display: flex;
            align-items: center;
            gap: 8px;
        }}

        .breadcrumb-separator {{
            color: #d1d5db;
        }}

        .breadcrumb-current {{
            color: #111827;
            font-weight: 600;
        }}

        .breadcrumb-label {{
            display: inline-block;
            font-size: 12px;
            color: #6366f1;
            background: #eef2ff;
            padding: 2px 8px;
            border-radius: 4px;
            font-weight: 500;
            margin-right: 4px;
        }}

        .inspiration-content-wrapper {{
            padding: 30px 40px;
        }}

        .inspirations-grid {{
            display: none;
        }}

        .inspiration-display {{
            width: 100%;
        }}

        .inspiration-detail {{
            display: none;
        }}

        .inspiration-detail.active {{
            display: block;
        }}

        .detail-header {{
            background: white;
            border-radius: 12px;
            padding: 25px 30px;
            margin-bottom: 15px;
            box-shadow: 0 1px 3px rgba(0,0,0,0.06);
            border: 1px solid #e5e7eb;
        }}

        .detail-title-row {{
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 15px;
        }}

        .detail-title-left, .detail-title-right {{
            display: flex;
            align-items: center;
            gap: 10px;
        }}

        .detail-label {{
            font-size: 12px;
            color: #5b21b6;
            background: rgba(109, 40, 217, 0.15);
            padding: 3px 10px;
            border-radius: 4px;
            font-weight: 500;
        }}

        .detail-category {{
            font-size: 18px;
            font-weight: 600;
            color: #374151;
        }}

        .detail-title {{
            font-size: 24px;
            font-weight: 700;
            color: #111827;
            margin-bottom: 12px;
        }}

        .detail-score-section {{
            display: flex;
            align-items: center;
            gap: 12px;
        }}

        .detail-score-label {{
            font-size: 13px;
            color: #6b7280;
            font-weight: 500;
        }}

        .detail-score-value {{
            font-size: 20px;
            font-weight: 700;
            color: #374151;
        }}

        .card-header {{
            display: flex;
            justify-content: space-between;
            align-items: flex-start;
            margin-bottom: 20px;
            gap: 12px;
        }}

        .inspiration-name {{
            font-size: 28px;
            font-weight: 800;
            color: #1a1a1a;
            line-height: 1.4;
            flex: 1;
        }}

        .grade-badge {{
            background: #10b981;
            color: white;
            padding: 6px 14px;
            border-radius: 20px;
            font-size: 12px;
            font-weight: 700;
            white-space: nowrap;
        }}

        .score-section {{
            display: flex;
            align-items: center;
            gap: 25px;
            margin-bottom: 20px;
            padding: 20px;
            background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
            border-radius: 12px;
        }}

        .score-item {{
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 8px;
            flex: 1;
        }}

        .main-score {{
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 8px;
        }}

        .score-circle {{
            width: 90px;
            height: 90px;
            border-radius: 50%;
            border: 6px solid #10b981;
            display: flex;
            align-items: center;
            justify-content: center;
            background: white;
        }}

        .score-value {{
            font-size: 26px;
            font-weight: 800;
            color: #10b981;
        }}

        .score-label {{
            font-size: 12px;
            color: #6b7280;
            font-weight: 600;
        }}

        .sub-scores {{
            flex: 1;
            display: flex;
            flex-direction: column;
            gap: 12px;
        }}

        .sub-score-item {{
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 10px 15px;
            background: white;
            border-radius: 8px;
        }}

        .sub-score-label {{
            font-size: 13px;
            color: #6b7280;
            font-weight: 600;
        }}

        .sub-score-value {{
            font-size: 18px;
            font-weight: 700;
            color: #2563eb;
        }}

        .metrics-section {{
            display: flex;
            flex-direction: column;
            gap: 10px;
            margin-bottom: 15px;
        }}

        .metric-item {{
            display: flex;
            align-items: center;
            gap: 8px;
            font-size: 13px;
            color: #4b5563;
        }}

        .metric-icon {{
            font-size: 16px;
        }}

        .metric-label {{
            font-weight: 600;
        }}

        .metric-value {{
            color: #1f2937;
        }}

        .match-preview {{
            background: #f9fafb;
            padding: 12px;
            border-radius: 8px;
            margin-bottom: 10px;
            border-left: 3px solid #8b5cf6;
        }}

        .match-preview-header {{
            font-size: 12px;
            font-weight: 600;
            color: #6b7280;
            margin-bottom: 6px;
        }}

        .match-preview-content {{
            display: flex;
            justify-content: space-between;
            align-items: center;
        }}

        .match-preview-name {{
            font-size: 13px;
            color: #1f2937;
            flex: 1;
        }}

        .match-preview-score {{
            font-size: 16px;
            font-weight: 700;
        }}

        .preview-parts {{
            margin-top: 8px;
            padding: 8px 10px;
            border-radius: 6px;
            font-size: 12px;
            line-height: 1.6;
        }}

        .preview-parts.same {{
            background: #f0fdf4;
            color: #15803d;
            border-left: 3px solid #10b981;
        }}

        .preview-parts.increment {{
            background: #fff7ed;
            color: #92400e;
            border-left: 3px solid #f59e0b;
            margin-top: 6px;
        }}

        .preview-parts strong {{
            font-weight: 700;
            margin-right: 6px;
        }}

        .score-divider {{
            width: 1px;
            height: 40px;
            background: #e5e7eb;
        }}

        .inspiration-category-section {{
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 25px;
            margin-bottom: 0;
        }}

        .info-detail-column {{
            background: #fafbfc;
            border-radius: 10px;
            border: 1px solid #e5e7eb;
            overflow: hidden;
        }}

        .info-header {{
            padding: 15px 20px;
            border-bottom: 1px solid #e5e7eb;
            background: #f9fafb;
            display: flex;
            align-items: center;
            gap: 10px;
        }}

        .info-label {{
            font-size: 11px;
            color: #8b5cf6;
            background: #f5f3ff;
            padding: 2px 6px;
            border-radius: 3px;
            font-weight: 500;
        }}

        .info-name {{
            font-size: 16px;
            font-weight: 600;
            color: #111827;
        }}

        .info-content {{
            padding: 20px;
        }}

        .info-field {{
            margin-bottom: 12px;
            line-height: 1.6;
        }}

        .info-field-label {{
            font-weight: 600;
            color: #374151;
            margin-right: 6px;
        }}

        .info-field-value {{
            color: #6b7280;
        }}

        .info-posts {{
            margin-top: 20px;
        }}

        .info-posts-title {{
            font-size: 14px;
            font-weight: 600;
            color: #374151;
            margin-bottom: 12px;
        }}

        .info-posts-grid {{
            display: flex;
            gap: 15px;
            overflow-x: auto;
            overflow-y: hidden;
            padding-bottom: 10px;
            scroll-behavior: smooth;
        }}

        .info-posts-grid::-webkit-scrollbar {{
            height: 6px;
        }}

        .info-posts-grid::-webkit-scrollbar-track {{
            background: #f3f4f6;
            border-radius: 3px;
        }}

        .info-posts-grid::-webkit-scrollbar-thumb {{
            background: #d1d5db;
            border-radius: 3px;
        }}

        .info-posts-grid::-webkit-scrollbar-thumb:hover {{
            background: #9ca3af;
        }}

        .info-posts-grid .search-note-item {{
            flex: 0 0 260px;
            min-width: 260px;
        }}

        .matches-list {{
            display: flex;
            flex-direction: column;
            gap: 15px;
        }}

        .match-item {{
            background: white;
            border-radius: 12px;
            overflow: hidden;
            box-shadow: 0 2px 8px rgba(0,0,0,0.06);
            transition: all 0.3s;
            margin-bottom: 20px;
        }}

        .match-item:hover {{
            box-shadow: 0 4px 16px rgba(0,0,0,0.1);
        }}

        .match-main-header {{
            background: linear-gradient(135deg, #a5b4fc 0%, #c4b5fd 100%);
            border-bottom: none;
            padding: 16px 20px;
            cursor: pointer;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }}

        .match-main-header:hover {{
            background: linear-gradient(135deg, #8b9cfc 0%, #b39dfc 100%);
        }}

        .match-header-row {{
            flex: 1;
            display: flex;
            justify-content: space-between;
            align-items: center;
            gap: 20px;
        }}

        .match-header-left, .match-header-right {{
            display: flex;
            align-items: center;
            gap: 10px;
            flex: 1;
        }}

        .match-header-center {{
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 4px;
            padding: 0 20px;
        }}

        .match-score-label {{
            font-size: 11px;
            color: #4c1d95;
            text-transform: uppercase;
        }}

        .match-score-value {{
            font-size: 20px;
            font-weight: 700;
            color: #5b21b6;
        }}

        .match-title {{
            color: #3730a3;
            font-size: 16px;
            font-weight: 700;
        }}

        .match-category {{
            color: #3730a3;
            font-size: 16px;
            font-weight: 700;
        }}

        .match-hierarchy {{
            color: #4c1d95;
            font-size: 13px;
        }}

        .match-toggle-main {{
            color: #5b21b6;
            font-size: 20px;
            transition: transform 0.3s;
            flex-shrink: 0;
        }}

        .match-item.expanded .match-toggle-main {{
            transform: rotate(180deg);
        }}

        .match-main-content {{
            max-height: 0;
            overflow: hidden;
            transition: max-height 0.3s ease;
        }}

        .match-item.expanded .match-main-content {{
            max-height: 10000px;
        }}

        .match-main-content .inspiration-category-section {{
            margin: 25px;
            margin-bottom: 0;
        }}

        .step-section-wrapper {{
            margin: 20px 25px;
            border: 2px solid #e5e7eb;
            border-radius: 12px;
            overflow: hidden;
            background: white;
        }}

        .step-1-wrapper {{
            border-color: #fbbf24;
        }}

        .step-2-wrapper {{
            border-color: #34d399;
        }}

        .step-header {{
            background: linear-gradient(135deg, #fbbf24 0%, #fcd34d 100%);
            padding: 16px 25px;
            display: flex;
            justify-content: space-between;
            align-items: center;
            cursor: pointer;
            transition: all 0.3s;
        }}

        .step-header:hover {{
            background: linear-gradient(135deg, #f59e0b 0%, #fbbf24 100%);
        }}

        .step-2-wrapper .step-header {{
            background: linear-gradient(135deg, #34d399 0%, #6ee7b7 100%);
        }}

        .step-2-wrapper .step-header:hover {{
            background: linear-gradient(135deg, #10b981 0%, #34d399 100%);
        }}

        .step-header-content {{
            display: flex;
            align-items: center;
            gap: 12px;
        }}

        .step-header .step-toggle {{
            font-size: 20px;
            color: #78350f;
            transition: transform 0.3s;
        }}

        .step-2-wrapper .step-header .step-toggle {{
            color: #065f46;
        }}

        .step-section-wrapper.expanded > .step-header .step-toggle {{
            transform: rotate(180deg);
        }}

        .step-wrapper-content {{
            max-height: 0;
            overflow: hidden;
            transition: max-height 0.3s ease;
        }}

        .step-section-wrapper.expanded .step-wrapper-content {{
            max-height: 30000px;
        }}

        .step-number-badge {{
            background: rgba(255, 255, 255, 0.5);
            color: #78350f;
            padding: 6px 14px;
            border-radius: 20px;
            font-size: 13px;
            font-weight: 700;
            letter-spacing: 0.5px;
        }}

        .step-2-wrapper .step-number-badge {{
            color: #065f46;
        }}

        .step-title {{
            color: #78350f;
            font-size: 16px;
            font-weight: 700;
        }}

        .step-2-wrapper .step-title {{
            color: #065f46;
        }}

        .match-analysis-section {{
            padding: 25px;
        }}

        .match-header {{
            padding: 20px;
            cursor: pointer;
            display: flex;
            justify-content: space-between;
            align-items: center;
            gap: 15px;
            background: #fafafa;
            transition: background 0.3s;
            border-bottom: 1px solid #e5e7eb;
        }}

        .match-header:hover {{
            background: #f5f5f5;
        }}

        .match-section.expanded .match-header {{
            background: rgba(102, 126, 234, 0.05);
        }}

        .match-header-left {{
            flex: 1;
            min-width: 0;
        }}

        .match-rank {{
            display: inline-block;
            padding: 3px 10px;
            border-radius: 4px;
            font-size: 11px;
            font-weight: 600;
            background: rgba(91, 33, 182, 0.15);
            color: #5b21b6;
        }}

        .match-element-name {{
            font-size: 18px;
            font-weight: 700;
            color: #1f2937;
            display: block;
            margin-bottom: 6px;
        }}

        .match-context {{
            font-size: 13px;
            color: #6b7280;
            display: flex;
            align-items: center;
            gap: 6px;
        }}

        .match-context-separator {{
            color: #d1d5db;
        }}

        .match-header-right {{
            display: flex;
            align-items: center;
            gap: 15px;
            flex-shrink: 0;
        }}

        .match-score {{
            font-size: 24px;
            font-weight: 800;
            color: #667eea;
        }}

        .match-toggle {{
            width: 32px;
            height: 32px;
            border-radius: 8px;
            background: #e5e7eb;
            display: flex;
            align-items: center;
            justify-content: center;
            transition: all 0.3s;
            font-size: 18px;
            color: #6b7280;
        }}

        .match-item.expanded .match-toggle {{
            background: #667eea;
            color: white;
            transform: rotate(180deg);
        }}

        .match-section {{
            border-bottom: 1px solid #e5e7eb;
        }}

        .match-section:last-child {{
            border-bottom: none;
        }}

        .step-section-header {{
            padding: 18px 25px;
            background: #f9fafb;
            cursor: pointer;
            display: flex;
            justify-content: space-between;
            align-items: center;
            border-bottom: 1px solid #e5e7eb;
        }}

        .step-section-header:hover {{
            background: #f3f4f6;
        }}

        .step-section-title {{
            font-size: 15px;
            font-weight: 600;
            color: #374151;
            display: flex;
            align-items: center;
            gap: 10px;
        }}

        .step-sub-number {{
            display: inline-flex;
            align-items: center;
            justify-content: center;
            min-width: 38px;
            padding: 4px 10px;
            background: #10b981;
            color: white;
            border-radius: 6px;
            font-size: 12px;
            font-weight: 700;
        }}

        .step-number {{
            display: inline-flex;
            align-items: center;
            justify-content: center;
            width: 20px;
            height: 20px;
            background: #e5e7eb;
            color: #6b7280;
            border-radius: 50%;
            font-size: 11px;
            font-weight: 600;
        }}

        .step-toggle {{
            font-size: 18px;
            color: #6b7280;
            transition: transform 0.3s;
        }}

        .step-section.expanded .step-toggle {{
            transform: rotate(180deg);
        }}

        .step-section-content {{
            max-height: 0;
            overflow: hidden;
            transition: max-height 0.3s ease;
        }}

        .step-section.expanded .step-section-content {{
            max-height: 20000px;
        }}

        .match-content {{
            max-height: 0;
            overflow: hidden;
            transition: max-height 0.3s ease;
        }}

        .match-section.expanded .match-content {{
            max-height: 2000px;
        }}

        .match-content-inner {{
            padding: 20px;
        }}

        .match-explain {{
            background: #fffbeb;
            padding: 16px 20px;
            border-radius: 8px;
            margin-top: 20px;
            border-left: 3px solid #f59e0b;
        }}

        .match-explain-title {{
            font-weight: 600;
            color: #374151;
            margin-bottom: 6px;
            font-size: 13px;
        }}

        .match-explain-text {{
            color: #6b7280;
            font-size: 14px;
            line-height: 1.7;
        }}

        .match-parts-container {{
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 25px;
            margin-bottom: 20px;
        }}

        .match-parts-column {{
            min-width: 0;
        }}

        .match-parts {{
            margin-bottom: 0;
            height: 100%;
            background: #f9fafb;
            padding: 16px;
            border-radius: 8px;
            border: 1px solid #e5e7eb;
        }}

        .match-parts-title {{
            font-weight: 700;
            font-size: 14px;
            margin-bottom: 16px;
            color: #374151;
            text-transform: uppercase;
            letter-spacing: 0.5px;
        }}

        .part-item {{
            padding: 10px 0;
            margin-bottom: 8px;
            font-size: 14px;
            border-bottom: 1px solid #e5e7eb;
        }}

        .part-item:last-child {{
            border-bottom: none;
        }}

        .part-key {{
            font-weight: 500;
            color: #374151;
            margin-right: 8px;
        }}

        .part-value {{
            color: #6b7280;
        }}

        .same-parts {{
            background: #ecfdf5;
            border-color: #10b981;
        }}

        .increment-parts {{
            background: #fef3c7;
            border-color: #f59e0b;
        }}

        .search-results-section {{
            padding: 25px;
        }}

        .search-params-section {{
            background: #f0f9ff;
            border: 1px solid #bae6fd;
            border-radius: 8px;
            padding: 20px;
            margin-bottom: 20px;
        }}

        .search-params-title {{
            font-size: 15px;
            font-weight: 700;
            color: #0c4a6e;
            margin-bottom: 16px;
        }}

        .search-params-grid {{
            display: grid;
            grid-template-columns: repeat(2, 1fr);
            gap: 12px;
        }}

        .search-param-item {{
            display: flex;
            align-items: center;
            gap: 8px;
        }}

        .search-param-label {{
            font-size: 13px;
            color: #0369a1;
            font-weight: 600;
            min-width: 80px;
        }}

        .search-param-value {{
            font-size: 13px;
            color: #0c4a6e;
            background: #e0f2fe;
            padding: 4px 12px;
            border-radius: 4px;
        }}

        .search-summary-section {{
            background: #ecfdf5;
            border: 1px solid #a7f3d0;
            border-radius: 8px;
            padding: 16px 20px;
            margin-bottom: 20px;
        }}

        .search-summary-header {{
            display: flex;
            justify-content: space-between;
            align-items: center;
            gap: 20px;
        }}

        .search-summary-left {{
            flex: 1;
        }}

        .search-summary-title {{
            font-size: 15px;
            font-weight: 700;
            color: #065f46;
            margin-bottom: 8px;
        }}

        .search-summary-content {{
            font-size: 14px;
            color: #047857;
        }}

        .search-result-count {{
            font-size: 18px;
            font-weight: 700;
            color: #059669;
            margin: 0 4px;
        }}

        .search-sort-buttons {{
            display: flex;
            align-items: center;
            gap: 8px;
        }}

        .search-sort-label {{
            font-size: 13px;
            color: #047857;
            font-weight: 600;
        }}

        .search-sort-btn {{
            background: white;
            border: 1px solid #a7f3d0;
            color: #065f46;
            padding: 6px 12px;
            border-radius: 6px;
            font-size: 12px;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.2s;
        }}

        .search-sort-btn:hover {{
            background: #d1fae5;
            border-color: #6ee7b7;
        }}

        .search-sort-btn.active {{
            background: #059669;
            color: white;
            border-color: #059669;
        }}

        .note-position {{
            font-size: 11px;
            color: #9ca3af;
            font-weight: 600;
        }}

        .search-notes-list {{
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
            gap: 16px;
        }}

        .search-note-item {{
            background: white;
            border-radius: 12px;
            overflow: hidden;
            box-shadow: 0 2px 8px rgba(0,0,0,0.08);
            border: 1px solid #e5e7eb;
            transition: all 0.3s;
            position: relative;
        }}

        .search-note-item:hover {{
            box-shadow: 0 4px 16px rgba(102, 126, 234, 0.2);
            border-color: #667eea;
        }}

        .note-score-badge {{
            position: absolute;
            top: 12px;
            right: 12px;
            background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
            padding: 8px 14px;
            border-radius: 8px;
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 2px;
            cursor: pointer;
            z-index: 5;
            box-shadow: 0 2px 8px rgba(99, 102, 241, 0.3);
            transition: all 0.3s;
        }}

        .note-score-badge:hover {{
            transform: scale(1.05);
            box-shadow: 0 4px 12px rgba(99, 102, 241, 0.5);
        }}

        .note-score-badge .score-label {{
            font-size: 10px;
            color: rgba(255, 255, 255, 0.9);
            text-transform: uppercase;
            letter-spacing: 0.5px;
        }}

        .note-score-badge .score-value {{
            font-size: 18px;
            font-weight: 700;
            color: white;
        }}

        .note-image-carousel {{
            position: relative;
            width: 100%;
            min-height: 200px;
            max-height: 400px;
            background: #f3f4f6;
            overflow: hidden;
            display: flex;
            align-items: center;
            justify-content: center;
        }}

        .note-images-track {{
            display: flex;
            width: 100%;
            height: 100%;
            transition: transform 0.4s ease;
        }}

        .note-image {{
            flex: 0 0 100%;
            width: 100%;
            height: auto;
            max-height: 400px;
            object-fit: contain;
        }}

        .note-carousel-button {{
            position: absolute;
            top: 50%;
            transform: translateY(-50%);
            width: 32px;
            height: 32px;
            background: rgba(255, 255, 255, 0.9);
            border: none;
            border-radius: 50%;
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: pointer;
            transition: all 0.3s;
            z-index: 10;
            font-size: 16px;
            color: #6b7280;
            opacity: 0;
        }}

        .search-note-item:hover .note-carousel-button {{
            opacity: 1;
        }}

        .note-carousel-button:hover {{
            background: #667eea;
            color: white;
        }}

        .note-carousel-button.disabled {{
            opacity: 0 !important;
        }}

        .note-carousel-button.prev {{
            left: 10px;
        }}

        .note-carousel-button.next {{
            right: 10px;
        }}

        .note-image-dots {{
            position: absolute;
            bottom: 10px;
            left: 50%;
            transform: translateX(-50%);
            display: flex;
            gap: 6px;
            z-index: 10;
        }}

        .note-image-dot {{
            width: 6px;
            height: 6px;
            border-radius: 50%;
            background: rgba(255, 255, 255, 0.6);
            transition: all 0.3s;
        }}

        .note-image-dot.active {{
            background: white;
            width: 20px;
            border-radius: 3px;
        }}

        .note-content {{
            padding: 15px;
        }}

        .note-title {{
            font-size: 15px;
            font-weight: 600;
            color: #1f2937;
            margin-bottom: 8px;
            overflow: hidden;
            text-overflow: ellipsis;
            display: -webkit-box;
            -webkit-line-clamp: 2;
            -webkit-box-orient: vertical;
            line-height: 1.4;
        }}

        .note-desc {{
            font-size: 13px;
            color: #6b7280;
            line-height: 1.6;
            margin-bottom: 12px;
            overflow: hidden;
            text-overflow: ellipsis;
            display: -webkit-box;
            -webkit-line-clamp: 2;
            -webkit-box-orient: vertical;
        }}

        .note-publish-date {{
            font-size: 12px;
            color: #9ca3af;
            margin-bottom: 12px;
            display: flex;
            align-items: center;
            gap: 4px;
        }}

        .note-footer {{
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding-top: 12px;
            border-top: 1px solid #f3f4f6;
        }}

        .note-author {{
            font-size: 12px;
            font-weight: 600;
            color: #6b7280;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
            max-width: 150px;
        }}

        .note-points-hover {{
            position: absolute;
            bottom: 100%;
            left: 0;
            right: 0;
            background: white;
            border: 1px solid #e5e7eb;
            border-radius: 8px;
            box-shadow: 0 4px 12px rgba(0,0,0,0.15);
            padding: 12px;
            margin-bottom: 8px;
            opacity: 0;
            visibility: hidden;
            transition: opacity 0.2s, visibility 0.2s;
            z-index: 10;
            max-height: 300px;
            overflow-y: auto;
        }}

        .search-note-item {{
            position: relative;
        }}

        .search-note-item:hover .note-points-hover {{
            opacity: 1;
            visibility: visible;
        }}

        .note-points {{
            /* 用于详情弹窗中的样式 */
        }}

        .points-section {{
            margin-bottom: 8px;
        }}

        .points-section:last-child {{
            margin-bottom: 0;
        }}

        .points-label {{
            font-size: 11px;
            font-weight: 600;
            color: #6b7280;
            margin-bottom: 4px;
        }}

        .points-list {{
            display: flex;
            flex-wrap: wrap;
            gap: 4px;
        }}

        .point-item {{
            display: inline-block;
        }}

        .point-name {{
            display: inline-block;
            font-size: 11px;
            color: #4b5563;
            background: #f3f4f6;
            padding: 2px 6px;
            border-radius: 3px;
        }}

        .note-stats {{
            display: flex;
            gap: 10px;
            font-size: 12px;
            color: #9ca3af;
        }}

        .note-link {{
            display: block;
            text-align: center;
            padding: 8px;
            margin-top: 10px;
            background: rgba(102, 126, 234, 0.1);
            color: #667eea;
            text-decoration: none;
            border-radius: 6px;
            font-size: 13px;
            font-weight: 600;
            transition: all 0.3s;
        }}

        .note-link:hover {{
            background: #667eea;
            color: white;
        }}

        /* 笔记详情Modal样式 */
        .note-detail-modal {{
            display: none;
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0, 0, 0, 0.8);
            z-index: 9999;
            overflow-y: auto;
            padding: 40px 20px;
        }}

        .note-detail-modal.active {{
            display: flex;
            align-items: flex-start;
            justify-content: center;
        }}

        .note-detail-content {{
            background: white;
            border-radius: 16px;
            max-width: 1200px;
            width: 100%;
            position: relative;
            margin: auto;
        }}

        .note-detail-close {{
            position: absolute;
            top: 20px;
            right: 20px;
            width: 40px;
            height: 40px;
            background: white;
            border: none;
            border-radius: 50%;
            font-size: 24px;
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center;
            box-shadow: 0 2px 8px rgba(0,0,0,0.2);
            transition: all 0.3s;
            z-index: 10;
        }}

        .note-detail-close:hover {{
            background: #ef4444;
            color: white;
            transform: rotate(90deg);
        }}

        .note-detail-header {{
            padding: 30px;
            border-bottom: 1px solid #e5e7eb;
        }}

        .note-detail-title {{
            font-size: 24px;
            font-weight: 800;
            color: #1f2937;
            margin-bottom: 15px;
        }}

        .note-detail-meta {{
            display: flex;
            align-items: center;
            gap: 20px;
            font-size: 14px;
            color: #6b7280;
        }}

        .note-detail-author {{
            font-weight: 600;
            color: #1f2937;
        }}

        .note-detail-stats {{
            display: flex;
            gap: 15px;
        }}

        .note-detail-body {{
            padding: 30px;
        }}

        .note-detail-desc {{
            font-size: 15px;
            line-height: 1.8;
            color: #4b5563;
            margin-bottom: 30px;
        }}

        .note-detail-points {{
            margin-bottom: 30px;
            background: #f9fafb;
            border-radius: 8px;
            padding: 20px;
        }}

        .detail-points-section {{
            margin-bottom: 20px;
        }}

        .detail-points-section:last-child {{
            margin-bottom: 0;
        }}

        .detail-points-title {{
            font-size: 16px;
            font-weight: 600;
            color: #111827;
            margin-bottom: 12px;
        }}

        .detail-point-item {{
            background: white;
            padding: 12px 15px;
            border-radius: 6px;
            margin-bottom: 10px;
            border-left: 3px solid #e5e7eb;
        }}

        .detail-point-item:last-child {{
            margin-bottom: 0;
        }}

        .detail-point-name {{
            font-size: 14px;
            font-weight: 600;
            color: #374151;
            margin-bottom: 4px;
        }}

        .detail-point-desc {{
            font-size: 13px;
            line-height: 1.6;
            color: #6b7280;
        }}

        .note-detail-images {{
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
            gap: 15px;
        }}

        .note-detail-image {{
            width: 100%;
            border-radius: 8px;
            cursor: pointer;
            transition: all 0.3s;
        }}

        .note-detail-image:hover {{
            transform: scale(1.05);
            box-shadow: 0 8px 24px rgba(0,0,0,0.2);
        }}

        .note-detail-footer {{
            padding: 20px 30px;
            border-top: 1px solid #e5e7eb;
            display: flex;
            justify-content: center;
        }}

        .note-detail-link {{
            display: inline-block;
            padding: 12px 32px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            text-decoration: none;
            border-radius: 10px;
            font-weight: 600;
            transition: all 0.3s;
        }}

        .note-detail-link:hover {{
            transform: translateY(-2px);
            box-shadow: 0 8px 20px rgba(102, 126, 234, 0.4);
        }}

        /* Modal样式 */
        .modal-overlay {{
            display: none;
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background: rgba(0, 0, 0, 0.8);
            z-index: 1000;
            align-items: center;
            justify-content: center;
            padding: 20px;
            overflow-y: auto;
        }}

        .modal-overlay.active {{
            display: flex;
        }}

        .modal-content {{
            background: white;
            border-radius: 16px;
            max-width: 1200px;
            width: 100%;
            max-height: 90vh;
            overflow-y: auto;
            position: relative;
        }}

        .modal-close {{
            position: sticky;
            top: 0;
            right: 0;
            background: white;
            border: none;
            font-size: 32px;
            color: #6b7280;
            cursor: pointer;
            padding: 15px 20px;
            z-index: 10;
            text-align: right;
            border-bottom: 1px solid #e5e7eb;
        }}

        .modal-close:hover {{
            color: #1f2937;
        }}

        .modal-body {{
            padding: 30px;
        }}

        /* 分数详情Modal样式 */
        .score-detail-content {{
            max-width: 800px;
        }}

        .score-detail-container {{
            padding: 10px;
        }}

        .score-detail-title {{
            font-size: 24px;
            font-weight: 700;
            color: #1f2937;
            margin-bottom: 20px;
            padding-bottom: 15px;
            border-bottom: 2px solid #e5e7eb;
        }}

        .score-detail-score {{
            background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
            padding: 20px;
            border-radius: 12px;
            margin-bottom: 20px;
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 10px;
        }}

        .score-detail-label {{
            font-size: 16px;
            color: rgba(255, 255, 255, 0.9);
            font-weight: 600;
        }}

        .score-detail-value {{
            font-size: 32px;
            font-weight: 700;
            color: white;
        }}

        .score-detail-explain {{
            background: #fffbeb;
            padding: 16px 20px;
            border-radius: 8px;
            margin-bottom: 20px;
            border-left: 3px solid #f59e0b;
        }}

        .score-input-info {{
            background: #f0f9ff;
            padding: 16px 20px;
            border-radius: 8px;
            margin-bottom: 20px;
            border-left: 3px solid #3b82f6;
        }}

        .score-input-title {{
            font-weight: 700;
            font-size: 14px;
            margin-bottom: 12px;
            color: #1e40af;
            text-transform: uppercase;
            letter-spacing: 0.5px;
        }}

        .score-input-item {{
            margin-bottom: 12px;
            padding-bottom: 12px;
            border-bottom: 1px solid #bfdbfe;
        }}

        .score-input-item:last-child {{
            margin-bottom: 0;
            padding-bottom: 0;
            border-bottom: none;
        }}

        .score-input-label {{
            font-weight: 600;
            font-size: 13px;
            color: #1e40af;
            margin-bottom: 4px;
        }}

        .score-input-value {{
            font-size: 14px;
            color: #374151;
            line-height: 1.6;
            white-space: pre-wrap;
            word-break: break-word;
        }}

        .score-parts-container {{
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 20px;
        }}

        .score-parts {{
            background: #f9fafb;
            padding: 16px;
            border-radius: 8px;
            border: 1px solid #e5e7eb;
        }}

        .score-parts.same-parts {{
            background: #ecfdf5;
            border-color: #10b981;
        }}

        .score-parts.increment-parts {{
            background: #fef3c7;
            border-color: #f59e0b;
        }}

        .score-parts-title {{
            font-weight: 700;
            font-size: 14px;
            margin-bottom: 16px;
            color: #374151;
            text-transform: uppercase;
            letter-spacing: 0.5px;
        }}

        .score-part-item {{
            padding: 10px 0;
            margin-bottom: 8px;
            font-size: 14px;
            border-bottom: 1px solid #e5e7eb;
        }}

        .score-part-item:last-child {{
            border-bottom: none;
        }}

        .score-part-key {{
            font-weight: 500;
            color: #374151;
            margin-right: 8px;
        }}

        .score-part-value {{
            color: #6b7280;
        }}

        .modal-header {{
            margin-bottom: 25px;
            padding-bottom: 20px;
            border-bottom: 2px solid #e5e7eb;
        }}

        .modal-title {{
            font-size: 28px;
            font-weight: 800;
            color: #1a1a1a;
        }}

        .modal-section {{
            margin-bottom: 30px;
        }}

        .modal-section h3 {{
            font-size: 20px;
            font-weight: 700;
            color: #374151;
            margin-bottom: 15px;
            padding-bottom: 10px;
            border-bottom: 2px solid #f3f4f6;
        }}

        .info-grid {{
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
            gap: 15px;
        }}

        .info-item {{
            background: #f9fafb;
            padding: 12px 16px;
            border-radius: 8px;
            border-left: 3px solid #8b5cf6;
        }}

        .info-label {{
            font-weight: 600;
            color: #6b7280;
            font-size: 13px;
            margin-right: 8px;
        }}

        .info-value {{
            color: #1f2937;
            font-size: 14px;
        }}

        .metrics-grid {{
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
            gap: 15px;
        }}

        .metric-box {{
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            padding: 20px;
            border-radius: 12px;
            text-align: center;
            color: white;
        }}

        .metric-box.wide {{
            grid-column: span 2;
        }}

        .metric-box-label {{
            font-size: 13px;
            opacity: 0.9;
            margin-bottom: 8px;
            font-weight: 600;
        }}

        .metric-box-value {{
            font-size: 28px;
            font-weight: 700;
        }}

        .metric-box-value.small {{
            font-size: 16px;
        }}

        .step-content {{
            background: #f9fafb;
            padding: 20px;
            border-radius: 12px;
        }}

        .step-field {{
            margin-bottom: 20px;
        }}

        .step-field-label {{
            font-weight: 700;
            color: #374151;
            font-size: 14px;
            margin-bottom: 8px;
            display: block;
        }}

        .step-field-value {{
            color: #1f2937;
            font-size: 15px;
            line-height: 1.7;
        }}

        .matches-list {{
            display: flex;
            flex-direction: column;
            gap: 15px;
            margin-top: 10px;
        }}

        .match-item {{
            background: white;
            padding: 18px;
            border-radius: 10px;
            border-left: 5px solid #3b82f6;
        }}

        .match-item.top1 {{
            border-left-color: #fbbf24;
            background: linear-gradient(135deg, #fef3c7 0%, #fde68a 50%, white 100%);
        }}

        .match-item.top2 {{
            border-left-color: #c0c0c0;
            background: linear-gradient(135deg, #f3f4f6 0%, #e5e7eb 50%, white 100%);
        }}

        .match-item.top3 {{
            border-left-color: #cd7f32;
            background: linear-gradient(135deg, #fef3c7 0%, #fed7aa 50%, white 100%);
        }}

        .match-header {{
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 12px;
            gap: 10px;
        }}

        .match-rank {{
            font-size: 18px;
            font-weight: 800;
            color: #6b7280;
        }}

        .match-element-name {{
            flex: 1;
            font-size: 16px;
            font-weight: 700;
            color: #1f2937;
        }}

        .match-score {{
            font-size: 22px;
            font-weight: 800;
            color: #2563eb;
            background: white;
            padding: 6px 14px;
            border-radius: 8px;
        }}

        .match-detail {{
            background: rgba(255, 255, 255, 0.7);
            padding: 10px;
            border-radius: 6px;
            margin-bottom: 10px;
            font-size: 13px;
            color: #4b5563;
        }}

        .match-reason {{
            color: #1f2937;
            font-size: 14px;
            line-height: 1.7;
        }}

        .increment-matches {{
            display: flex;
            flex-direction: column;
            gap: 12px;
            margin-top: 10px;
        }}

        .increment-item {{
            background: white;
            padding: 15px;
            border-radius: 8px;
            border-left: 4px solid #10b981;
        }}

        .increment-header {{
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 10px;
        }}

        .increment-words {{
            font-weight: 700;
            color: #1f2937;
            font-size: 15px;
        }}

        .increment-score {{
            font-size: 20px;
            font-weight: 800;
            color: #10b981;
        }}

        .increment-reason {{
            color: #4b5563;
            font-size: 13px;
            line-height: 1.6;
        }}

        .empty-state {{
            text-align: center;
            padding: 40px;
            color: #9ca3af;
            font-size: 14px;
        }}

        .modal-link {{
            margin-top: 25px;
            padding-top: 20px;
            border-top: 2px solid #e5e7eb;
            text-align: center;
        }}

        .modal-link-btn {{
            display: inline-flex;
            align-items: center;
            gap: 10px;
            padding: 12px 24px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            text-decoration: none;
            border-radius: 10px;
            font-size: 15px;
            font-weight: 600;
            transition: all 0.3s;
        }}

        .modal-link-btn:hover {{
            transform: translateY(-2px);
            box-shadow: 0 8px 20px rgba(102, 126, 234, 0.4);
        }}

        .timestamp {{
            text-align: center;
            color: white;
            font-size: 13px;
            margin-top: 30px;
            opacity: 0.8;
        }}

        .match-context {{
            background: #f3f4f6;
            padding: 8px 12px;
            border-radius: 6px;
            margin: 8px 0;
            font-size: 12px;
            color: #6b7280;
            line-height: 1.6;
        }}

        .match-explain {{
            background: #fef3c7;
            padding: 10px 12px;
            border-radius: 6px;
            margin: 10px 0;
            font-size: 13px;
            color: #92400e;
            line-height: 1.7;
            border-left: 3px solid #f59e0b;
        }}

        .match-parts {{
            margin: 12px 0;
            border-radius: 8px;
            overflow: hidden;
        }}

        .match-parts.same-parts {{
            background: #f0fdf4;
            border: 2px solid #10b981;
        }}

        .match-parts.increment-parts {{
            background: #fff7ed;
            border: 2px solid #f59e0b;
        }}

        .parts-header {{
            font-weight: 700;
            padding: 10px 12px;
            font-size: 13px;
        }}

        .same-parts .parts-header {{
            background: #dcfce7;
            color: #15803d;
        }}

        .increment-parts .parts-header {{
            background: #fed7aa;
            color: #92400e;
        }}

        .parts-content {{
            padding: 8px 12px;
        }}

        .part-item {{
            padding: 6px 0;
            border-bottom: 1px solid rgba(0,0,0,0.05);
            font-size: 13px;
            line-height: 1.6;
        }}

        .part-item:last-child {{
            border-bottom: none;
        }}

        .part-key {{
            font-weight: 600;
            color: #374151;
            margin-right: 6px;
        }}

        .part-value {{
            color: #1f2937;
        }}

        .increment-context {{
            background: #fef3c7;
            padding: 10px 12px;
            border-radius: 6px;
            margin: 10px 0;
            font-size: 12px;
            color: #92400e;
            line-height: 1.6;
            border-left: 3px solid #f59e0b;
        }}



        @media (max-width: 768px) {{
            .main-content-layout {{
                flex-direction: column;
            }}

            .sidebar-nav {{
                position: relative;
                width: 100%;
                max-height: 300px;
            }}

            .inspirations-grid {{
                grid-template-columns: 1fr;
            }}

            .header h1 {{
                font-size: 32px;
            }}

            .stats-overview {{
                grid-template-columns: repeat(2, 1fr);
            }}
        }}
    </style>
</head>
<body>
    <div class="container">
        <div class="main-content-layout">
            <div class="sidebar-nav">
                <div class="nav-title">📑 灵感点列表</div>
                <div class="nav-list" id="navList">
                    <!-- 目录将通过JS动态生成 -->
                </div>
            </div>
            <div class="inspirations-section">
                <div class="inspiration-display">
                    {cards_html_str}
                </div>
            </div>
        </div>

        <!-- Modal -->
        <div id="detailModal" class="modal-overlay" onclick="closeModalOnOverlay(event)">
            <div class="modal-content">
                <button class="modal-close" onclick="closeModal()">&times;</button>
                <div class="modal-body" id="modalBody">
                    <!-- Content will be inserted here -->
                </div>
            </div>
        </div>

        <!-- Score Detail Modal -->
        <div id="scoreDetailModal" class="modal-overlay" onclick="closeScoreDetailModal(event)">
            <div class="modal-content score-detail-content">
                <button class="modal-close" onclick="closeScoreDetail()">&times;</button>
                <div class="modal-body" id="scoreModalBody">
                    <!-- Score detail content will be inserted here -->
                </div>
            </div>
        </div>
    </div>

    <script>
    {detail_modal_js}
    </script>
</body>
</html>'''

    # 写入文件
    output_file = Path(output_path)
    output_file.parent.mkdir(parents=True, exist_ok=True)

    with open(output_file, 'w', encoding='utf-8') as f:
        f.write(html_content)

    return str(output_file.absolute())


def load_persona_data(persona_path: str) -> Dict[str, Any]:
    """
    加载人设数据

    Args:
        persona_path: 人设JSON文件路径

    Returns:
        人设数据字典
    """
    try:
        with open(persona_path, 'r', encoding='utf-8') as f:
            return json.load(f)
    except Exception as e:
        print(f"警告: 读取人设文件失败: {e}")
        return {}


def main():
    """主函数"""
    import sys
    import os

    # 配置路径（使用当前脚本的相对路径）
    script_dir = os.path.dirname(os.path.abspath(__file__))
    base_dir = os.path.join(script_dir, "data/阿里多多酱")

    inspiration_dir = os.path.join(base_dir, "out/人设_1110/how/灵感点")
    posts_dir = os.path.join(base_dir, "作者历史帖子")
    persona_path = os.path.join(base_dir, "out/人设_1110/人设.json")
    inspiration_to_post_path = os.path.join(base_dir, "out/人设_1110/点到帖子映射.json")
    category_index_path = os.path.join(base_dir, "out/人设_1110/分类索引_完整.json")
    post_to_mapping_path = os.path.join(base_dir, "out/人设_1110/帖子到分类和点映射.json")
    output_path = os.path.join(base_dir, "out/人设_1110/how/灵感点可视化.html")

    print("=" * 60)
    print("灵感点分析可视化脚本")
    print("=" * 60)

    # 加载数据
    print("\n📂 正在加载灵感点数据...")
    inspirations_data = load_inspiration_points_data(inspiration_dir)
    print(f"✅ 成功加载 {len(inspirations_data)} 个灵感点")

    print("\n📂 正在加载帖子数据...")
    posts_map = load_posts_data(posts_dir)
    print(f"✅ 成功加载 {len(posts_map)} 个帖子")

    print("\n📂 正在加载人设数据...")
    persona_data = load_persona_data(persona_path)
    print(f"✅ 成功加载人设数据")

    print("\n📂 正在加载点到帖子映射数据...")
    with open(inspiration_to_post_path, 'r', encoding='utf-8') as f:
        inspiration_to_post_data = json.load(f)
    print(f"✅ 成功加载点到帖子映射数据")

    print("\n📂 正在加载分类索引数据...")
    with open(category_index_path, 'r', encoding='utf-8') as f:
        category_index_data = json.load(f)
    print(f"✅ 成功加载分类索引数据")

    print("\n📂 正在加载帖子到分类和点映射数据...")
    with open(post_to_mapping_path, 'r', encoding='utf-8') as f:
        post_to_mapping_data = json.load(f)
    print(f"✅ 成功加载帖子到分类和点映射数据")

    # 生成HTML
    print("\n🎨 正在生成可视化HTML...")
    result_path = generate_html(
        inspirations_data,
        posts_map,
        persona_data,
        output_path,
        inspiration_to_post_data,
        category_index_data,
        post_to_mapping_data
    )

    print(f"\n✅ 可视化文件已生成!")
    print(f"📄 文件路径: {result_path}")
    print(f"\n💡 在浏览器中打开该文件即可查看可视化结果")
    print("=" * 60)


if __name__ == "__main__":
    main()
