"""§0 开场 + Agile/Scrum/TDD 垫层 · slide 4-12 (9 内容页) · v0.2 真实内容版

依赖 build_main.py 的 helpers + design token。
嵌入 8 张 draw.io 图（G1 / G2 / B1 / B2 / B3 / B4 / A1 / A3）+ 1 张自画 checklist。
"""

from pathlib import Path
from pptx.util import Inches, Pt
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR

from build_main import (
    _fill_ph, _add_textbox, _add_card,
    NAVY, CORAL, CORAL_LIGHT, BLUE_ACCENT, GREEN, GOLD,
    GRAY_50, GRAY_300, GRAY_500, GRAY_700, GRAY_900, WHITE,
)

DIAGRAM_DIR = Path(__file__).resolve().parent / "diagrams" / "png"
BOOK_COVER_DIR = Path(__file__).resolve().parent / "diagrams" / "book-covers"


# ============================================================
# Helpers（本章专用）
# ============================================================

def _add_centered_image(slide, img_filename, *, top_inch=1.4, height_inch=5.9,
                        slide_width_inch=13.333):
    """嵌入图片并水平居中（读实际 PNG 尺寸计算宽度，不假设 16:9）
    v1.1.2: 改为从 PNG IHDR 读取真实宽高比后再居中，修复各图片宽高比不一致导致的偏移。
    """
    import struct
    img_path = DIAGRAM_DIR / img_filename
    if not img_path.exists():
        raise FileNotFoundError(f"Image not found: {img_path}")
    # 读 PNG IHDR chunk（不依赖 PIL）
    with open(img_path, "rb") as f:
        f.read(8)   # PNG signature
        f.read(4)   # IHDR chunk length
        f.read(4)   # IHDR chunk type
        img_w = struct.unpack(">I", f.read(4))[0]
        img_h = struct.unpack(">I", f.read(4))[0]
    actual_ratio = img_w / img_h
    width_inch = height_inch * actual_ratio
    x_inch = (slide_width_inch - width_inch) / 2
    return slide.shapes.add_picture(
        str(img_path), Inches(x_inch), Inches(top_inch), height=Inches(height_inch)
    )


def _add_panel_image(slide, img_filename, *, panel_x, panel_y, panel_w, panel_h):
    """嵌入图片到指定 panel · 等比缩放 + 居中（用于 slide 7/8/9 双栏右半）"""
    import struct
    img_path = DIAGRAM_DIR / img_filename
    if not img_path.exists():
        raise FileNotFoundError(f"Image not found: {img_path}")
    with open(img_path, "rb") as f:
        f.read(8); f.read(4); f.read(4)
        img_w = struct.unpack(">I", f.read(4))[0]
        img_h = struct.unpack(">I", f.read(4))[0]
    aspect = img_w / img_h
    # 先按 panel 高度等比缩放；若宽度溢出则改按宽度
    target_h = panel_h
    target_w = target_h * aspect
    if target_w > panel_w:
        target_w = panel_w
        target_h = target_w / aspect
    cx = panel_x + (panel_w - target_w) / 2
    cy = panel_y + (panel_h - target_h) / 2
    return slide.shapes.add_picture(
        str(img_path), Inches(cx), Inches(cy), width=Inches(target_w)
    )


def _draw_left_section(s, x, y, w, h, label, *, body=None, bullets=None,
                       accent=CORAL, body_size=12, body_color=GRAY_900,
                       body_bold=False, bullet_size=11):
    """左栏 section · accent bar + label + (body text 或 bullets)

    用于 slide 7/8/9 双栏左半的"定义 / 目标 / 要点"三段式。
    body 与 bullets 二选一传入。
    """
    from pptx.enum.shapes import MSO_SHAPE as _MS
    # Accent bar（左侧 0.06" 色条）
    bar = s.shapes.add_shape(_MS.RECTANGLE, Inches(x), Inches(y),
                              Inches(0.06), Inches(h))
    bar.fill.solid(); bar.fill.fore_color.rgb = accent
    bar.line.fill.background()
    # Label（小标签，bar 右侧）
    _add_textbox(s, Inches(x + 0.18), Inches(y), Inches(w - 0.18), Inches(0.28),
                 label, size_pt=11, bold=True, color=accent)
    # Body 区域
    body_y = y + 0.30
    body_h = h - 0.30
    if body is not None:
        _add_textbox(s, Inches(x + 0.18), Inches(body_y),
                     Inches(w - 0.18), Inches(body_h),
                     body, size_pt=body_size, color=body_color, bold=body_bold)
    elif bullets is not None:
        h_per = body_h / len(bullets)
        for i, bullet in enumerate(bullets):
            by = body_y + i * h_per
            _add_textbox(s, Inches(x + 0.20), Inches(by),
                         Inches(0.22), Inches(h_per),
                         "▎", size_pt=bullet_size + 2, bold=True, color=accent,
                         anchor=MSO_ANCHOR.MIDDLE)
            _add_textbox(s, Inches(x + 0.44), Inches(by),
                         Inches(w - 0.55), Inches(h_per),
                         bullet, size_pt=bullet_size, color=GRAY_900,
                         anchor=MSO_ANCHOR.MIDDLE)


def _title(slide, text, *, size_pt=26):
    """填章节标题（layout[3] title placeholder）"""
    return _fill_ph(slide, 0, text, size_pt=size_pt, bold=True, color=NAVY)


# ============================================================
# §0 9 内容页（slide 4-12）
# ============================================================

def build_slide_4_what_you_get(prs):
    """slide 4: 6 小时教你的不是 prompt · 是让 AI 不能不靠谱的工程 · v2.0 X5 论断式

    设计（McKinsey Pyramid Principle）：
    - 1 个核心论断（左右范式对照 + 中间大箭头）
    - 3 条工程支撑（思想 / 战略 / 技能 = Why / What / How，3 列卡）
    - 1 行学员桥（你今天进来 vs 离开）

    完全丢弃 v1.5 的 5 层金字塔 / 时代条 / 章节路线条 / Arrow 标尺。
    参见 docs/training/specs/2026-05-17-p4-dual-axis-redesign.md（X5 section）
    """
    from pptx.enum.shapes import MSO_SHAPE as _MS
    s = prs.slides.add_slide(prs.slide_layouts[3])
    _title(s, "§0 · 6 小时教你的不是 prompt · 是让 AI 不能不靠谱的工程")

    # 副标题
    _add_textbox(s, Inches(0.55), Inches(1.25), Inches(12.2), Inches(0.35),
                 "1 个核心论断 + 3 条工程支撑 · 思想 / 战略 / 技能 = Why / What / How",
                 size_pt=14, color=GRAY_700, align=PP_ALIGN.CENTER)

    # ============================================================
    # 上半 · Hero 论断对照（y=1.70-3.85, h=2.15）
    # 左 ✗ 行业范式  /  中 →  /  右 ✓ AGF 范式
    # ============================================================
    hero_y, hero_h = 1.70, 2.15
    left_x, left_w = 0.55, 5.50
    right_x, right_w = 7.30, 5.50
    arrow_x, arrow_w = 6.10, 1.15

    # --- 左卡 · WRONG 范式 ---
    left = s.shapes.add_shape(_MS.RECTANGLE,
                               Inches(left_x), Inches(hero_y),
                               Inches(left_w), Inches(hero_h))
    left.fill.solid(); left.fill.fore_color.rgb = GRAY_50
    left.line.color.rgb = GRAY_300; left.line.width = Pt(0.75)
    # 顶部 header 条
    _add_textbox(s, Inches(left_x + 0.15), Inches(hero_y + 0.08),
                 Inches(left_w - 0.3), Inches(0.32),
                 "✗  行业普遍范式", size_pt=13, bold=True, color=GRAY_700,
                 align=PP_ALIGN.LEFT, anchor=MSO_ANCHOR.MIDDLE)
    # 大字 quote
    _add_textbox(s, Inches(left_x + 0.15), Inches(hero_y + 0.45),
                 Inches(left_w - 0.3), Inches(0.65),
                 "「靠 prompt」", size_pt=28, bold=True, color=GRAY_500,
                 align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
    # 信念 行
    _add_textbox(s, Inches(left_x + 0.20), Inches(hero_y + 1.18),
                 Inches(0.55), Inches(0.28),
                 "信念", size_pt=10, bold=True, color=GRAY_500,
                 anchor=MSO_ANCHOR.MIDDLE)
    _add_textbox(s, Inches(left_x + 0.85), Inches(hero_y + 1.18),
                 Inches(left_w - 1.0), Inches(0.28),
                 "写更好的 prompt → AI 自觉靠谱",
                 size_pt=12, color=GRAY_700, anchor=MSO_ANCHOR.MIDDLE)
    # 后果 行
    _add_textbox(s, Inches(left_x + 0.20), Inches(hero_y + 1.55),
                 Inches(0.55), Inches(0.28),
                 "后果", size_pt=10, bold=True, color=GRAY_500,
                 anchor=MSO_ANCHOR.MIDDLE)
    _add_textbox(s, Inches(left_x + 0.85), Inches(hero_y + 1.55),
                 Inches(left_w - 1.0), Inches(0.28),
                 "70% 公司装 AI 不产能",
                 size_pt=12, color=GRAY_700, anchor=MSO_ANCHOR.MIDDLE)
    # 脚注（数据来源）
    _add_textbox(s, Inches(left_x + 0.15), Inches(hero_y + 1.85),
                 Inches(left_w - 0.3), Inches(0.22),
                 "[据 IDC 2024 / Gartner 2024 AI 落地调研]",
                 size_pt=8, color=GRAY_500, align=PP_ALIGN.RIGHT)

    # --- 中间 → 大箭头 ---
    _add_textbox(s, Inches(arrow_x), Inches(hero_y),
                 Inches(arrow_w), Inches(hero_h),
                 "→", size_pt=64, bold=True, color=CORAL,
                 align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)

    # --- 右卡 · AGF 范式 ---
    right = s.shapes.add_shape(_MS.RECTANGLE,
                                Inches(right_x), Inches(hero_y),
                                Inches(right_w), Inches(hero_h))
    right.fill.solid(); right.fill.fore_color.rgb = CORAL_LIGHT
    right.line.color.rgb = CORAL; right.line.width = Pt(1.0)
    # 顶部 header 条
    _add_textbox(s, Inches(right_x + 0.15), Inches(hero_y + 0.08),
                 Inches(right_w - 0.3), Inches(0.32),
                 "✓  AGF 工程范式", size_pt=13, bold=True, color=CORAL,
                 align=PP_ALIGN.LEFT, anchor=MSO_ANCHOR.MIDDLE)
    # 大字 quote
    _add_textbox(s, Inches(right_x + 0.15), Inches(hero_y + 0.45),
                 Inches(right_w - 0.3), Inches(0.65),
                 "「靠 enforcement」", size_pt=28, bold=True, color=CORAL,
                 align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
    # 信念 行
    _add_textbox(s, Inches(right_x + 0.20), Inches(hero_y + 1.18),
                 Inches(0.55), Inches(0.28),
                 "信念", size_pt=10, bold=True, color=CORAL,
                 anchor=MSO_ANCHOR.MIDDLE)
    _add_textbox(s, Inches(right_x + 0.85), Inches(hero_y + 1.18),
                 Inches(right_w - 1.0), Inches(0.28),
                 "设计 enforcement → AI 不能不靠谱",
                 size_pt=12, bold=True, color=NAVY, anchor=MSO_ANCHOR.MIDDLE)
    # 后果 行
    _add_textbox(s, Inches(right_x + 0.20), Inches(hero_y + 1.55),
                 Inches(0.55), Inches(0.28),
                 "后果", size_pt=10, bold=True, color=CORAL,
                 anchor=MSO_ANCHOR.MIDDLE)
    _add_textbox(s, Inches(right_x + 0.85), Inches(hero_y + 1.55),
                 Inches(right_w - 1.0), Inches(0.28),
                 "6 阶段门 + 14 角色 + 4 层 hook · 全部可复制",
                 size_pt=12, bold=True, color=NAVY, anchor=MSO_ANCHOR.MIDDLE)

    # ============================================================
    # 下半 · 3 条工程支撑（y=4.00-6.45, h=2.45）
    # ============================================================
    sup_y, sup_h = 4.00, 2.45
    sup_w = 4.07
    sup_gap = 0.075
    sup_xs = [0.55 + i * (sup_w + sup_gap) for i in range(3)]

    supports = [
        # (chip_label, chip_color, statement_line1, statement_line2, [bullets])
        ("支撑 1 · 思想 / Why", GOLD,
         "心法嵌路径",
         "不靠 agent 自觉",
         [
             "PreToolUse hook 硬拦 `rm -rf` / `DROP TABLE`",
             "scan-secrets.sh 阻断 AWS / OpenAI key 提交",
             "validate-task-schema.sh 派单时校验 6 段齐全",
         ]),
        ("支撑 2 · 战略 / What", NAVY,
         "角色制衡 + 阶段门",
         "不让单一 agent 独大",
         [
             "14 agent 各管一段 · 无 1 个独大",
             "6 阶段门：PRD → Design → Code → SIT → E2E → UAT",
             "reviewer + qa 双维 review 互不可旁路",
         ]),
        ("支撑 3 · 技能 / How", CORAL,
         "可复制工程资产",
         "今天就能 fork",
         [
             "14 `.claude/agents/*.md` + 8 `.claude/skills/*/SKILL.md`",
             "4 slash cmd · 一键派工 / UAT / 复盘",
             "41 张 draw.io 架构图 · Template repo 可整体 fork",
         ]),
    ]

    for i, (chip_label, chip_color, st1, st2, bullets) in enumerate(supports):
        x = sup_xs[i]
        # 卡 body（白底灰边 · 矩形不圆角以匹配 chip 直角）
        body = s.shapes.add_shape(_MS.RECTANGLE,
                                   Inches(x), Inches(sup_y),
                                   Inches(sup_w), Inches(sup_h))
        body.fill.solid(); body.fill.fore_color.rgb = WHITE
        body.line.color.rgb = GRAY_300; body.line.width = Pt(0.75)
        # chip 头条（顶部 0.36" 高的色条）
        chip = s.shapes.add_shape(_MS.RECTANGLE,
                                   Inches(x), Inches(sup_y),
                                   Inches(sup_w), Inches(0.36))
        chip.fill.solid(); chip.fill.fore_color.rgb = chip_color
        chip.line.fill.background()
        _add_textbox(s, Inches(x), Inches(sup_y), Inches(sup_w), Inches(0.36),
                     chip_label, size_pt=13, bold=True, color=WHITE,
                     align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
        # statement 2 行
        _add_textbox(s, Inches(x + 0.15), Inches(sup_y + 0.47),
                     Inches(sup_w - 0.3), Inches(0.32),
                     st1, size_pt=15, bold=True, color=NAVY,
                     align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
        _add_textbox(s, Inches(x + 0.15), Inches(sup_y + 0.80),
                     Inches(sup_w - 0.3), Inches(0.26),
                     st2, size_pt=11, color=GRAY_700,
                     align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
        # statement 与 bullets 之间的分隔线
        div = s.shapes.add_shape(_MS.RECTANGLE,
                                  Inches(x + 0.4), Inches(sup_y + 1.12),
                                  Inches(sup_w - 0.8), Inches(0.012))
        div.fill.solid(); div.fill.fore_color.rgb = chip_color
        div.line.fill.background()
        # 3 个 bullet
        for j, bullet in enumerate(bullets):
            by = sup_y + 1.22 + j * 0.40
            # 短竖线 ▎ 前缀
            _add_textbox(s, Inches(x + 0.18), Inches(by),
                         Inches(0.18), Inches(0.36),
                         "▎", size_pt=14, bold=True, color=chip_color,
                         anchor=MSO_ANCHOR.MIDDLE)
            _add_textbox(s, Inches(x + 0.38), Inches(by),
                         Inches(sup_w - 0.55), Inches(0.40),
                         bullet, size_pt=10, color=GRAY_900,
                         anchor=MSO_ANCHOR.MIDDLE)

    # ============================================================
    # 底部 · 学员桥（y=6.60-7.30, h=0.70）
    # ============================================================
    bridge_y, bridge_h = 6.60, 0.70
    bridge = s.shapes.add_shape(_MS.RECTANGLE,
                                 Inches(0.55), Inches(bridge_y),
                                 Inches(12.2), Inches(bridge_h))
    bridge.fill.solid(); bridge.fill.fore_color.rgb = GRAY_50
    bridge.line.color.rgb = GRAY_300; bridge.line.width = Pt(0.5)
    _add_textbox(s, Inches(0.55), Inches(bridge_y + 0.05),
                 Inches(12.2), Inches(0.30),
                 "你今天进来：以为来学 prompt",
                 size_pt=13, color=GRAY_700, align=PP_ALIGN.CENTER,
                 anchor=MSO_ANCHOR.MIDDLE)
    _add_textbox(s, Inches(0.55), Inches(bridge_y + 0.35),
                 Inches(12.2), Inches(0.30),
                 "你今天离开：知道「AI 工程力 = 设计让 AI 不能不靠谱」",
                 size_pt=14, bold=True, color=CORAL, align=PP_ALIGN.CENTER,
                 anchor=MSO_ANCHOR.MIDDLE)
    return s


def build_slide_5_self_positioning(prs):
    """slide 5: 5 分钟自定位 · 嵌入 G2 P1/P2/P3 三栏"""
    s = prs.slides.add_slide(prs.slide_layouts[3])
    _title(s, "§0 · 5 分钟自定位 · 你是 P1 / P2 / P3？")
    _add_centered_image(s, "G2-self-positioning.png")
    return s


def build_slide_6_three_pain_points(prs):
    """slide 6: 软件交付 3 个老大难 · 嵌入 B1"""
    s = prs.slides.add_slide(prs.slide_layouts[3])
    _title(s, "§0 · 软件交付 3 个老大难（没 Agile/Scrum/TDD 的世界）")
    _add_centered_image(s, "B1-three-pain-points.png")
    return s


def build_slide_7_agile_vs_waterfall(prs):
    """slide 7: Agile · v1.3 双栏（左 定义+目标+4 价值观 / 右 B2 类比图）
    清掉 v1.2 脚注的 emoji；保留 Manifesto 2001-02 雪鸟会议出处。
    """
    s = prs.slides.add_slide(prs.slide_layouts[3])
    _title(s, "§0 · Agile · 心法不是方法（两周一交付 vs 一锤子三月）")

    # ===== 左栏 5.40" · y=1.50 → 6.30（h=4.80）=====
    L_X, L_Y, L_W = 0.55, 1.50, 5.40
    _draw_left_section(s, L_X, L_Y, L_W, 1.10, "定义",
        body="以「持续交付可工作软件」为核心、「拥抱变化」的协作心法。"
             "17 位敏捷方法先驱 2001-02 雪鸟会议确立。",
        accent=CORAL, body_size=12)
    _draw_left_section(s, L_X, L_Y + 1.20, L_W, 1.10, "目标",
        body="把交付从「一锤子 3 个月」改成「两周一次」—— "
             "需求改了不致命、用户早看见、团队早调整。",
        accent=CORAL, body_size=12, body_bold=True)
    _draw_left_section(s, L_X, L_Y + 2.40, L_W, 2.40, "4 条价值观（Manifesto）",
        bullets=[
            "个体与互动 > 过程与工具",
            "可工作软件 > 详尽文档",
            "客户协作 > 合同谈判",
            "响应变化 > 遵循计划",
        ],
        accent=GOLD, bullet_size=12)

    # ===== 右栏 6.78" · 嵌入 B2 类比图 =====
    _add_panel_image(s, "B2-agile-vs-waterfall.png",
                     panel_x=6.10, panel_y=1.50, panel_w=6.78, panel_h=4.80)

    # ===== 底部脚注（清 emoji · KEEP 出处）=====
    _add_textbox(s, Inches(0.55), Inches(6.40), Inches(12.2), Inches(0.35),
                 "Agile Manifesto · 2001-02 · Snowbird Utah · 17 人签署"
                 "（Kent Beck / Martin Fowler / Sutherland / Schwaber / Uncle Bob 等）",
                 size_pt=12, color=NAVY, align=PP_ALIGN.CENTER, bold=True)
    _add_textbox(s, Inches(0.55), Inches(6.80), Inches(12.2), Inches(0.30),
                 "agilemanifesto.org · Martin Fowler 现场记录："
                 "martinfowler.com/articles/agileStory.html",
                 size_pt=10, color=GRAY_500, align=PP_ALIGN.CENTER)
    return s


def build_slide_8_scrum_football(prs):
    """slide 8: Scrum · v1.3 双栏（左 定义+目标+3+5+1 要点 / 右 B3 类比图）
    清掉 v1.2 脚注的 emoji；保留 Sutherland 1993 + Scrum Guide 出处。
    """
    s = prs.slides.add_slide(prs.slide_layouts[3])
    _title(s, "§0 · Scrum · 足球队类比 · Agile 的可执行框架")

    # ===== 左栏 5.40" · y=1.50 → 6.30（h=4.80）=====
    L_X, L_Y, L_W = 0.55, 1.50, 5.40
    _draw_left_section(s, L_X, L_Y, L_W, 1.10, "定义",
        body="实施 Agile 的「轻量级框架」—— 3 角色 + 5 仪式 + 1 时间盒（Sprint）。"
             "Sutherland 1993 首次实施。",
        accent=CORAL, body_size=12)
    _draw_left_section(s, L_X, L_Y + 1.20, L_W, 1.10, "目标",
        body="把 Agile 心法落成「可重复的执行节奏」—— "
             "团队不靠默契、不靠加班、靠固定节拍持续交付。",
        accent=CORAL, body_size=12, body_bold=True)
    _draw_left_section(s, L_X, L_Y + 2.40, L_W, 2.40, "3 角色 + 5 仪式 + 1 时间盒",
        bullets=[
            "3 角色：PO（要什么）/ SM（怎么协作）/ Team（怎么实现）",
            "5 仪式：Planning · Daily · Review · Retro · Refinement",
            "1 时间盒：Sprint 1-4 周（AGF 用 2 周）",
            "产物：Product Backlog → Sprint Backlog → Increment",
        ],
        accent=GOLD, bullet_size=11)

    # ===== 右栏 6.78" · 嵌入 B3 足球队类比图 =====
    _add_panel_image(s, "B3-scrum-football.png",
                     panel_x=6.10, panel_y=1.50, panel_w=6.78, panel_h=4.80)

    # ===== 底部脚注（清 emoji · KEEP 出处）=====
    _add_textbox(s, Inches(0.55), Inches(6.75), Inches(12.2), Inches(0.4),
                 "Sutherland @ Easel Corp 1993 首次实施 · 改自 Takeuchi & Nonaka HBR 1986 · "
                 "Schwaber & Sutherland《Scrum Guide》2020-11 latest · scrumguides.org",
                 size_pt=11, color=NAVY, align=PP_ALIGN.CENTER, bold=True)
    return s


def _draw_tdd_triangle(s, *, center_text, center_subtext,
                       anchor_cx=6.667, anchor_cy=4.43, scale=1.0,
                       labels=None):
    """TDD 三角循环 helper · 借鉴 template t-10 三角形循环
    3 顶点圆 (Red 顶 / Green 右下 / Refactor 左下) + 顺时针箭头 + 中心金句

    anchor_cx / anchor_cy: 三角形几何中心（默认 6.667 / 4.43 兼容 v1.3 全幅版）
    scale: 1.0 = 原大小（4.7" 高 × 5.87" 宽 · 全幅居中）
           0.92 = 适配右栏 6.78×4.80（slide 9 双栏版）
    labels: 3 元组 list [(顶 Red, sub), (右下 Green, sub), (左下 Refactor, sub)]
            默认 = 原 TDD canonical 含 ① ② ③ 前缀（M5 caller 兼容）；
            slide 9 双栏版传 no-prefix + 紧凑 sub 以防圆内文字溢出。
    """
    from pptx.enum.shapes import MSO_SHAPE as _MS
    if labels is None:
        labels = [
            ("① Red",      "先写一个失败的测试"),
            ("② Green",    "最少代码让 test 过"),
            ("③ Refactor", "改进设计（测试保护下）"),
        ]
    # 顶点相对几何中心的偏移（原版坐标算出来的）
    R = 0.9 * scale  # radius in inches
    vertex_offsets = [
        # (dx, dy, color, label_color)
        (0,        -1.93, CORAL, WHITE),
        (+2.033,   +0.97, GREEN, WHITE),
        (-2.034,   +0.97, GOLD,  NAVY),
    ]
    # 与 labels 拼成完整顶点描述
    vertex_offsets = [
        (dx, dy, color, lbl, sub, lc)
        for ((dx, dy, color, lc), (lbl, sub)) in zip(vertex_offsets, labels)
    ]
    for (dx, dy, color, label, sub, lc) in vertex_offsets:
        cx = anchor_cx + dx * scale
        cy = anchor_cy + dy * scale
        x = Inches(cx - R); y = Inches(cy - R)
        d = Inches(2 * R)
        circle = s.shapes.add_shape(_MS.OVAL, x, y, d, d)
        circle.fill.solid(); circle.fill.fore_color.rgb = color
        circle.line.color.rgb = NAVY
        circle.line.width = Pt(max(1.0, 2 * scale))
        # 圆内文字（用 textbox 覆盖以保中文字体）
        _add_textbox(s, x, y + Inches(0.35 * scale), d, Inches(0.6 * scale),
                     label, size_pt=max(10, int(round(22 * scale))),
                     bold=True, color=lc,
                     align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
        _add_textbox(s, x, y + Inches(0.95 * scale), d, Inches(0.65 * scale),
                     sub, size_pt=max(8, int(round(12 * scale))), color=lc,
                     align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
    # 3 箭头（弯曲连接：Red→Green→Refactor→Red 顺时针）
    # 端点相对几何中心的偏移
    arrow_offsets = [
        ((+0.833, -1.13), (+1.333, +0.17)),   # Red → Green
        ((+1.033, +1.57), (-1.067, +1.57)),   # Green → Refactor
        ((-1.367, +0.17), (-0.767, -1.13)),   # Refactor → Red
    ]
    for ((fx_o, fy_o), (tx_o, ty_o)) in arrow_offsets:
        fx = anchor_cx + fx_o * scale
        fy = anchor_cy + fy_o * scale
        tx = anchor_cx + tx_o * scale
        ty = anchor_cy + ty_o * scale
        line = s.shapes.add_connector(1, Inches(fx), Inches(fy), Inches(tx), Inches(ty))  # 1=straight
        line.line.color.rgb = NAVY
        line.line.width = Pt(max(1.5, 3 * scale))
        # 末端箭头
        from pptx.oxml.ns import qn as _qn
        from lxml import etree as _etree
        spPr = line.line._get_or_add_ln()
        tail = _etree.SubElement(spPr, _qn("a:tailEnd"))
        tail.set("type", "triangle"); tail.set("w", "lg"); tail.set("len", "lg")
    # 中心文字（金句 + 副字）· 相对 anchor 偏移
    _add_textbox(s, Inches(anchor_cx - 1.0 * scale), Inches(anchor_cy - 0.73 * scale),
                 Inches(2.0 * scale), Inches(0.5 * scale),
                 center_text, size_pt=max(10, int(round(15 * scale))),
                 bold=True, color=CORAL,
                 align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
    _add_textbox(s, Inches(anchor_cx - 1.0 * scale), Inches(anchor_cy - 0.23 * scale),
                 Inches(2.0 * scale), Inches(0.4 * scale),
                 center_subtext, size_pt=max(7, int(round(10 * scale))),
                 color=GRAY_700,
                 align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)


def build_slide_9_tdd_cycle(prs):
    """slide 9: TDD · v1.4 双栏（左 定义+目标+3 步要点 / 右 缩小三角图 scale=0.90）
    清掉 v1.3 脚注的 emoji；保留 Kent Beck 出处 + 中文版书名。
    """
    s = prs.slides.add_slide(prs.slide_layouts[3])
    _title(s, "§0 · TDD · 先写测试 = 先想验收")

    # ===== 左栏 5.40" · y=1.50 → 6.30（h=4.80）=====
    L_X, L_Y, L_W = 0.55, 1.50, 5.40
    _draw_left_section(s, L_X, L_Y, L_W, 1.10, "定义",
        body="「先写测试 → 最少代码让测试通过 → 在测试保护下重构」—— "
             "3 步循环编码法。Kent Beck 2002 系统化。",
        accent=CORAL, body_size=12)
    _draw_left_section(s, L_X, L_Y + 1.20, L_W, 1.10, "目标",
        body="把「验收」从交付最后挪到设计最前 · 让重构有底气（测试是网）· "
             "让 AI 写的代码不能糊弄人。",
        accent=CORAL, body_size=12, body_bold=True)
    _draw_left_section(s, L_X, L_Y + 2.40, L_W, 2.40, "3 步循环 + 节奏",
        bullets=[
            "① Red — 先写一个失败的测试",
            "② Green — 最少代码让 test 通过",
            "③ Refactor — 测试保护下改进设计",
            "节奏：1 cycle 1-10 分钟",
        ],
        accent=GOLD, bullet_size=12)

    # ===== 右栏 6.78" · TDD 三角缩到 0.92 倍居中 =====
    # anchor_cx = 6.10 + 6.78/2 = 9.49 · anchor_cy = 4.35（防 Red 顶溢出 panel 上沿）
    # 圆内标签去掉 ① ② ③ 前缀（左栏 bullets 已编号）· sub 简化避免 wrap
    _draw_tdd_triangle(s, center_text="Kent Beck\nCanon TDD",
                       center_subtext="每个 cycle 1-10 分钟",
                       anchor_cx=9.49, anchor_cy=4.35, scale=0.92,
                       labels=[
                           ("Red",      "先写失败的测试"),
                           ("Green",    "最少代码让 test 过"),
                           ("Refactor", "测试保护下改进设计"),
                       ])

    # ===== 底部脚注（清 emoji · KEEP Kent Beck 出处）=====
    _add_textbox(s, Inches(0.55), Inches(6.55), Inches(12.2), Inches(0.35),
                 "Kent Beck: \"a minute-by-minute micro-commitment to good design\"",
                 size_pt=12, color=GRAY_700, align=PP_ALIGN.CENTER, bold=True)
    _add_textbox(s, Inches(0.55), Inches(6.95), Inches(12.2), Inches(0.35),
                 "Kent Beck《Test-Driven Development: By Example》2002 · ISBN 0321146530 · "
                 "中文版《测试驱动开发》中国电力出版社",
                 size_pt=10, color=NAVY, align=PP_ALIGN.CENTER, bold=True)
    return s


def build_slide_10_agf_definition(prs):
    """slide 10: AGF 一句话定位 · 嵌入 A1 三层落地图"""
    s = prs.slides.add_slide(prs.slide_layouts[3])
    _title(s, "§0 · AGF 一句话定位 · 心法嵌进 enforcement 路径")
    _add_centered_image(s, "A1-agf-three-layer.png", top_inch=1.4, height_inch=5.0)
    _add_textbox(s, Inches(0.55), Inches(6.6), Inches(12.2), Inches(0.5),
                 "不靠 agent 自觉 · 靠 hook + skill + DoD 三道齿轮兜底",
                 size_pt=15, color=CORAL, align=PP_ALIGN.CENTER, bold=True)
    return s


def build_slide_11_postcard_story(prs):
    """slide 11: 旅游明信片主线 · 嵌入 A3 三步流水线 + 故事钩子 (overview，§3 deep dive)"""
    s = prs.slides.add_slide(prs.slide_layouts[3])
    _title(s, "§0 · 今天主线：旅游明信片生成器")
    # 上方故事钩子
    _add_textbox(s, Inches(0.55), Inches(1.5), Inches(12.2), Inches(0.5),
                 "晚 8 点业务方：旅游回来想发朋友圈，懒得写文案——做个工具",
                 size_pt=16, color=GRAY_700, align=PP_ALIGN.CENTER)
    _add_centered_image(s, "A3-postcard-pipeline.png", top_inch=2.0, height_inch=4.3)
    # 与 p48 区分：本页 overview·先建立 mental model，§3（p48）会 deep dive
    _add_textbox(s, Inches(0.55), Inches(6.5), Inches(12.2), Inches(0.4),
                 "本节 overview · §3（p48）将深入拆 PRD AC / 派单 schema / "
                 "ml-engineer 选型",
                 size_pt=12, color=GRAY_500, align=PP_ALIGN.CENTER, bold=True)
    return s


def build_slide_12_timeline_and_setup(prs):
    """slide 12: 6h 时间线 + 你需要装什么 · 双栏自画 checklist"""
    s = prs.slides.add_slide(prs.slide_layouts[3])
    _title(s, "§0 · 6h 时间线 + 你需要装什么")

    # 左半：时间线
    _add_textbox(s, Inches(0.55), Inches(1.5), Inches(5.8), Inches(0.5),
                 "⏰ 6h 时间线", size_pt=22, bold=True, color=NAVY)
    timeline_items = [
        "9:00-9:30   §0 开场 + 垫层",
        "9:30-10:13  §2 Git 基础",
        "10:13-10:23 休息 1",
        "10:23-12:00 §3+§4 场景 A/B",
        "12:00-12:30 午休",
        "12:30-14:18 §5+§6 场景 C/D",
        "14:18-14:28 休息 2",
        "14:28-15:28 §7 综合 Lab",
        "15:28-15:56 §8 拓展",
        "15:56-16:11 §9 收尾 + Q&A",
    ]
    for i, item in enumerate(timeline_items):
        _add_textbox(s, Inches(0.7), Inches(2.1 + i * 0.42), Inches(5.7), Inches(0.4),
                     f"▎ {item}", size_pt=13, color=GRAY_700)

    # 右半：装什么 checklist
    _add_textbox(s, Inches(6.8), Inches(1.5), Inches(5.8), Inches(0.5),
                 "你需要装什么", size_pt=22, bold=True, color=NAVY)
    setup_items = [
        "Claude Code ≥ v2.1.130",
        "Git ≥ 2.30",
        "GitHub 账号（含 SSH key）",
        "Python ≥ 3.10",
        "豆包 API key（火山引擎方舟 console）",
        "VS Code / Cursor（推荐）",
        "draw.io desktop（看 41 张架构图）",
    ]
    for i, item in enumerate(setup_items):
        _add_textbox(s, Inches(6.95), Inches(2.1 + i * 0.5), Inches(5.7), Inches(0.4),
                     f"✓ {item}", size_pt=14, color=GRAY_700)
    # 底部注解
    _add_textbox(s, Inches(0.55), Inches(6.6), Inches(12.2), Inches(0.4),
                 "macOS 主测 · Win/Linux 兼容 · 豆包 API 全程不超 ¥10 / 学员",
                 size_pt=12, color=GRAY_500, align=PP_ALIGN.CENTER)
    return s


# ============================================================
# v1.2 新增 4 页：理论根源 + Vibe Coding + Claude Code + 对比
# ============================================================

def build_slide_13_theory_lineage(prs):
    """v1.2 NEW: 理论根源时间线 + 权威书单（左右双栏）"""
    s = prs.slides.add_slide(prs.slide_layouts[3])
    _title(s, "§0 · 三件套理论根源 · 时间线 + 权威书单")

    # 左栏：时间线（紧凑单行布局：when | who · what）
    _add_textbox(s, Inches(0.55), Inches(1.45), Inches(6.3), Inches(0.5),
                 "⏳ 30 年发展时间线", size_pt=20, bold=True, color=NAVY)
    timeline = [
        ("1986",    "Takeuchi & Nonaka · HBR《New New Product Development Game》"),
        ("1993",    "Sutherland @ Easel · 首次实施 Scrum（与 Scumniotales / McKenna）"),
        ("1995",    "OOPSLA · Austin TX · Ken Schwaber 公开 Scrum 论文"),
        ("2001-02", "Snowbird Utah · 17 人签 Agile Manifesto"),
        ("2002",    "Kent Beck《Test-Driven Development: By Example》"),
        ("2010",    "Schwaber + Sutherland · 首版《Scrum Guide》"),
        ("2020-11", "Schwaber + Sutherland · Scrum Guide 最新修订版"),
    ]
    for i, (when, what) in enumerate(timeline):
        y = Inches(2.05 + i * 0.62)
        # 左：时间标签
        _add_textbox(s, Inches(0.55), y, Inches(1.2), Inches(0.5),
                     when, size_pt=14, bold=True, color=CORAL,
                     anchor=MSO_ANCHOR.MIDDLE)
        # 右：事件单行
        _add_textbox(s, Inches(1.8), y, Inches(5.1), Inches(0.5),
                     what, size_pt=12, color=NAVY,
                     anchor=MSO_ANCHOR.MIDDLE)
        # 分隔线（轻灰）
        if i < len(timeline) - 1:
            from pptx.enum.shapes import MSO_SHAPE as _MS
            sep = s.shapes.add_shape(_MS.RECTANGLE,
                                      Inches(0.55), y + Inches(0.55),
                                      Inches(6.35), Inches(0.01))
            sep.fill.solid(); sep.fill.fore_color.rgb = GRAY_300
            sep.line.fill.background()

    # 右栏：中文版权威书单（含封面缩略图）
    _add_textbox(s, Inches(7.1), Inches(1.45), Inches(5.7), Inches(0.5),
                 "中文版权威书单（含封面）", size_pt=20, bold=True, color=NAVY)
    # v1.2.1: 全部改为中文版 + 加封面图（来源：豆瓣 large 版封面）
    books = [
        ("tdd.jpg", "测试驱动开发",
         "Kent Beck 著 · 孙平平 / 张小龙 译",
         "中国电力出版社 · 2004 · ISBN 9787508321738", CORAL),
        ("scrum.jpg", "敏捷革命",
         "Jeff Sutherland 著 · 蒋宗强 译",
         "中信出版社 · 2017 · ISBN 9787508671437", GREEN),
        ("clean-code.jpg", "代码整洁之道",
         "Robert C. Martin 著 · 韩磊 译",
         "人民邮电出版社 · 2010 · ISBN 9787115216878", BLUE_ACCENT),
        ("clean-agile.jpg", "敏捷整洁之道：回归本源",
         "Robert C. Martin 著",
         "人民邮电出版社 · 2020 · ISBN 9787115537157", GOLD),
        ("refactoring.jpg", "重构 · 改善既有代码的设计（第 2 版）",
         "Martin Fowler 著 · 熊节 / 林从羽 译",
         "人民邮电出版社 · 2019 · ISBN 9787115508652", NAVY),
    ]
    for i, (cover, title_cn, author, src, c) in enumerate(books):
        y = Inches(1.95 + i * 1.05)
        _add_card(s, Inches(7.1), y, Inches(5.7), Inches(0.98),
                  fill=WHITE, border=c, accent=c)
        # 左侧封面缩略图（按高 0.85 in 等比缩放，宽约 0.55-0.65 in）
        cover_path = BOOK_COVER_DIR / cover
        if cover_path.exists():
            s.shapes.add_picture(str(cover_path),
                                  Inches(7.3), y + Inches(0.07),
                                  height=Inches(0.85))
        # 右侧文字（中文书名 / 作者译者 / 出版信息）
        text_x = Inches(8.05)
        text_w = Inches(4.7)
        _add_textbox(s, text_x, y + Inches(0.05), text_w, Inches(0.36),
                     title_cn, size_pt=13, bold=True, color=c)
        _add_textbox(s, text_x, y + Inches(0.4), text_w, Inches(0.28),
                     author, size_pt=10, color=NAVY)
        _add_textbox(s, text_x, y + Inches(0.68), text_w, Inches(0.26),
                     src, size_pt=9, color=GRAY_500)
    # 底部小注：Scrum Guide 免费 PDF
    _add_textbox(s, Inches(7.1), Inches(7.18), Inches(5.7), Inches(0.3),
                 "ℹ Scrum Guide 中文版免费 PDF · scrumguides.org/download",
                 size_pt=9, color=GRAY_500, align=PP_ALIGN.LEFT)
    return s


def build_slide_14_vibe_coding(prs):
    """v1.2 NEW: Vibe Coding 概念（Karpathy 2025-02）+ AGF 取舍对比"""
    s = prs.slides.add_slide(prs.slide_layouts[3])
    _title(s, "§0 · Vibe Coding · 概念 vs AGF 取舍")

    # Karpathy 原文金句卡（CORAL_LIGHT 底）
    _add_card(s, Inches(0.55), Inches(1.45), Inches(12.2), Inches(1.4),
              fill=CORAL_LIGHT, border=CORAL)
    _add_textbox(s, Inches(0.8), Inches(1.55), Inches(11.7), Inches(0.4),
                 "Andrej Karpathy · X · 2025-02-02：",
                 size_pt=14, bold=True, color=CORAL)
    _add_textbox(s, Inches(0.8), Inches(1.95), Inches(11.7), Inches(0.85),
                 '"fully give in to the vibes, embrace exponentials,\n'
                 ' and forget that the code even exists"',
                 size_pt=18, bold=True, color=NAVY, align=PP_ALIGN.CENTER)

    # 两栏对比：Vibe Coding 极致 vs AGF 取舍
    _add_card(s, Inches(0.55), Inches(3.1), Inches(5.95), Inches(3.4),
              fill=WHITE, border=GRAY_500, accent=GRAY_500)
    _add_textbox(s, Inches(0.8), Inches(3.2), Inches(5.5), Inches(0.5),
                 "✗ Vibe Coding 极致（Karpathy 原版）", size_pt=15, bold=True, color=GRAY_700)
    vibe_items = [
        "接受所有 AI 生成代码（不审 diff）",
        "用 SuperWhisper 语音输入 minimize 键盘",
        "错误 → 直接贴回 AI 让它修",
        "不写测试 · 不查文档 · 让代码 organically 生长",
        "适合 1 人玩具项目 / prototype",
        "副作用：15 小时清理（@nijho.lt 实测）",
    ]
    for i, item in enumerate(vibe_items):
        _add_textbox(s, Inches(0.95), Inches(3.85 + i * 0.42), Inches(5.4), Inches(0.4),
                     f"▎ {item}", size_pt=12, color=GRAY_700)

    _add_card(s, Inches(6.8), Inches(3.1), Inches(5.95), Inches(3.4),
              fill=WHITE, border=GREEN, accent=GREEN)
    _add_textbox(s, Inches(7.05), Inches(3.2), Inches(5.5), Inches(0.5),
                 "✓ AGF 取舍（保速度 + 加兜底）", size_pt=15, bold=True, color=GREEN)
    agf_items = [
        "接受 LLM 速度，但 4 层 hook 拦危险命令",
        "skill 强制路径（不走 skill 不行）",
        "DoD 9 项 checklist · 不达标任务不能 done",
        "AC 派单 + dev 自跑 SIT + code review 双 verdict",
        "适合 14 同事真协作 / 上生产",
        "目标：享 vibe 速度 · 免 cleanup 灾难",
    ]
    for i, item in enumerate(agf_items):
        _add_textbox(s, Inches(7.2), Inches(3.85 + i * 0.42), Inches(5.4), Inches(0.4),
                     f"▎ {item}", size_pt=12, color=GREEN)

    # 底部数据点
    _add_textbox(s, Inches(0.55), Inches(6.65), Inches(12.2), Inches(0.4),
                 "Collins English Dictionary · 2025 Word of the Year · en.wikipedia.org/wiki/Vibe_coding",
                 size_pt=11, color=NAVY, align=PP_ALIGN.CENTER, bold=True)
    return s


def build_slide_15_claude_code_intro(prs):
    """v1.2 NEW: Claude Code 简介（Anthropic 官方 CLI · 本课程的载体）"""
    s = prs.slides.add_slide(prs.slide_layouts[3])
    _title(s, "§0 · Claude Code · 本课程的运行环境")

    # 5 张关键信息卡（2×3 grid 中 5 张）
    cards = [
        ("发布时间线", "2025-02 preview / 2025-05 GA（与 Claude 4 同发）", CORAL),
        ("形态", "Agentic CLI · 终端原生 · 读整个 codebase · 执行 git", NAVY),
        ("核心能力", "写代码 / 跑测试 / git workflow / 多 agent 编排", GOLD),
        ("安装", "brew install claude-ai-cli · 或 npm i -g @anthropic/claude-code", GREEN),
        ("采用度", "2025-07 收入同比 5.5×（Anthropic 官方）", BLUE_ACCENT),
    ]
    for i, (head, body, c) in enumerate(cards):
        row, col = i // 2, i % 2
        x = Inches(0.55 + col * 6.25)
        y = Inches(1.5 + row * 1.4)
        w = Inches(6.05)
        h = Inches(1.25)
        _add_card(s, x, y, w, h, fill=WHITE, border=c, accent=c)
        _add_textbox(s, x + Inches(0.25), y + Inches(0.15), w - Inches(0.5), Inches(0.45),
                     head, size_pt=16, bold=True, color=c)
        _add_textbox(s, x + Inches(0.25), y + Inches(0.65), w - Inches(0.5), Inches(0.55),
                     body, size_pt=13, color=GRAY_700)

    # 底部：本课程为什么选它（CORAL chip 注脚）
    _add_textbox(s, Inches(0.55), Inches(5.75), Inches(12.2), Inches(0.5),
                 "AGF 选 Claude Code 原因：CLI 友好 · 多 agent 原生 · skill / hook 生态完整",
                 size_pt=16, bold=True, color=CORAL, align=PP_ALIGN.CENTER)
    # 出处
    _add_textbox(s, Inches(0.55), Inches(6.4), Inches(12.2), Inches(0.4),
                 "docs.anthropic.com/en/release-notes/claude-code · github.com/anthropics/claude-code",
                 size_pt=11, color=GRAY_500, align=PP_ALIGN.CENTER)
    _add_textbox(s, Inches(0.55), Inches(6.85), Inches(12.2), Inches(0.4),
                 "报告：Simon Willison 2025-10-20 · Claude Code for Web 上线 · simonwillison.net",
                 size_pt=11, color=GRAY_500, align=PP_ALIGN.CENTER)
    return s


def build_slide_16_claude_vs_codex(prs):
    """v1.2 NEW: Claude Code vs OpenAI Codex vs Cursor 对比矩阵"""
    s = prs.slides.add_slide(prs.slide_layouts[3])
    _title(s, "§0 · Claude Code vs Codex vs Cursor · 三选一矩阵")

    # 表头
    cols = ["维度", "Claude Code", "OpenAI Codex", "Cursor"]
    col_x = [Inches(0.55), Inches(3.0), Inches(6.5), Inches(10.0)]
    col_w = [Inches(2.4), Inches(3.45), Inches(3.45), Inches(2.8)]
    header_colors = [GRAY_700, CORAL, NAVY, GOLD]
    for i, (h, x, w, c) in enumerate(zip(cols, col_x, col_w, header_colors)):
        _add_card(s, x, Inches(1.4), w, Inches(0.55),
                  fill=c, border=c)
        _add_textbox(s, x + Inches(0.1), Inches(1.5), w - Inches(0.2), Inches(0.4),
                     h, size_pt=15, bold=True, color=WHITE, align=PP_ALIGN.CENTER)

    # 4 行对比数据
    rows = [
        ("形态",       "CLI 终端原生",                "Cloud agent + IDE + Web",         "VS Code Fork（GUI IDE）"),
        ("SWE-bench Verified",  "80.9%（Opus 4.6 · 第 1）",  "约 80%（GPT-5 系列）",            "取决于选用 model"),
        ("强项场景",   "多文件深度 / 大重构 / planning",       "后台异步 / batch / 长任务",        "日常实时编辑 / autocomplete"),
        ("订阅",       "$20–$220 / 月",               "$20–$220 / 月",                   "$20–$200 / 月"),
    ]
    for i, row in enumerate(rows):
        y = Inches(2.05 + i * 0.85)
        # 行底色（轻交替）
        row_color = WHITE if i % 2 == 0 else CORAL_LIGHT
        for j, (val, x, w) in enumerate(zip(row, col_x, col_w)):
            _add_card(s, x, y, w, Inches(0.78),
                      fill=row_color, border=GRAY_300)
            _add_textbox(s, x + Inches(0.15), y + Inches(0.1), w - Inches(0.3), Inches(0.6),
                         val, size_pt=12,
                         bold=(j == 0),
                         color=NAVY if j == 0 else GRAY_700)

    # AGF 选择理由 + 引用
    _add_textbox(s, Inches(0.55), Inches(5.6), Inches(12.2), Inches(0.5),
                 "AGF 用 Claude Code：CLI / 多 agent / skill 生态 → 与本课程的 14 同事编排天然契合",
                 size_pt=15, bold=True, color=CORAL, align=PP_ALIGN.CENTER)
    _add_textbox(s, Inches(0.55), Inches(6.25), Inches(12.2), Inches(0.4),
                 "benchmark 来源：SWE-bench Verified 2026 · Terminal-Bench 2.0 · builtin.com / mindstudio.ai 2026 综评",
                 size_pt=11, color=GRAY_500, align=PP_ALIGN.CENTER)
    _add_textbox(s, Inches(0.55), Inches(6.65), Inches(12.2), Inches(0.4),
                 "实践建议：多数开发者同时用 2 个 — Claude Code 做 planning + 大重构 / Cursor 做实时编辑",
                 size_pt=11, color=GRAY_700, align=PP_ALIGN.CENTER, bold=True)
    return s


# ============================================================
# 主调用
# ============================================================

def build(prs):
    """构建 §0 13 张内容 slide (v1.2: slide 4-16，原 9 张 + 新 4 张)"""
    build_slide_4_what_you_get(prs)
    build_slide_5_self_positioning(prs)
    build_slide_6_three_pain_points(prs)
    build_slide_7_agile_vs_waterfall(prs)        # v1.2 加权威注脚（Manifesto 出处）
    build_slide_8_scrum_football(prs)            # v1.2 加权威注脚（Sutherland 1993）
    build_slide_9_tdd_cycle(prs)                 # v1.2 加权威注脚（Beck 2002 书）
    build_slide_10_agf_definition(prs)
    build_slide_11_postcard_story(prs)
    build_slide_12_timeline_and_setup(prs)
    # v1.2 NEW · §0 保留理论根源时间线（与 Agile/Scrum/TDD 同章节）
    # v1.4: Vibe Coding / Claude Code 简介 / vs Codex 3 张 → 移入 §1 AI 工具基础章节
    # 这 3 个函数体保留供 M1_5.py 参考 / 复用，不在 build() 中调用
    build_slide_13_theory_lineage(prs)           # 理论根源时间线 + 中文版权威书单
