def build(wb, pages):
    from openpyxl.chart import LineChart, Reference
    from openpyxl.styles import Alignment, Border, Font, PatternFill, Side
    from openpyxl.utils import get_column_letter

    FONT = "Malgun Gothic"
    NAVY = "1F4EA0"
    BLUE = "1F6FE5"
    HEAD = "3A63A8"
    PERF_HEAD = "5B7FB4"
    LIGHT = "EEF2F8"
    WHITE = "FFFFFF"
    GREY = "8A93A5"

    thin = Side(style="thin", color="CFD6E4")
    border = Border(left=thin, right=thin, top=thin, bottom=thin)
    center = Alignment(horizontal="center", vertical="center")
    left = Alignment(horizontal="left", vertical="center", wrap_text=True)

    v = pages[0] if pages else {}
    comps = v.get("components", [])

    def find(t, kw=None):
        for c in comps:
            if c.get("type") == t and (kw is None or kw in (c.get("title") or "")):
                return c
        return None

    def by_type(t):
        return [c for c in comps if c.get("type") == t]

    ws = wb.active
    ws.title = (v.get("title") or "리포트")[:31]
    ws.sheet_view.showGridLines = False
    for col, width in {"A": 16, "B": 22, "C": 14, "D": 12, "E": 12, "F": 12, "G": 12}.items():
        ws.column_dimensions[col].width = width

    r = 1

    def head_cell(cell, text, fill):
        cell.value = text
        cell.font = Font(name=FONT, bold=True, color=WHITE, size=10)
        cell.fill = PatternFill("solid", fgColor=fill)
        cell.alignment = center
        cell.border = border

    def data_cell(cell, text, bold=False, fill=None):
        cell.value = text
        cell.font = Font(name=FONT, bold=bold, size=10)
        if fill:
            cell.fill = PatternFill("solid", fgColor=fill)
        cell.alignment = center
        cell.border = border

    # Header block
    ws.cell(r, 1, "Kodex").font = Font(name=FONT, bold=True, size=20, color=BLUE)
    ws.cell(r, 6, "| 판매사 사내한 |").font = Font(name=FONT, size=10, color=NAVY)
    r += 1
    meta = find("metadata_text")
    if meta:
        for f in meta.get("fields", []):
            ws.cell(r, 1, f.get("label", "")).font = Font(name=FONT, size=10, color=GREY)
            ws.cell(r, 2, str(f.get("value", ""))).font = Font(name=FONT, size=10)
            r += 1
    ws.cell(r, 1, v.get("title", "")).font = Font(name=FONT, bold=True, size=13, color=NAVY)
    r += 2

    # Performance section + native line chart
    lc = find("line_chart")
    if lc and lc.get("categories"):
        ws.cell(r, 1, "ETF 성과 추이").font = Font(name=FONT, bold=True, size=12, color=NAVY)
        r += 1
        cats = lc["categories"]
        head_cell(ws.cell(r, 1), "구분", PERF_HEAD)
        for j, cat in enumerate(cats):
            head_cell(ws.cell(r, 2 + j), cat, PERF_HEAD)
        data_start = r
        r += 1
        for s in lc.get("series", []):
            data_cell(ws.cell(r, 1), s.get("name", ""), bold=True, fill=LIGHT)
            for j, val in enumerate(s.get("values", [])):
                data_cell(ws.cell(r, 2 + j), val)
            r += 1
        chart = LineChart()
        chart.title = "ETF 성과 추이"
        chart.height = 6
        chart.width = 12
        data_ref = Reference(ws, min_col=2, max_col=1 + len(cats), min_row=data_start + 1, max_row=r - 1)
        cats_ref = Reference(ws, min_col=2, max_col=1 + len(cats), min_row=data_start, max_row=data_start)
        chart.add_data(data_ref, titles_from_data=False, from_rows=True)
        chart.set_categories(cats_ref)
        for i, s in enumerate(lc.get("series", [])):
            if i < len(chart.series):
                chart.series[i].tx = None
        ws.add_chart(chart, f"A{r + 1}")
        r += 14

    # Holdings table
    ht = find("holdings_table") or find("metric_table") or find("table")
    if ht and ht.get("headers"):
        ws.cell(r, 1, ht.get("title", "ETF Top 10 보유내역")).font = Font(name=FONT, bold=True, size=12, color=NAVY)
        r += 1
        for j, h in enumerate(ht["headers"]):
            head_cell(ws.cell(r, 1 + j), h, HEAD)
        r += 1
        for ri, row in enumerate(ht.get("rows", [])):
            fill = "F6F8FC" if ri % 2 == 1 else None
            for j, cell in enumerate(row):
                data_cell(ws.cell(r, 1 + j), cell, fill=fill)
            r += 1
        ws.cell(r, 1, "총 보유종목 수: 58개 · 자료 : 삼성자산운용, 2026.03.31 기준").font = Font(name=FONT, size=9, color=GREY)
        r += 2

    # Bullet sections
    for bl in by_type("bullet_list"):
        ws.cell(r, 1, bl.get("title", "")).font = Font(name=FONT, bold=True, size=11, color=NAVY)
        r += 1
        for b in bl.get("bullets", []):
            c = ws.cell(r, 1, "– " + b)
            c.font = Font(name=FONT, size=10)
            c.alignment = left
            ws.merge_cells(start_row=r, start_column=1, end_row=r, end_column=7)
            r += 1
        r += 1

    # Footer
    ws.cell(r, 1, "Kodex").font = Font(name=FONT, bold=True, size=12)
    ws.cell(r, 6, "Samsung Asset Management").font = Font(name=FONT, bold=True, size=11, color=NAVY)
