from __future__ import annotations

import csv
import math
from pathlib import Path

from openpyxl import load_workbook
from openpyxl.drawing.image import Image as XLImage
from openpyxl.styles import Alignment, Border, Font, PatternFill, Side
from openpyxl.utils import get_column_letter
from PIL import Image, ImageDraw, ImageFilter, ImageFont, ImageOps


ROOT = Path(__file__).resolve().parents[1]
SRC_XLSX = Path("/Users/jack/Downloads/产品周报_三部分汇报版_美化增强版_单Sheet_20260529.xlsx")
OUT_XLSX = ROOT / "outputs" / "product-weekly-report-visual-refresh-20260530.xlsx"
ASSET_INDEX = ROOT / "source-assets" / "candidate-image-source-index.csv"
DERIVED = ROOT / "derived-images"
FONT_PATH = Path("/System/Library/Fonts/Hiragino Sans GB.ttc")

NAVY = "#113A5C"
INK = "#1B2733"
MUTED = "#5D6B78"
BG = "#F4F7FA"
BLUE = "#2B72D6"
TEAL = "#20A779"
AMBER = "#F4B740"
CORAL = "#E85D5C"
PURPLE = "#7257D9"
GREEN = "#21A67A"
LINE = "#DCE5EE"


def font(size: int, bold: bool = False) -> ImageFont.FreeTypeFont:
    return ImageFont.truetype(str(FONT_PATH), size=size, index=0)


F12 = font(12)
F14 = font(14)
F16 = font(16)
F18 = font(18)
F20 = font(20)
F24 = font(24)
F28 = font(28)
F34 = font(34)
F42 = font(42)


def hex_rgb(value: str) -> tuple[int, int, int]:
    value = value.lstrip("#")
    return tuple(int(value[i : i + 2], 16) for i in (0, 2, 4))


def load_assets() -> dict[str, dict[str, str]]:
    with ASSET_INDEX.open(encoding="utf-8") as f:
        return {row["asset_key"]: row for row in csv.DictReader(f)}


ASSETS = load_assets()


def asset_img(key: str) -> Image.Image:
    return Image.open(ASSETS[key]["abs_path"]).convert("RGB")


def rounded_rect(draw: ImageDraw.ImageDraw, box, radius, fill, outline=None, width=1):
    draw.rounded_rectangle(box, radius=radius, fill=fill, outline=outline, width=width)


def shadowed_panel(canvas: Image.Image, box, radius=18, shadow=(8, 10), fill="white", outline="#DFE7EF"):
    x1, y1, x2, y2 = box
    shadow_layer = Image.new("RGBA", canvas.size, (0, 0, 0, 0))
    sd = ImageDraw.Draw(shadow_layer)
    sd.rounded_rectangle((x1 + shadow[0], y1 + shadow[1], x2 + shadow[0], y2 + shadow[1]), radius, fill=(18, 38, 58, 35))
    shadow_layer = shadow_layer.filter(ImageFilter.GaussianBlur(12))
    canvas.alpha_composite(shadow_layer)
    d = ImageDraw.Draw(canvas)
    rounded_rect(d, box, radius, fill, outline, 1)


def text_wrap(draw: ImageDraw.ImageDraw, text: str, fnt, max_width: int) -> list[str]:
    lines = []
    for para in text.split("\n"):
        cur = ""
        for ch in para:
            trial = cur + ch
            if draw.textbbox((0, 0), trial, font=fnt)[2] <= max_width:
                cur = trial
            else:
                if cur:
                    lines.append(cur)
                cur = ch
        if cur:
            lines.append(cur)
    return lines


def draw_text(draw, xy, text, fnt, fill=INK, max_width=None, line_gap=6, anchor=None):
    x, y = xy
    if max_width is None:
        draw.text((x, y), text, font=fnt, fill=fill, anchor=anchor)
        return
    for line in text_wrap(draw, text, fnt, max_width):
        draw.text((x, y), line, font=fnt, fill=fill)
        y += fnt.size + line_gap


def contain(im: Image.Image, size: tuple[int, int], bg="white") -> Image.Image:
    thumb = ImageOps.contain(im, size, Image.LANCZOS)
    out = Image.new("RGB", size, bg)
    out.paste(thumb, ((size[0] - thumb.width) // 2, (size[1] - thumb.height) // 2))
    return out


def cover(im: Image.Image, size: tuple[int, int]) -> Image.Image:
    return ImageOps.fit(im, size, Image.LANCZOS, centering=(0.5, 0.5))


def paste_round(canvas: Image.Image, im: Image.Image, box, radius=16, mode="cover", bg="white"):
    x1, y1, x2, y2 = box
    size = (x2 - x1, y2 - y1)
    fitted = cover(im, size) if mode == "cover" else contain(im, size, bg)
    mask = Image.new("L", size, 0)
    md = ImageDraw.Draw(mask)
    md.rounded_rectangle((0, 0, size[0], size[1]), radius=radius, fill=255)
    canvas.paste(fitted, (x1, y1), mask)


def draw_tag(draw, x, y, text, color, fnt=F16):
    w = draw.textbbox((0, 0), text, font=fnt)[2] + 28
    rounded_rect(draw, (x, y, x + w, y + 34), 17, color, None)
    draw.text((x + 14, y + 6), text, font=fnt, fill="white")
    return x + w + 10


def add_title(draw, title, subtitle, width, accent=TEAL):
    draw.rectangle((0, 0, width, 88), fill=hex_rgb(NAVY))
    draw.rectangle((0, 84, width, 90), fill=hex_rgb(accent))
    draw.text((36, 22), title, font=F34, fill="white")
    draw.text((36, 58), subtitle, font=F16, fill="#C8D8E8")


def metric_card(canvas, xy, title, value, desc, color):
    x, y = xy
    shadowed_panel(canvas, (x, y, x + 250, y + 138), radius=18, fill="white")
    d = ImageDraw.Draw(canvas)
    d.rounded_rectangle((x + 22, y + 18, x + 68, y + 64), radius=14, fill=hex_rgb(color))
    d.text((x + 84, y + 20), title, font=F18, fill=INK)
    d.text((x + 84, y + 48), value, font=F28, fill=hex_rgb(color))
    draw_text(d, (x + 24, y + 86), desc, F14, MUTED, max_width=205)


def screenshot_card(canvas, box, key, title, accent, mode="contain"):
    x1, y1, x2, y2 = box
    shadowed_panel(canvas, box, radius=18, fill="white")
    d = ImageDraw.Draw(canvas)
    d.rounded_rectangle((x1 + 18, y1 + 16, x1 + 26, y1 + 52), radius=4, fill=hex_rgb(accent))
    d.text((x1 + 36, y1 + 18), title, font=F18, fill=INK)
    paste_round(canvas, asset_img(key), (x1 + 18, y1 + 62, x2 - 18, y2 - 18), radius=14, mode=mode)


def panel_01_flow(path: Path):
    canvas = Image.new("RGBA", (1600, 620), BG)
    d = ImageDraw.Draw(canvas)
    add_title(d, "产品差异性主线", "AI 营养能力 × 数字技术能力：从功能亮点进入采购语言、控标参数和验收证据", 1600, TEAL)
    nodes = [
        ("AI 营养中枢", "健康画像 / 推荐 / 餐中指导 / 周期报告", TEAL),
        ("数字设备入口", "AI 识别结算台 / 称重台 / 移动端 / 证书材料", BLUE),
        ("控标与验收", "参数可写 / 演示可见 / 报告可交付 / 证据可复用", AMBER),
    ]
    xs = [52, 574, 1096]
    for i, (title, desc, color) in enumerate(nodes):
        x = xs[i]
        shadowed_panel(canvas, (x, 128, x + 450, 492), radius=24, fill="white")
        d.rounded_rectangle((x + 26, 154, x + 106, 234), radius=22, fill=hex_rgb(color))
        d.text((x + 126, 156), title, font=F30 if False else F28, fill=INK)
        draw_text(d, (x + 126, 198), desc, F18, MUTED, max_width=260)
        if i == 0:
            paste_round(canvas, asset_img("nutrition_mobile_stage"), (x + 28, 258, x + 170, 456), radius=18, mode="contain")
            paste_round(canvas, asset_img("nutrition_pc_home"), (x + 188, 258, x + 420, 456), radius=18, mode="cover")
        elif i == 1:
            paste_round(canvas, asset_img("nutrition_device_standing"), (x + 28, 260, x + 214, 456), radius=18, mode="contain")
            paste_round(canvas, asset_img("device_weigh_1"), (x + 232, 260, x + 420, 456), radius=18, mode="contain")
        else:
            paste_round(canvas, asset_img("certificate_kylin"), (x + 28, 260, x + 205, 456), radius=18, mode="contain")
            paste_round(canvas, asset_img("nutrition_pc_monitor"), (x + 222, 260, x + 420, 456), radius=18, mode="cover")
    for x in [514, 1036]:
        d.line((x, 310, x + 54, 310), fill=hex_rgb(TEAL), width=6)
        d.polygon([(x + 54, 310), (x + 34, 296), (x + 34, 324)], fill=hex_rgb(TEAL))
    draw_text(d, (52, 532), "汇报提示：差异性不靠口号，靠“原始系统截图 + 设备照片 + 证书材料 + 周期报告样例”组合成可被客户看见的证据链。", F20, INK, max_width=1460)
    canvas.convert("RGB").save(path, quality=95)


def panel_01_evidence(path: Path):
    canvas = Image.new("RGBA", (1500, 780), BG)
    d = ImageDraw.Draw(canvas)
    add_title(d, "差异性证据墙", "用原始系统截图和设备素材承接：健康画像、AI 识别、餐中指导、周期报告、国产化证书", 1500, BLUE)
    screenshot_card(canvas, (44, 126, 634, 394), "nutrition_pc_home", "管理端：营养健康数据驾驶舱", BLUE, mode="cover")
    screenshot_card(canvas, (666, 126, 1042, 394), "nutrition_mobile_daily", "移动端：营养日报", TEAL, mode="contain")
    screenshot_card(canvas, (1074, 126, 1456, 394), "nutrition_mobile_stage", "移动端：阶段报告", AMBER, mode="contain")
    screenshot_card(canvas, (44, 430, 396, 728), "device_recognizer_1", "AI 识别结算设备", CORAL, mode="contain")
    screenshot_card(canvas, (428, 430, 780, 728), "nutrition_device_standing", "双通道识别结算台", PURPLE, mode="contain")
    screenshot_card(canvas, (812, 430, 1114, 728), "certificate_kylin", "国产化证书材料", TEAL, mode="contain")
    metric_card(canvas, (1160, 448), "可写参数", "4 类", "截图、硬件、报告、证书共同进入控标表达。", BLUE)
    metric_card(canvas, (1160, 604), "可演示链路", "3 段", "餐前目标、餐中识别、餐后报告。", TEAL)
    canvas.convert("RGB").save(path, quality=95)


def panel_02_standardization(path: Path):
    canvas = Image.new("RGBA", (1600, 680), BG)
    d = ImageDraw.Draw(canvas)
    add_title(d, "产品标准化不是关流程", "每个项目拆成：标准能力、配置项、专项定制、可回流资产，避免一个项目拖散一个产品", 1600, AMBER)
    lanes = [
        ("标准能力", "主系统、账户、收银、订单、报表、设备入口", BLUE),
        ("配置项", "规则参数、权限入口、客户组织、设备组合", GREEN),
        ("专项定制", "第三方小程序、国产化内网、学校三端监管", AMBER),
        ("回流资产", "设备白名单、联调 SOP、验收截图、手册视频", CORAL),
    ]
    for i, (t, desc, color) in enumerate(lanes):
        x = 54 + i * 382
        shadowed_panel(canvas, (x, 132, x + 330, 330), radius=20, fill="white")
        d.rounded_rectangle((x + 24, 160, x + 86, 222), radius=18, fill=hex_rgb(color))
        d.text((x + 106, 160), t, font=F24, fill=INK)
        draw_text(d, (x + 28, 242), desc, F18, MUTED, max_width=270)
    for x in [386, 768, 1150]:
        d.line((x, 232, x + 44, 232), fill=hex_rgb("#9AAFC2"), width=5)
        d.polygon([(x + 44, 232), (x + 26, 220), (x + 26, 244)], fill=hex_rgb("#9AAFC2"))
    labels = [
        ("机场 / 赛迪", "营养结算 1.0 标准包", "nutrition_pc_home"),
        ("金斯瑞", "软硬件联调 SOP", "device_weigh_1"),
        ("江西 206", "纯内网 / 国产化部署", "certificate_tongxin"),
        ("江苏国信", "报餐订餐标准包", "nutrition_pc_monitor"),
    ]
    for i, (t, desc, key) in enumerate(labels):
        x = 54 + i * 382
        shadowed_panel(canvas, (x, 382, x + 330, 620), radius=20, fill="white")
        paste_round(canvas, asset_img(key), (x + 22, 410, x + 308, 540), radius=16, mode="contain")
        d.text((x + 24, 560), t, font=F20, fill=INK)
        d.text((x + 24, 590), desc, font=F16, fill=MUTED)
    canvas.convert("RGB").save(path, quality=95)


def panel_02_school(path: Path):
    canvas = Image.new("RGBA", (1500, 580), BG)
    d = ImageDraw.Draw(canvas)
    add_title(d, "学校标准化路径", "政策法规 + 竞品分析 + 专家评审，沉淀教委监管端、单校执行端、家长公示端", 1500, PURPLE)
    blocks = [
        ("政策法规", "监管指标 / 风险预警 / 信息公开 / 经费透明", BLUE),
        ("竞品分析", "明厨亮灶 / 食安大屏 / 缴费订餐 / 硬件参数", PURPLE),
        ("专家评审", "字段、模块、权限、页面入口、开发衔接", AMBER),
    ]
    for i, (t, desc, color) in enumerate(blocks):
        x = 54 + i * 474
        shadowed_panel(canvas, (x, 126, x + 410, 262), radius=18, fill="white")
        d.rounded_rectangle((x + 22, 152, x + 34, 232), radius=6, fill=hex_rgb(color))
        d.text((x + 52, 150), t, font=F24, fill=INK)
        draw_text(d, (x + 52, 190), desc, F16, MUTED, max_width=320)
    d.line((208, 284, 208, 324), fill=hex_rgb(BLUE), width=5)
    d.line((682, 284, 682, 324), fill=hex_rgb(PURPLE), width=5)
    d.line((1156, 284, 1156, 324), fill=hex_rgb(AMBER), width=5)
    d.line((208, 324, 1156, 324), fill=hex_rgb("#9AAFC2"), width=4)
    d.polygon([(1156, 324), (1136, 312), (1136, 336)], fill=hex_rgb("#9AAFC2"))
    evidence = [
        ("school_edu_overview", "教委监管端"),
        ("school_edu_food", "食安监管端"),
        ("purchase_school_mobile", "家校移动协同"),
    ]
    for i, (key, t) in enumerate(evidence):
        x = 78 + i * 470
        screenshot_card(canvas, (x, 350, x + 390, 542), key, t, [TEAL, CORAL, AMBER][i], mode="cover" if i < 2 else "contain")
    canvas.convert("RGB").save(path, quality=95)


def panel_02_enterprise(path: Path):
    canvas = Image.new("RGBA", (1500, 780), BG)
    d = ImageDraw.Draw(canvas)
    add_title(d, "企事业单位标准化证据", "把营养结算主系统、AI 识别设备、食安/进销存能力打包成可复制项目包", 1500, TEAL)
    screenshot_card(canvas, (40, 126, 520, 360), "nutrition_pc_home", "后台主系统", BLUE, mode="cover")
    screenshot_card(canvas, (552, 126, 1008, 360), "nutrition_pc_monitor", "膳食监控", TEAL, mode="cover")
    screenshot_card(canvas, (1040, 126, 1460, 360), "device_weigh_1", "称重结算硬件", AMBER, mode="contain")
    screenshot_card(canvas, (40, 398, 452, 720), "nutrition_device_standing", "AI 识别结算台", PURPLE, mode="contain")
    screenshot_card(canvas, (484, 398, 932, 720), "food_safety_cockpit", "食安驾驶舱", CORAL, mode="cover")
    screenshot_card(canvas, (964, 398, 1460, 720), "food_safety_sample", "留样 / 晨检 / 台账", GREEN, mode="cover")
    canvas.convert("RGB").save(path, quality=95)


def panel_02_school_evidence(path: Path):
    canvas = Image.new("RGBA", (1500, 780), BG)
    d = ImageDraw.Draw(canvas)
    add_title(d, "学校场景三端方案证据", "教委监管、单校执行、家长公示，用原始大屏和移动端素材增强汇报可信度", 1500, BLUE)
    screenshot_card(canvas, (42, 126, 728, 386), "school_edu_overview", "教委端：总览数据大屏", BLUE, mode="cover")
    screenshot_card(canvas, (772, 126, 1458, 386), "school_edu_food", "教委端：食安大屏", TEAL, mode="cover")
    screenshot_card(canvas, (42, 424, 500, 720), "school_edu_procure", "采购监管", AMBER, mode="cover")
    screenshot_card(canvas, (532, 424, 990, 720), "food_safety_single_bright", "单校食安监管", PURPLE, mode="cover")
    screenshot_card(canvas, (1022, 424, 1458, 720), "purchase_school_mobile", "家长 / 移动协同", CORAL, mode="contain")
    canvas.convert("RGB").save(path, quality=95)


def panel_03_ipo(path: Path):
    canvas = Image.new("RGBA", (1500, 500), BG)
    d = ImageDraw.Draw(canvas)
    add_title(d, "AI 赋能：从个人工具到组织资产", "资料有索引、任务有流程、过程有证据、经验可复用", 1500, TEAL)
    items = [
        ("Input", "57,265 文件索引\n6,318 文档索引\n55 来源索引", BLUE),
        ("Process", "67 个 Skill\n50 个 Workflow\nDEFINE → SHIP", TEAL),
        ("Output", "144 个 HTML 页面\n144 条任务证据\n83 份工作总结", AMBER),
        ("Reuse", "复用次数待补\n沉淀为下一次入口\n少人肉交接", CORAL),
    ]
    for i, (t, desc, color) in enumerate(items):
        x = 58 + i * 354
        shadowed_panel(canvas, (x, 138, x + 286, 384), radius=22, fill="white")
        d.text((x + 28, 166), t, font=F30 if False else F28, fill=hex_rgb(color))
        draw_text(d, (x + 30, 218), desc, F18, MUTED, max_width=220, line_gap=10)
        if i < 3:
            d.line((x + 292, 258, x + 344, 258), fill=hex_rgb("#9AAFC2"), width=5)
            d.polygon([(x + 344, 258), (x + 324, 246), (x + 324, 270)], fill=hex_rgb("#9AAFC2"))
    draw_tag(d, 54, 420, "口径边界：当前能证明资产沉淀，效率提升百分比仍需原耗时/AI 后耗时补证", NAVY, F18)
    canvas.convert("RGB").save(path, quality=95)


def panel_03_evidence(path: Path):
    canvas = Image.new("RGBA", (1500, 780), BG)
    d = ImageDraw.Draw(canvas)
    add_title(d, "AI Native 协作证据", "用流程图和证据链展示：AI 执行，人做评审，资产回到项目", 1500, PURPLE)
    screenshot_card(canvas, (42, 126, 728, 386), "ai_native_main", "总方案：AI 协同云效落地", BLUE, mode="cover")
    screenshot_card(canvas, (772, 126, 1458, 386), "ai_native_flow", "闭环流程：需求到交付", TEAL, mode="cover")
    screenshot_card(canvas, (42, 424, 728, 720), "ai_native_evidence", "证据链：过程可复查", AMBER, mode="cover")
    shadowed_panel(canvas, (772, 424, 1458, 720), radius=22, fill="white")
    d.text((806, 462), "下周补证重点", font=F28, fill=INK)
    todos = [
        ("原耗时 vs AI 后耗时", BLUE),
        ("需求澄清、评审准备、恢复交接样本", TEAL),
        ("Skill / Workflow 复用次数", AMBER),
        ("客户验收、手册、视频证据闭环", CORAL),
    ]
    y = 526
    for text, color in todos:
        d.rounded_rectangle((806, y, 834, y + 28), radius=8, fill=hex_rgb(color))
        d.text((852, y - 2), text, font=F20, fill=INK)
        y += 48
    canvas.convert("RGB").save(path, quality=95)


def apply_range(ws, cell_range, fill=None, font_obj=None, alignment=None, border=None):
    for row in ws[cell_range]:
        for cell in row:
            if fill is not None:
                cell.fill = fill
            if font_obj is not None:
                cell.font = font_obj
            if alignment is not None:
                cell.alignment = alignment
            if border is not None:
                cell.border = border


def style_workbook():
    wb = load_workbook(SRC_XLSX)
    ws = wb["产品周报汇报版"]
    ws._images = []
    ws.sheet_view.showGridLines = False
    ws.sheet_view.zoomScale = 86
    ws.freeze_panes = "A9"
    ws.page_setup.orientation = "landscape"
    ws.page_setup.fitToWidth = 1
    ws.page_setup.fitToHeight = 0
    ws.sheet_properties.pageSetUpPr.fitToPage = True
    ws.page_margins.left = 0.25
    ws.page_margins.right = 0.25
    ws.page_margins.top = 0.35
    ws.page_margins.bottom = 0.35

    thin = Side(style="thin", color="DCE5EE")
    white_side = Side(style="thin", color="FFFFFF")
    subtle_border = Border(left=thin, right=thin, top=thin, bottom=thin)
    white_border = Border(left=white_side, right=white_side, top=white_side, bottom=white_side)
    base_align = Alignment(horizontal="left", vertical="center", wrap_text=True)
    center_align = Alignment(horizontal="center", vertical="center", wrap_text=True)
    top_align = Alignment(horizontal="left", vertical="top", wrap_text=True)

    for row in ws.iter_rows():
        for cell in row:
            cell.font = Font(name="PingFang SC", size=11, color="243142")
            cell.alignment = base_align
            cell.border = subtle_border
            cell.fill = PatternFill("solid", fgColor="FFFFFF")

    for col in range(1, 17):
        ws.column_dimensions[get_column_letter(col)].width = 13.5

    # Title and framing.
    apply_range(ws, "A1:P2", PatternFill("solid", fgColor=NAVY.replace("#", "")), Font(name="PingFang SC", size=22, bold=True, color="FFFFFF"), center_align, white_border)
    apply_range(ws, "A3:P3", PatternFill("solid", fgColor="EAF2F8"), Font(name="PingFang SC", size=13, bold=True, color="1E3855"), center_align, white_border)

    card_specs = [
        ("A5:E7", "EAF2FF", BLUE),
        ("F5:K7", "EAF8F1", TEAL),
        ("L5:P7", "FFF3D6", "B36A00"),
    ]
    for rng, fill, color in card_specs:
        apply_range(ws, rng, PatternFill("solid", fgColor=fill), Font(name="PingFang SC", size=13, bold=True, color=color.replace("#", "")), center_align, Border(left=thin, right=thin, top=thin, bottom=thin))

    section_rows = [9, 41, 120]
    for r in section_rows:
        apply_range(ws, f"A{r}:P{r}", PatternFill("solid", fgColor=NAVY.replace("#", "")), Font(name="PingFang SC", size=17, bold=True, color="FFFFFF"), center_align, white_border)

    summary_ranges = ["A10:P10", "A28:P30", "A42:P42", "A61:P63", "A90:P91", "A121:P121", "A138:P141", "A156:P156"]
    for rng in summary_ranges:
        apply_range(ws, rng, PatternFill("solid", fgColor="F4F7FA"), Font(name="PingFang SC", size=12, bold=True, color="1B2733"), top_align, subtle_border)

    header_rows = [32, 66, 75, 96, 106, 143, 151]
    for r in header_rows:
        apply_range(ws, f"A{r}:P{r}", PatternFill("solid", fgColor="1E5A7A"), Font(name="PingFang SC", size=12, bold=True, color="FFFFFF"), center_align, white_border)

    accent_rows = {
        65: ("EAF8F1", TEAL),
        87: ("F8FBFF", BLUE),
        104: ("F2EDFF", PURPLE),
        150: ("FFF3D6", "A85E00"),
    }
    for r, (fill, color) in accent_rows.items():
        apply_range(ws, f"A{r}:P{r}", PatternFill("solid", fgColor=fill), Font(name="PingFang SC", size=13, bold=True, color=color.replace("#", "")), center_align, subtle_border)

    data_ranges = ["A33:P38", "A67:P70", "I76:P78", "A97:P100", "I107:P110", "A144:P147", "A152:P155"]
    for rng in data_ranges:
        for row in ws[rng]:
            row_num = row[0].row
            fill = "FFFFFF" if row_num % 2 else "F8FBFD"
            for cell in row:
                cell.fill = PatternFill("solid", fgColor=fill)
                cell.font = Font(name="PingFang SC", size=10.5, color="243142")
                cell.alignment = top_align
                cell.border = subtle_border

    for r in [33, 34, 35, 36, 37, 38, 67, 68, 69, 70, 97, 98, 99, 100, 144, 145, 146, 147]:
        for c in range(1, 4):
            cell = ws.cell(r, c)
            cell.font = Font(name="PingFang SC", size=11, bold=True, color=NAVY.replace("#", ""))

    # Keep picture zones airy.
    for r in list(range(12, 28)) + list(range(44, 61)) + list(range(72, 87)) + list(range(103, 120)) + list(range(122, 138)):
        for c in range(1, 17):
            ws.cell(r, c).fill = PatternFill("solid", fgColor="F8FBFD")
            ws.cell(r, c).border = Border()

    panels = [
        ("panel-01-flow.png", "A12", 1600, 620),
        ("panel-01-evidence.png", "K12", 1500, 780),
        ("panel-02-standardization-map.png", "A44", 1600, 680),
        ("panel-02-school-standardization.png", "K44", 1500, 580),
        ("panel-02-enterprise-evidence.png", "A73", 1500, 780),
        ("panel-02-school-evidence.png", "A104", 1500, 780),
        ("panel-03-ai-ipo.png", "A123", 1500, 500),
        ("panel-03-ai-native-evidence.png", "K123", 1500, 780),
    ]
    for name, anchor, w, h in panels:
        img = XLImage(str(DERIVED / name))
        img.width = w
        img.height = h
        ws.add_image(img, anchor)

    wb.save(OUT_XLSX)


def main():
    DERIVED.mkdir(parents=True, exist_ok=True)
    (ROOT / "outputs").mkdir(parents=True, exist_ok=True)
    panel_01_flow(DERIVED / "panel-01-flow.png")
    panel_01_evidence(DERIVED / "panel-01-evidence.png")
    panel_02_standardization(DERIVED / "panel-02-standardization-map.png")
    panel_02_school(DERIVED / "panel-02-school-standardization.png")
    panel_02_enterprise(DERIVED / "panel-02-enterprise-evidence.png")
    panel_02_school_evidence(DERIVED / "panel-02-school-evidence.png")
    panel_03_ipo(DERIVED / "panel-03-ai-ipo.png")
    panel_03_evidence(DERIVED / "panel-03-ai-native-evidence.png")
    style_workbook()
    print(OUT_XLSX)


if __name__ == "__main__":
    main()
