#!/usr/bin/env python3
from __future__ import annotations

from copy import deepcopy
from io import BytesIO
from pathlib import Path
import shutil

from pptx import Presentation
from pptx.dml.color import RGBColor
from pptx.enum.shapes import MSO_CONNECTOR, MSO_SHAPE
from pptx.enum.text import MSO_ANCHOR, PP_ALIGN
from pptx.util import Inches, Pt


ROOT = Path(__file__).resolve().parents[3]
WORK = ROOT / "work/2026-08-14-smart-canteen-douyin-value-marketing"
INFOGRAPHIC = ROOT / "work/2026-08-06-smart-canteen-marketing-infographics"
ATLAS = ROOT / "modules/product/products/smart-canteen/product-solutions/2026-08-06-28-project-3d-case-atlas/local-deliverables/智慧食堂28项目案例图册_20260806/01-最终交付/assets/scenes"

TEMPLATE = Path("/Users/jack/Library/Containers/com.tencent.WeWorkMac/Data/Documents/Profiles/A74DF84611CC2ED79C4C206E40B5D80E/Caches/Files/2026-08/045560c1d7932b455e022e2df32c77d7/路演PPT模板-科技风.pptx")
MANUAL = Path("/Users/jack/Library/Containers/com.tencent.WeWorkMac/Data/Documents/Profiles/A74DF84611CC2ED79C4C206E40B5D80E/Caches/Files/2026-07/c2d27b19b462f39c8fcd82b51dccd4d6/智慧营养健康餐厅-产品手册20260125.pptx")

BUILD = WORK / "build/park-five-force-tech-deck"
OUTPUT = BUILD / "康比特园区智慧食堂五力路演方案_科技风版_20260815.pptx"
DESKTOP = Path("/Users/jack/Desktop/智慧食堂抖音价值营销方案_20260814/01-最终交付/康比特园区智慧食堂五力路演方案_科技风版_20260815.pptx")

FONT = "微软雅黑"
WHITE = RGBColor(0xFF, 0xFF, 0xFF)
SOFT = RGBColor(0xC7, 0xD6, 0xE8)
MUTED = RGBColor(0x8E, 0xA6, 0xBD)
NAVY = RGBColor(0x06, 0x17, 0x2F)
CARD = RGBColor(0x08, 0x21, 0x3E)
CARD_2 = RGBColor(0x0B, 0x2A, 0x4A)
CYAN = RGBColor(0x00, 0xD5, 0xF7)
ORANGE = RGBColor(0xF2, 0x68, 0x22)
GREEN = RGBColor(0xA8, 0xD3, 0x39)
BLUE = RGBColor(0x3D, 0x8C, 0xFF)


def I(v: float):
    return Inches(v)


def rgb_hex(color: RGBColor) -> str:
    return str(color)


def set_shape_fill(shape, color: RGBColor):
    shape.fill.solid()
    shape.fill.fore_color.rgb = color


def set_shape_line(shape, color: RGBColor, width=1.0):
    shape.line.color.rgb = color
    shape.line.width = Pt(width)


def add_text(slide, text, x, y, w, h, *, size=20, color=WHITE, bold=False,
             align=PP_ALIGN.LEFT, valign=MSO_ANCHOR.MIDDLE, margin=0.05,
             font=FONT, line_spacing=1.05):
    box = slide.shapes.add_textbox(I(x), I(y), I(w), I(h))
    tf = box.text_frame
    tf.clear()
    tf.word_wrap = True
    tf.margin_left = I(margin)
    tf.margin_right = I(margin)
    tf.margin_top = I(margin)
    tf.margin_bottom = I(margin)
    tf.vertical_anchor = valign
    p = tf.paragraphs[0]
    p.alignment = align
    p.line_spacing = line_spacing
    run = p.add_run()
    run.text = text
    run.font.name = font
    run.font.size = Pt(size)
    run.font.bold = bold
    run.font.color.rgb = color
    return box


def add_multi_text(slide, parts, x, y, w, h, *, size=20, align=PP_ALIGN.LEFT,
                   valign=MSO_ANCHOR.MIDDLE, margin=0.05):
    box = slide.shapes.add_textbox(I(x), I(y), I(w), I(h))
    tf = box.text_frame
    tf.clear()
    tf.word_wrap = True
    tf.margin_left = I(margin)
    tf.margin_right = I(margin)
    tf.margin_top = I(margin)
    tf.margin_bottom = I(margin)
    tf.vertical_anchor = valign
    p = tf.paragraphs[0]
    p.alignment = align
    for text, color, bold, local_size in parts:
        r = p.add_run()
        r.text = text
        r.font.name = FONT
        r.font.size = Pt(local_size or size)
        r.font.bold = bold
        r.font.color.rgb = color
    return box


def add_card(slide, x, y, w, h, *, fill=CARD, line=CYAN, radius=True, width=1.0):
    shape_type = MSO_SHAPE.ROUNDED_RECTANGLE if radius else MSO_SHAPE.RECTANGLE
    shape = slide.shapes.add_shape(shape_type, I(x), I(y), I(w), I(h))
    set_shape_fill(shape, fill)
    set_shape_line(shape, line, width)
    return shape


def add_line(slide, x1, y1, x2, y2, *, color=CYAN, width=1.4):
    line = slide.shapes.add_connector(MSO_CONNECTOR.STRAIGHT, I(x1), I(y1), I(x2), I(y2))
    line.line.color.rgb = color
    line.line.width = Pt(width)
    return line


def add_circle(slide, x, y, d, *, fill=CARD_2, line=CYAN, width=1.2):
    shp = slide.shapes.add_shape(MSO_SHAPE.OVAL, I(x), I(y), I(d), I(d))
    set_shape_fill(shp, fill)
    set_shape_line(shp, line, width)
    return shp


def add_pill(slide, text, x, y, w, *, fill=ORANGE, color=WHITE, size=12):
    shp = slide.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE, I(x), I(y), I(w), I(0.35))
    set_shape_fill(shp, fill)
    shp.line.fill.background()
    add_text(slide, text, x, y, w, 0.35, size=size, color=color, bold=True, align=PP_ALIGN.CENTER)
    return shp


def add_title(slide, n: int, title: str, subtitle: str | None = None):
    add_text(slide, f"#{n:02d}", 0.60, 0.50, 0.92, 0.50, size=21, color=CYAN, bold=True)
    add_text(slide, title, 1.78, 0.48, 9.55, 0.58, size=26, color=WHITE, bold=True)
    add_line(slide, 1.80, 1.12, 7.30, 1.12, color=CYAN, width=1.2)
    if subtitle:
        add_text(slide, subtitle, 7.52, 0.83, 3.65, 0.30, size=10.5, color=MUTED, align=PP_ALIGN.RIGHT)
    add_text(slide, f"KANGBITE  ·  PARK SMART CANTEEN  ·  {n:02d}", 9.05, 7.08, 3.70, 0.20, size=8.5, color=MUTED, align=PP_ALIGN.RIGHT)


def add_background(prs, slide, base_shapes, logo_blob):
    for el in base_shapes:
        slide.shapes._spTree.insert_element_before(deepcopy(el), "p:extLst")
    slide.shapes.add_picture(BytesIO(logo_blob), I(11.58), I(0.23), I(1.38), I(0.71))


def add_full_image(slide, path: Path, *, x=1.90, y=1.35, h=5.65, frame=True):
    if not path.exists():
        raise FileNotFoundError(path)
    if frame:
        add_card(slide, x - 0.06, y - 0.06, 9.16, h + 0.12, fill=WHITE, line=CYAN, radius=False, width=1.0)
    slide.shapes.add_picture(str(path), I(x), I(y), height=I(h))


def add_image_cover(slide, path: Path, x, y, w, h, *, frame=True):
    from PIL import Image
    if frame:
        add_card(slide, x - 0.03, y - 0.03, w + 0.06, h + 0.06, fill=CARD, line=CYAN, radius=False, width=0.8)
    with Image.open(path) as im:
        iw, ih = im.size
    ratio = max(w / iw, h / ih)
    crop_w = w / ratio
    crop_h = h / ratio
    crop_x = (iw - crop_w) / 2
    crop_y = (ih - crop_h) / 2
    pic = slide.shapes.add_picture(str(path), I(x), I(y), I(w), I(h))
    pic.crop_left = crop_x / iw
    pic.crop_right = crop_x / iw
    pic.crop_top = crop_y / ih
    pic.crop_bottom = crop_y / ih
    return pic


def force_card(slide, n, title, result, x, y, w=2.25, h=2.20, accent=CYAN):
    add_card(slide, x, y, w, h, fill=CARD, line=accent, radius=True, width=1.1)
    add_text(slide, f"0{n}", x + 0.18, y + 0.15, 0.55, 0.38, size=15, color=ORANGE, bold=True)
    add_text(slide, title, x + 0.18, y + 0.52, w - 0.36, 0.48, size=20, color=WHITE, bold=True)
    add_text(slide, result, x + 0.18, y + 1.08, w - 0.36, h - 1.22, size=12, color=SOFT, valign=MSO_ANCHOR.TOP)


def build():
    for required in [TEMPLATE, MANUAL]:
        if not required.exists():
            raise FileNotFoundError(required)
    BUILD.mkdir(parents=True, exist_ok=True)
    DESKTOP.parent.mkdir(parents=True, exist_ok=True)

    prs = Presentation(str(TEMPLATE))
    original_slide_count = len(prs.slides)
    base_slide = prs.slides[1]
    base_shapes = [shape._element for shape in list(base_slide.shapes)[:51]]
    logo_blob = list(base_slide.shapes)[-1].image.blob
    blank = prs.slide_layouts[6]

    def new_slide():
        slide = prs.slides.add_slide(blank)
        add_background(prs, slide, base_shapes, logo_blob)
        return slide

    # 01 Cover
    s = new_slide()
    add_pill(s, "KANGBITE · AI NUTRITION SMART CANTEEN", 0.78, 0.72, 3.85, fill=ORANGE, size=11)
    add_multi_text(s, [("康比特园区智慧食堂\n", WHITE, True, 32), ("五力路演方案", CYAN, True, 39)], 1.10, 2.10, 8.95, 1.72, valign=MSO_ANCHOR.MIDDLE)
    add_text(s, "一个园区，一套健康团餐运营底座", 1.12, 4.15, 7.80, 0.54, size=22, color=SOFT, bold=True)
    add_line(s, 1.12, 4.90, 9.80, 4.90, color=ORANGE, width=2.2)
    add_text(s, "品牌力 × 场景力 × 产品力 × 数据力 × 增长力", 1.12, 5.15, 8.20, 0.42, size=15, color=WHITE)
    for i, name in enumerate(["品牌", "场景", "产品", "数据", "增长"]):
        add_circle(s, 8.20 + (i % 3) * 1.28, 5.10 + (i // 3) * 0.78, 0.68, fill=CARD_2, line=CYAN)
        add_text(s, name, 8.20 + (i % 3) * 1.28, 5.10 + (i // 3) * 0.78, 0.68, 0.68, size=12, color=WHITE, bold=True, align=PP_ALIGN.CENTER)
    add_text(s, "园区场景专案 · 2026", 1.12, 6.42, 3.10, 0.35, size=11, color=MUTED)

    # 02 unique battlefield
    s = new_slide(); add_title(s, 2, "市场边界：企业园区 TAM / SAM / SOM", "唯一主场景：千人级企业园区智慧食堂")
    add_text(s, "园区的复杂度，正好让康比特的 5+2 能力全部发挥。", 0.82, 1.40, 11.70, 0.58, size=25, color=WHITE, bold=True, align=PP_ALIGN.CENTER)
    tiers = [
        ("TAM", "全国企业与园区团餐数字化", "所有需要提升就餐、食安、供应链与健康服务的组织"),
        ("SAM", "多组织、多食堂、多规则园区", "高峰明显、补贴复杂、系统割裂、管理需要统一"),
        ("SOM", "可复制的千人级园区项目", "已有企业园区项目与 3D 场景资产支撑标准化传播")]
    for i, (tag, title, body) in enumerate(tiers):
        x = 0.82 + i * 4.10
        add_card(s, x, 2.25, 3.70, 3.25, fill=CARD, line=CYAN if i < 2 else ORANGE, radius=True, width=1.2)
        add_pill(s, tag, x + 0.24, 2.50, 0.82, fill=ORANGE if i == 2 else BLUE, size=12)
        add_text(s, title, x + 0.24, 3.10, 3.22, 0.74, size=20, color=WHITE, bold=True, valign=MSO_ANCHOR.TOP)
        add_text(s, body, x + 0.24, 4.05, 3.22, 1.05, size=12.5, color=SOFT, valign=MSO_ANCHOR.TOP)
    add_text(s, "结论：园区不是一个行业标签，而是康比特最完整的产品证明场。", 1.15, 5.90, 11.05, 0.58, size=21, color=CYAN, bold=True, align=PP_ALIGN.CENTER)

    # 03 customer portrait
    s = new_slide(); add_title(s, 3, "客户画像：五类角色，共同决定一套系统", "谁买、谁管、谁用、谁核算、谁维护")
    roles = [
        ("园区运营方", "统一标准\n统一形象\n统一运营"),
        ("后勤与食堂", "高峰稳定\n规则灵活\n现场好管"),
        ("财务 / 人力 / 工会", "补贴清楚\n对账清晰\n健康关怀"),
        ("信息化团队", "系统可接\n部署可控\n运维可视"),
        ("园区员工", "少排队\n吃得明白\n持续反馈")]
    for i, (role, body) in enumerate(roles):
        force_card(s, i + 1, role, body, 0.55 + i * 2.52, 2.05, w=2.25, h=3.35, accent=ORANGE if i == 0 else CYAN)
    add_text(s, "购买的不是一台设备，而是一套让五类角色都能持续使用的园区运营机制。", 0.85, 5.92, 11.65, 0.60, size=21, color=CYAN, bold=True, align=PP_ALIGN.CENTER)

    # 04 problems
    s = new_slide(); add_title(s, 4, "客户问题：五个断点，必须同时解决", "只解决结算，解决不了园区经营")
    problems = [
        ("高峰不能停", "班次集中、弱网与设备故障，都不能让队伍停下来。"),
        ("规则不能乱", "企业、访客、亲友、招待、补贴与计次必须自动执行。"),
        ("数据不能散", "OA、一卡通、财务、门禁和餐饮数据必须连起来。"),
        ("食安不能靠记", "晨检、留样、检测、巡检、整改需要全过程留痕。"),
        ("健康不能只宣传", "每一餐都要形成可理解、可持续的营养反馈。")]
    positions = [(0.82,1.72),(4.55,1.72),(8.28,1.72),(2.68,4.20),(6.41,4.20)]
    for i, ((title, body), (x, y)) in enumerate(zip(problems, positions)):
        add_card(s, x, y, 3.25, 1.82, fill=CARD, line=ORANGE if i == 0 else CYAN, radius=True, width=1.1)
        add_text(s, f"0{i+1}", x + 0.18, y + 0.15, 0.50, 0.30, size=13, color=ORANGE, bold=True)
        add_text(s, title, x + 0.18, y + 0.47, 2.85, 0.42, size=19, color=WHITE, bold=True)
        add_text(s, body, x + 0.18, y + 0.96, 2.85, 0.67, size=11.5, color=SOFT, valign=MSO_ANCHOR.TOP)

    # 05 park map
    s = new_slide(); add_title(s, 5, "消费场景：一餐连接整个园区", "园区路线照片仅作为路径设计启发")
    add_text(s, "园区食堂的边界，已经从餐厅延伸到入口、企业、供应链、健康服务和运营中心。", 0.90, 1.36, 11.45, 0.52, size=20, color=WHITE, bold=True, align=PP_ALIGN.CENTER)
    nodes = [
        ("园区入口", 0.90, 3.15), ("企业 A", 2.60, 2.05), ("企业 B", 2.60, 4.28),
        ("中央餐厅", 5.05, 3.12), ("健康服务站", 7.62, 1.95),
        ("收货 / 后厨", 7.62, 4.42), ("园区运营中心", 10.02, 3.12)]
    for label, x, y in nodes:
        add_card(s, x, y, 1.72 if len(label) <= 5 else 2.05, 0.90, fill=CARD_2, line=ORANGE if label == "中央餐厅" else CYAN, radius=True, width=1.2)
        add_text(s, label, x, y, 1.72 if len(label) <= 5 else 2.05, 0.90, size=14, color=WHITE, bold=True, align=PP_ALIGN.CENTER)
    path_segments = [(2.58,3.57,5.04,3.57),(4.32,2.50,5.04,3.20),(4.32,4.73,5.04,3.95),(6.78,3.35,7.60,2.63),(6.78,3.80,7.60,4.85),(9.70,2.45,10.00,3.25),(9.70,4.85,10.00,3.95)]
    for x1,y1,x2,y2 in path_segments: add_line(s,x1,y1,x2,y2,color=GREEN,width=2.0)
    add_pill(s, "一个身份", 4.90, 5.62, 1.25, fill=ORANGE)
    add_pill(s, "一套规则", 6.08, 5.62, 1.25, fill=BLUE)
    add_pill(s, "一套数据", 7.26, 5.62, 1.25, fill=GREEN, color=NAVY)
    add_text(s, "设备完成结算，数据完成经营。", 4.20, 6.20, 4.92, 0.46, size=21, color=CYAN, bold=True, align=PP_ALIGN.CENTER)

    # 06 who we are
    s = new_slide(); add_title(s, 6, "我们是谁：AI 营养智慧食堂", "来自产品手册：一平台 · 三系统 · N 硬件")
    add_text(s, "用统一数据平台连接结算、营养、食安与供应链，\n让员工吃得健康，让后勤管得透明，让财务对得清楚。", 0.82, 1.55, 5.25, 1.50, size=25, color=WHITE, bold=True, valign=MSO_ANCHOR.TOP)
    add_pill(s, "统一数据平台", 0.85, 3.35, 1.72, fill=ORANGE)
    for i, (title, body) in enumerate([
        ("营养结算与健康管理", "多场景结算 · 账户补贴 · 营养反馈"),
        ("智慧食品安全监管", "晨检留样 · AI 识别 · 整改留痕"),
        ("智慧进销存与后厨运营", "采购验收 · 库存成本 · 经营分析")]):
        y = 4.00 + i * 0.82
        add_card(s, 0.82, y, 5.25, 0.64, fill=CARD, line=CYAN, radius=True, width=0.8)
        add_text(s, title, 1.02, y + 0.02, 2.55, 0.60, size=14, color=WHITE, bold=True)
        add_text(s, body, 3.48, y + 0.02, 2.38, 0.60, size=10.5, color=SOFT, align=PP_ALIGN.RIGHT)
    add_circle(s, 8.48, 2.35, 2.18, fill=CARD_2, line=ORANGE, width=2.0)
    add_text(s, "统一\n数据平台", 8.48, 2.35, 2.18, 2.18, size=23, color=WHITE, bold=True, align=PP_ALIGN.CENTER)
    for label, x, y in [("人",7.05,1.55),("货",10.66,1.55),("场",10.66,5.03),("健康",7.05,5.03)]:
        add_circle(s, x, y, 1.18, fill=CARD, line=CYAN, width=1.2)
        add_text(s, label, x, y, 1.18, 1.18, size=15, color=CYAN, bold=True, align=PP_ALIGN.CENTER)
    for x1,y1,x2,y2 in [(8.10,2.25,8.65,2.75),(10.22,2.28,10.45,2.75),(10.20,4.58,10.42,5.05),(8.10,4.58,8.65,4.95)]: add_line(s,x1,y1,x2,y2,color=CYAN,width=1.2)

    # 07 five forces
    s = new_slide(); add_title(s, 7, "五力协同：从被看见，到被采购，再到被复购", "品牌、场景、产品、数据、增长")
    add_circle(s, 5.58, 2.33, 2.18, fill=CARD_2, line=ORANGE, width=2.0)
    add_text(s, "园区\n智慧食堂", 5.58, 2.33, 2.18, 2.18, size=24, color=WHITE, bold=True, align=PP_ALIGN.CENTER)
    forces = [
        ("品牌力", "建立信任", 1.02, 1.55), ("场景力", "精准共鸣", 1.02, 4.58),
        ("产品力", "证明能力", 9.85, 1.55), ("数据力", "持续增值", 9.85, 4.58),
        ("增长力", "推动成交", 5.05, 5.35)]
    for title, result, x, y in forces:
        add_card(s, x, y, 2.45, 1.10, fill=CARD, line=ORANGE if title == "品牌力" else CYAN, radius=True, width=1.2)
        add_text(s, title, x + 0.15, y + 0.12, 2.15, 0.42, size=18, color=WHITE, bold=True, align=PP_ALIGN.CENTER)
        add_text(s, result, x + 0.15, y + 0.58, 2.15, 0.30, size=11.5, color=CYAN, align=PP_ALIGN.CENTER)
    for seg in [(3.47,2.10,5.58,3.05),(3.47,5.12,5.58,4.05),(9.85,2.10,7.76,3.05),(9.85,5.12,7.76,4.05),(6.28,5.35,6.65,4.52)]: add_line(s,*seg,color=GREEN,width=1.7)
    add_text(s, "能被看见，才有线索；能被验证，才有订单；能持续增值，才有复购。", 1.00, 6.60, 11.32, 0.45, size=18, color=SOFT, bold=True, align=PP_ALIGN.CENTER)

    # 08 brand force
    s = new_slide(); add_title(s, 8, "品牌力：让专业运动营养，进入园区日常一餐", "康比特的信任，不从设备参数开始")
    add_text(s, "运动营养专业能力，\n是康比特最难被复制的品牌资产。", 0.85, 1.55, 5.40, 1.25, size=29, color=WHITE, bold=True, valign=MSO_ANCHOR.TOP)
    add_text(s, "依托 20 余年运动营养服务经验，\n把专业知识转化为组织健康服务。", 0.88, 3.05, 5.15, 0.95, size=17, color=SOFT, valign=MSO_ANCHOR.TOP)
    add_pill(s, "品牌主张", 0.88, 4.26, 1.18, fill=ORANGE)
    add_text(s, "不是让员工多看一份健康知识，\n而是让每一餐都得到专业反馈。", 0.88, 4.75, 5.10, 1.02, size=20, color=CYAN, bold=True, valign=MSO_ANCHOR.TOP)
    brand_items = [
        ("20余年", "运动营养服务经验"), ("百余支", "国家队服务积累"),
        ("1个Agent", "AI 运动营养师"), ("每一餐", "持续形成健康反馈")]
    for i,(num,label) in enumerate(brand_items):
        x=6.55+(i%2)*2.92; y=1.65+(i//2)*2.25
        add_card(s,x,y,2.58,1.78,fill=CARD,line=ORANGE if i==0 else CYAN,radius=True,width=1.2)
        add_text(s,num,x+0.15,y+0.22,2.28,0.58,size=25,color=WHITE,bold=True,align=PP_ALIGN.CENTER)
        add_text(s,label,x+0.15,y+0.93,2.28,0.45,size=12,color=SOFT,align=PP_ALIGN.CENTER)

    # 09 scene force
    s = new_slide(); add_title(s, 9, "场景力：园区不是一间食堂，而是一个多组织运营系统", "复杂度交给系统，体验留给员工")
    add_text(s, "一套系统，必须同时承接五种园区复杂度。", 1.00, 1.45, 11.32, 0.55, size=24, color=WHITE, bold=True, align=PP_ALIGN.CENTER)
    scene_items = [
        ("多企业", "不同组织、不同人员、不同管理边界"),
        ("多班次", "固定班次、加班、临时就餐同时存在"),
        ("多食堂", "园区中心餐厅、档口、取餐点统一运营"),
        ("多账户", "现金、补贴、计次、储值、聚合支付"),
        ("多系统", "OA、一卡通、财务、门禁与餐饮互联")]
    for i,(title,body) in enumerate(scene_items):
        x=0.58+i*2.50
        add_card(s,x,2.40,2.26,3.20,fill=CARD,line=ORANGE if i==0 else CYAN,radius=True,width=1.2)
        add_text(s,f"0{i+1}",x+0.18,2.60,0.50,0.35,size=13,color=ORANGE,bold=True)
        add_text(s,title,x+0.18,3.08,1.90,0.50,size=22,color=WHITE,bold=True)
        add_text(s,body,x+0.18,3.92,1.90,1.15,size=12,color=SOFT,valign=MSO_ANCHOR.TOP)
    add_text(s, "每多接入一家企业，账户、菜单、策略和运营数据都会让平台价值继续增长。", 0.92, 6.05, 11.52, 0.55, size=20, color=CYAN, bold=True, align=PP_ALIGN.CENTER)

    # 10-18 original graphics
    graphic_slides = [
        (10, "产品力：五大产品能力 + 两大差异能力", INFOGRAPHIC / "outputs/v2/00-overview-5-plus-2.png"),
        (11, "产品力：一套平台，串起健康团餐全链路", INFOGRAPHIC / "outputs/local/06-capability-panorama.png"),
        (12, "产品力 01：饭点再忙，队伍照样向前", INFOGRAPHIC / "outputs/local/01-peak-and-offline.png"),
        (13, "产品力 02：适配信创环境", INFOGRAPHIC / "outputs/local/02-domestic-platforms.png"),
        (14, "产品力 03：不同的人、不同的餐，系统自动结算", INFOGRAPHIC / "outputs/local/03-consumption-policy-account-settlement.png"),
        (15, "产品力 04：原有系统继续用，数据照样连起来", INFOGRAPHIC / "outputs/local/04-system-connections-hd.png"),
        (16, "产品力 05：一套系统连接前厅、后厨与健康设备", INFOGRAPHIC / "outputs/local/05-hardware-ecosystem-hd.png"),
        (17, "数据力 01：国家队营养经验，服务每一位园区员工", INFOGRAPHIC / "outputs/v2/01-ai-nutrition-capability.png"),
        (18, "数据力 02：餐前看身体，餐中看选择，餐后看变化", INFOGRAPHIC / "outputs/v2/02-digital-technology-capability-wysiwyg.png")]
    for n,title,path in graphic_slides:
        s=new_slide(); add_title(s,n,title,"5+2 原始高清图完整嵌入")
        add_full_image(s,path,x=1.88,y=1.31,h=5.70,frame=True)

    # 19 named competition
    s = new_slide(); add_title(s, 19, "竞品对比：雄伟科技、南京小牛、满客宝", "公开资料口径 · 企业园区场景")
    add_text(s, "对手都很强；康比特的机会不是重复功能，而是建立“运动营养 + 园区运营 + AI”的独特认知。", 0.72, 1.28, 11.90, 0.46, size=17, color=WHITE, bold=True, align=PP_ALIGN.CENTER)
    headers = [("对手",0.72,1.90),("公开优势",2.37,3.02),("相对短板",5.54,3.12),("康比特赢点",8.80,3.83)]
    for label,x,w in headers:
        add_card(s,x,1.80,w,0.52,fill=ORANGE if label=="康比特赢点" else CARD_2,line=CYAN,radius=False,width=0.8)
        add_text(s,label,x,1.80,w,0.52,size=13,color=WHITE,bold=True,align=PP_ALIGN.CENTER)
    rows = [
        ("雄伟科技", "整体营建\n全场景覆盖\n软硬件集成与交付", "公开品牌核心更偏\n智慧食堂整体营建", "运动营养品牌\n+ AI 健康运营"),
        ("南京小牛", "RFID / AI 结算\n智能硬件\n供应链与食安", "公开认知更偏\n技术、终端与食堂数字化", "5+2 产品组合\n+ 组织健康价值"),
        ("满客宝", "SaaS / 云平台\n多场景、预订、餐补\n集团管控", "能力范围广，\n容易进入通用功能比较", "园区复杂策略\n+ 系统连接 + AI 营养")]
    for i,row in enumerate(rows):
        y=2.42+i*1.30
        cells=[(row[0],0.72,1.90,WHITE,True),(row[1],2.37,3.02,SOFT,False),(row[2],5.54,3.12,SOFT,False),(row[3],8.80,3.83,CYAN,True)]
        for text_value,x,w,color,bold in cells:
            add_card(s,x,y,w,1.12,fill=CARD,line=ORANGE if x==8.80 else CYAN,radius=False,width=0.7)
            add_text(s,text_value,x+0.10,y+0.06,w-0.20,1.00,size=12.2 if x!=0.72 else 14,color=color,bold=bold,align=PP_ALIGN.CENTER)
    add_card(s,0.72,6.36,11.91,0.58,fill=CARD_2,line=ORANGE,radius=True,width=1.0)
    add_text(s, "竞争结论：不是“谁的功能更多”，而是谁能让园区每天稳定运营，并把每一餐持续转化为健康与经营价值。", 0.93, 6.39, 11.49, 0.50, size=15, color=WHITE, bold=True, align=PP_ALIGN.CENTER)
    add_text(s, "相对短板为基于各厂商公开定位的比较，不代表对方不具备相关能力。来源：sovell.com / xnzn.net / mankebao.com", 0.95, 7.01, 11.45, 0.22, size=8.3, color=MUTED, align=PP_ALIGN.CENTER)

    # 20 park matrix
    s = new_slide(); add_title(s, 20, "园区场景矩阵：一个主场景，多种经营复杂度", "精选 28 项目 3D 场景图")
    scene_cards = [
        (ATLAS / "P002-产业园-evidence-draft-v2.png", "产业园", "多企业 · 多班次 · 多策略"),
        (ATLAS / "P012-海开集团.png", "海开集团", "集团园区 · 员工食堂"),
        (ATLAS / "P017-赛迪物业.png", "赛迪物业", "产业园 · 多入口"),
        (ATLAS / "P019-金斯瑞.png", "金斯瑞", "前厅订餐 · 取餐 · 运营")]
    for i,(path,name,tag) in enumerate(scene_cards):
        x=0.72+(i%2)*6.00; y=1.45+(i//2)*2.83
        add_image_cover(s,path,x,y,5.58,2.28,frame=True)
        add_card(s,x,y+2.02,5.58,0.48,fill=CARD_2,line=CYAN,radius=False,width=0.6)
        add_text(s,name,x+0.12,y+2.05,1.25,0.40,size=13,color=WHITE,bold=True)
        add_text(s,tag,x+1.42,y+2.05,4.02,0.40,size=10.5,color=SOFT,align=PP_ALIGN.RIGHT)
    add_text(s, "同一套底座，可以适配园区组织、入口、班次、账户与后厨能力的不同组合。", 0.92, 6.96, 11.48, 0.28, size=13, color=CYAN, bold=True, align=PP_ALIGN.CENTER)

    # 21 Genscript
    s = new_slide(); add_title(s, 21, "标杆实践：金斯瑞企业园区", "企业前厅完整链路")
    add_image_cover(s, ATLAS / "P019-金斯瑞.png", 0.72, 1.35, 8.42, 4.74, frame=True)
    add_card(s, 9.48, 1.35, 3.15, 4.74, fill=CARD, line=ORANGE, radius=True, width=1.2)
    add_text(s, "企业园区标准包，\n不是单点设备，\n而是完整前厅。", 9.75, 1.72, 2.62, 1.45, size=23, color=WHITE, bold=True, valign=MSO_ANCHOR.TOP)
    for i,t in enumerate(["线上订餐与取餐", "后厨出品协同", "现场设备与运营", "项目数据持续沉淀"]):
        add_pill(s, t, 9.76, 3.42+i*0.54, 2.58, fill=ORANGE if i==0 else BLUE, size=10.5)
    add_text(s, "从员工到店，到后厨出品，再到运营管理，形成可持续使用的园区餐饮数字入口。", 0.85, 6.34, 11.80, 0.55, size=18, color=CYAN, bold=True, align=PP_ALIGN.CENTER)

    # 22 Guoxin
    s = new_slide(); add_title(s, 22, "标杆实践：江苏国信央企园区", "复杂规则与前后厅协同")
    add_image_cover(s, ATLAS / "P010-江苏国信.png", 0.72, 1.35, 8.42, 4.74, frame=True)
    add_card(s, 9.48, 1.35, 3.15, 4.74, fill=CARD, line=ORANGE, radius=True, width=1.2)
    add_text(s, "央企园区的复杂规则，\n用系统自动执行。", 9.75, 1.72, 2.62, 1.12, size=23, color=WHITE, bold=True, valign=MSO_ANCHOR.TOP)
    for i,t in enumerate(["本人 / 亲友 / 招待", "一卡通与财务对账", "国产化部署", "前厅、食安、采购协同"]):
        add_pill(s, t, 9.76, 3.05+i*0.58, 2.58, fill=ORANGE if i==0 else BLUE, size=10.3)
    add_text(s, "从消费策略到收货、食安与财务接口，园区复杂度在一套平台里被统一管理。", 0.85, 6.34, 11.80, 0.55, size=18, color=CYAN, bold=True, align=PP_ALIGN.CENTER)

    # 23 growth
    s = new_slide(); add_title(s, 23, "增长力：抖音先拿线索，再把线索推进到方案会", "线索获取 · 商机转化 · 订单成交 · 客户复购 · 营销传播")
    add_text(s, "抖音的第一目标是获取园区后勤与管理者线索；第二目标是推动数字体检和方案沟通。", 0.78, 1.32, 11.78, 0.52, size=20, color=WHITE, bold=True, align=PP_ALIGN.CENTER)
    steps = [
        ("01", "短视频", "高峰卡顿\n规则混乱\n对账困难"),
        ("02", "私信园区", "领取\n园区食堂\n数字体检表"),
        ("03", "30分钟诊断", "人数 · 班次\n食堂 · 账户\n系统 · 目标"),
        ("04", "场景方案", "5+2 能力\n软硬件组合\n实施路径"),
        ("05", "订单与复购", "试点 · 成交\n月报 · 扩容\n转介绍")]
    for i,(num,title,body) in enumerate(steps):
        x=0.48+i*2.56
        add_card(s,x,2.30,2.18,2.88,fill=CARD,line=ORANGE if i in [0,4] else CYAN,radius=True,width=1.2)
        add_text(s,num,x+0.18,2.50,0.52,0.35,size=13,color=ORANGE,bold=True)
        add_text(s,title,x+0.18,2.98,1.82,0.52,size=19,color=WHITE,bold=True,align=PP_ALIGN.CENTER)
        add_text(s,body,x+0.18,3.70,1.82,1.10,size=12,color=SOFT,align=PP_ALIGN.CENTER,valign=MSO_ANCHOR.MIDDLE)
        if i<4: add_line(s,x+2.20,3.75,x+2.52,3.75,color=GREEN,width=2.4)
    add_text(s, "内容五柱：品牌信任｜园区痛点｜5+2 证据｜项目 3D 场景｜客户运营成果", 0.95, 5.62, 11.42, 0.52, size=17, color=CYAN, bold=True, align=PP_ALIGN.CENTER)
    add_pill(s, "线索获取", 2.30, 6.25, 1.40, fill=BLUE)
    add_pill(s, "商机转化", 3.98, 6.25, 1.40, fill=BLUE)
    add_pill(s, "订单成交", 5.66, 6.25, 1.40, fill=ORANGE)
    add_pill(s, "客户复购", 7.34, 6.25, 1.40, fill=GREEN, color=NAVY)
    add_pill(s, "营销传播", 9.02, 6.25, 1.40, fill=BLUE)

    # 24 CTA
    s = new_slide()
    add_pill(s, "KANGBITE · PARK SMART CANTEEN", 0.80, 0.72, 3.26, fill=ORANGE, size=11)
    add_text(s, "一个园区，\n一套健康团餐运营底座。", 1.05, 1.65, 8.85, 1.72, size=37, color=WHITE, bold=True, valign=MSO_ANCHOR.MIDDLE)
    add_text(s, "用品牌建立信任，用场景形成共鸣，用产品证明能力，\n用数据持续增值，用增长推动成交。", 1.08, 3.75, 8.75, 1.02, size=20, color=SOFT, valign=MSO_ANCHOR.TOP)
    add_card(s, 1.08, 5.10, 7.80, 1.02, fill=CARD_2, line=ORANGE, radius=True, width=1.4)
    add_text(s, "私信「园区」，预约 30 分钟园区食堂数字体检", 1.28, 5.20, 7.40, 0.78, size=22, color=WHITE, bold=True, align=PP_ALIGN.CENTER)
    add_circle(s, 10.05, 2.15, 1.70, fill=CARD_2, line=CYAN, width=1.8)
    add_text(s, "AI\n营养", 10.05, 2.15, 1.70, 1.70, size=23, color=CYAN, bold=True, align=PP_ALIGN.CENTER)
    add_circle(s, 10.65, 4.35, 1.18, fill=CARD, line=ORANGE, width=1.4)
    add_text(s, "5+2", 10.65, 4.35, 1.18, 1.18, size=17, color=WHITE, bold=True, align=PP_ALIGN.CENTER)
    add_text(s, "THANK YOU", 9.90, 6.28, 2.45, 0.46, size=20, color=CYAN, bold=True, align=PP_ALIGN.CENTER)

    # Remove the six original template slides after all new slides are built.
    for sld_id in list(prs.slides._sldIdLst)[:original_slide_count]:
        prs.part.drop_rel(sld_id.rId)
        prs.slides._sldIdLst.remove(sld_id)

    prs.core_properties.title = "康比特园区智慧食堂五力路演方案"
    prs.core_properties.subject = "千人级企业园区智慧食堂；品牌力、场景力、产品力、数据力、增长力"
    prs.core_properties.author = "康比特数字体育科技"
    prs.core_properties.keywords = "园区智慧食堂,5+2,五力,抖音,AI营养"
    prs.save(str(OUTPUT))
    shutil.copy2(OUTPUT, DESKTOP)
    print(OUTPUT)
    print(DESKTOP)
    print(f"slides={len(prs.slides)}")


if __name__ == "__main__":
    build()
