///|
/// Web / PC 宽屏模板:桌面 Web 与 PC 应用形态的原型(1280×800)。
///
/// 与手机模板的区别不是"画布变大",而是页面语义变了:
/// - Web:顶部导航 + 居中内容列 + 多栏网格 + footer
/// - PC 应用:标题栏 + 侧边栏 + 工具栏 + 多面板 + 状态栏
///
/// 模板仍只用设计令牌与组件系统,保证 Agent 后续修改的一致性。
const WIDE_W : Double = 1280.0

///|
const WIDE_H : Double = 800.0

///|
/// 模板内联样式辅助:直接落 k=v(fill/text_color/stroke/radius/opacity)
fn Project::w_style(
  self : Project,
  ab : String,
  id : String,
  kvs : Array[(String, String)],
) -> Unit {
  let _ = self.edit_node(artboard=ab, node_id=id, kvs~)
}

// ---------------------------------------------------------------------------
// Web 落地页:导航栏 + Hero 双栏 + 三特性卡 + Footer
// ---------------------------------------------------------------------------

///|
fn Project::template_web_landing(self : Project, name : String) -> String {
  let ab = self.create_artboard(name~, width=WIDE_W, height=WIDE_H)

  // 顶部导航栏
  let _ = self.place_comp(ab, "rect", "nav_bar", x=0.0, y=0.0)
  self.resize_node(ab, "nav_bar", WIDE_W, 64.0, 0.0)
  self.w_style(ab, "nav_bar", [("fill", "#FFFFFF"), ("stroke", "#E0E0E0")])
  let _ = self.place_comp(
    ab,
    "badge",
    "nav_logo",
    variant="primary",
    x=32.0,
    y=12.0,
  )
  self.resize_node(ab, "nav_logo", 40.0, 40.0, 999.0)
  let nav_items = ["Product", "Features", "Pricing", "Docs"]
  let nav_x = [320.0, 440.0, 560.0, 660.0]
  for i in 0..<4 {
    let id = "nav_i" + i.to_string()
    let _ = self.place_comp(ab, "body_text", id, x=nav_x[i], y=24.0)
    self.set_text(ab, id, nav_items[i])
    self.resize_node(ab, id, 72.0, 20.0, 0.0)
  }
  let _ = self.place_comp(
    ab,
    "button",
    "nav_cta",
    variant="primary",
    x=1120.0,
    y=14.0,
  )
  self.set_text(ab, "nav_cta", "Get Started")
  self.resize_node(ab, "nav_cta", 128.0, 36.0, 8.0)

  // Hero:左文案右插图
  let _ = self.place_comp(
    ab,
    "heading",
    "hero_title",
    variant="h1",
    x=96.0,
    y=150.0,
  )
  self.set_text(ab, "hero_title", "Prototype at the speed of thought")
  self.resize_node(ab, "hero_title", 560.0, 56.0, 0.0)
  let _ = self.place_comp(ab, "body_text", "hero_sub", x=96.0, y=228.0)
  self.set_text(
    ab, "hero_sub", "Design, critique and iterate — human drags, agent commands.",
  )
  self.resize_node(ab, "hero_sub", 520.0, 24.0, 0.0)
  let _ = self.place_comp(
    ab,
    "button",
    "hero_btn1",
    variant="primary",
    x=96.0,
    y=292.0,
  )
  self.set_text(ab, "hero_btn1", "Start Free")
  self.resize_node(ab, "hero_btn1", 160.0, 48.0, 10.0)
  let _ = self.place_comp(
    ab,
    "button",
    "hero_btn2",
    variant="secondary",
    x=276.0,
    y=292.0,
  )
  self.set_text(ab, "hero_btn2", "Watch Demo")
  self.resize_node(ab, "hero_btn2", 160.0, 48.0, 10.0)
  let _ = self.place_comp(ab, "rect", "hero_art", x=720.0, y=140.0)
  self.resize_node(ab, "hero_art", 464.0, 260.0, 16.0)
  self.w_style(ab, "hero_art", [("fill", "#EDF0FE")])

  // 三列特性卡
  let feats = ["Fast", "Smart", "Open"]
  let descs = ["Instant canvas", "Agent driven", "Plain-text source"]
  let fx = [96.0, 496.0, 896.0]
  for i in 0..<3 {
    let cid = "feat_card" + i.to_string()
    let _ = self.place_comp(
      ab,
      "card",
      cid,
      variant="elevated",
      x=fx[i],
      y=460.0,
    )
    self.resize_node(ab, cid, 288.0, 210.0, 12.0)
    let iid = "feat_icon" + i.to_string()
    let _ = self.place_comp(
      ab,
      "badge",
      iid,
      variant="primary",
      x=fx[i] + 24.0,
      y=484.0,
    )
    self.resize_node(ab, iid, 48.0, 48.0, 999.0)
    let tid = "feat_title" + i.to_string()
    let _ = self.place_comp(
      ab,
      "heading",
      tid,
      variant="h3",
      x=fx[i] + 24.0,
      y=552.0,
    )
    self.set_text(ab, tid, feats[i])
    self.resize_node(ab, tid, 200.0, 28.0, 0.0)
    let bid = "feat_body" + i.to_string()
    let _ = self.place_comp(ab, "body_text", bid, x=fx[i] + 24.0, y=588.0)
    self.set_text(ab, bid, descs[i])
    // 200 而非 240:收编进卡片后由栈布局接管排布,240 会溢出卡片内容盒
    // (issue #14:web_landing feat_bodyN contained_in_parent)
    self.resize_node(ab, bid, 200.0, 20.0, 0.0)
  }

  // Footer
  let _ = self.place_comp(ab, "rect", "footer", x=0.0, y=736.0)
  self.resize_node(ab, "footer", WIDE_W, 64.0, 0.0)
  self.w_style(ab, "footer", [("fill", "#14161A")])
  let _ = self.place_comp(ab, "body_text", "footer_note", x=32.0, y=758.0)
  self.set_text(ab, "footer_note", "© 2026 MoonViz — AI native prototyping")
  self.resize_node(ab, "footer_note", 400.0, 20.0, 0.0)
  self.w_style(ab, "footer_note", [("text_color", "#9AA1AB")])
  let _ = self.place_comp(ab, "body_text", "footer_links", x=980.0, y=758.0)
  self.set_text(ab, "footer_links", "About   Privacy   Contact")
  self.resize_node(ab, "footer_links", 260.0, 20.0, 0.0)
  self.w_style(ab, "footer_links", [("text_color", "#9AA1AB")])

  ab
}

// ---------------------------------------------------------------------------
// Web 登录页:左品牌半屏 + 右居中表单卡
// ---------------------------------------------------------------------------

///|
fn Project::template_web_login(self : Project, name : String) -> String {
  let ab = self.create_artboard(name~, width=WIDE_W, height=WIDE_H)

  // 左侧品牌区
  let _ = self.place_comp(ab, "rect", "brand_pane", x=0.0, y=0.0)
  self.resize_node(ab, "brand_pane", 560.0, WIDE_H, 0.0)
  self.w_style(ab, "brand_pane", [("fill", "#4B6BFB")])
  let _ = self.place_comp(ab, "badge", "brand_logo", x=96.0, y=120.0)
  self.resize_node(ab, "brand_logo", 64.0, 64.0, 999.0)
  self.w_style(ab, "brand_logo", [("fill", "#FFFFFF")])
  let _ = self.place_comp(
    ab,
    "heading",
    "brand_title",
    variant="h1",
    x=96.0,
    y=320.0,
  )
  self.set_text(ab, "brand_title", "Design together\nwith your agent")
  self.resize_node(ab, "brand_title", 400.0, 112.0, 0.0)
  self.w_style(ab, "brand_title", [("text_color", "#FFFFFF")])
  let _ = self.place_comp(ab, "body_text", "brand_sub", x=96.0, y=452.0)
  self.set_text(
    ab, "brand_sub", "One plain-text source of truth for humans and AI.",
  )
  self.resize_node(ab, "brand_sub", 380.0, 24.0, 0.0)
  self.w_style(ab, "brand_sub", [("text_color", "#DCE3FF")])

  // 右侧表单卡
  let _ = self.place_comp(ab, "rect", "form_card", x=680.0, y=180.0)
  self.resize_node(ab, "form_card", 440.0, 440.0, 16.0)
  self.w_style(ab, "form_card", [("fill", "#FFFFFF"), ("stroke", "#E0E0E0")])
  let _ = self.place_comp(
    ab,
    "heading",
    "wl_title",
    variant="h3",
    x=716.0,
    y=220.0,
  )
  self.set_text(ab, "wl_title", "Welcome back")
  self.resize_node(ab, "wl_title", 300.0, 30.0, 0.0)
  let _ = self.place_comp(ab, "body_text", "wl_sub", x=716.0, y=254.0)
  self.set_text(ab, "wl_sub", "Sign in to continue")
  self.resize_node(ab, "wl_sub", 300.0, 20.0, 0.0)
  let _ = self.place_comp(
    ab,
    "text_input",
    "wl_email",
    variant="filled",
    x=716.0,
    y=304.0,
  )
  self.set_text(ab, "wl_email", "Email address")
  self.resize_node(ab, "wl_email", 368.0, 48.0, 10.0)
  let _ = self.place_comp(
    ab,
    "text_input",
    "wl_pass",
    variant="filled",
    x=716.0,
    y=364.0,
  )
  self.set_text(ab, "wl_pass", "Password")
  self.resize_node(ab, "wl_pass", 368.0, 48.0, 10.0)
  let _ = self.place_comp(
    ab,
    "body_text",
    "wl_forgot",
    variant="caption",
    x=940.0,
    y=424.0,
  )
  self.set_text(ab, "wl_forgot", "Forgot password?")
  self.resize_node(ab, "wl_forgot", 144.0, 18.0, 0.0)
  let _ = self.place_comp(
    ab,
    "button",
    "wl_btn",
    variant="primary",
    x=716.0,
    y=460.0,
  )
  self.set_text(ab, "wl_btn", "Sign In")
  self.resize_node(ab, "wl_btn", 368.0, 48.0, 10.0)
  let _ = self.place_comp(
    ab,
    "body_text",
    "wl_signup",
    variant="caption",
    x=796.0,
    y=540.0,
  )
  self.set_text(ab, "wl_signup", "No account? Create one")
  self.resize_node(ab, "wl_signup", 220.0, 18.0, 0.0)

  ab
}

// ---------------------------------------------------------------------------
// Web 仪表盘:侧边栏 + 顶栏 + 统计卡 + 图表/侧面板双栏
// ---------------------------------------------------------------------------

///|
fn Project::template_web_dashboard(self : Project, name : String) -> String {
  let ab = self.create_artboard(name~, width=WIDE_W, height=WIDE_H)

  // 侧边栏
  let _ = self.place_comp(ab, "rect", "sb", x=0.0, y=0.0)
  self.resize_node(ab, "sb", 224.0, WIDE_H, 0.0)
  self.w_style(ab, "sb", [("fill", "#1E2126")])
  let _ = self.place_comp(
    ab,
    "badge",
    "sb_logo",
    variant="primary",
    x=24.0,
    y=24.0,
  )
  self.resize_node(ab, "sb_logo", 36.0, 36.0, 999.0)
  let _ = self.place_comp(
    ab,
    "heading",
    "sb_name",
    variant="h4",
    x=72.0,
    y=32.0,
  )
  self.set_text(ab, "sb_name", "MoonViz")
  self.resize_node(ab, "sb_name", 120.0, 22.0, 0.0)
  self.w_style(ab, "sb_name", [("text_color", "#FFFFFF")])
  let items = ["Overview", "Analytics", "Projects", "Team", "Billing"]
  for i in 0..<5 {
    let id = "sb_i" + i.to_string()
    let _ = self.place_comp(
      ab,
      "body_text",
      id,
      x=28.0,
      y=96.0 + i.to_double() * 44.0,
    )
    self.set_text(ab, id, items[i])
    self.resize_node(ab, id, 160.0, 20.0, 0.0)
    let active : Array[(String, String)] = if i == 0 {
      [("text_color", "#FFFFFF")]
    } else {
      [("text_color", "#7C838C")]
    }
    self.w_style(ab, id, active)
    // 选中项背景条
    if i == 0 {
      let _ = self.place_comp(ab, "rect", id + "_bg", x=16.0, y=88.0)
      self.resize_node(ab, id + "_bg", 192.0, 36.0, 8.0)
      self.w_style(ab, id + "_bg", [("fill", "#4B6BFB")])
      let _ = self.place_comp(ab, "body_text", id + "_fg", x=28.0, y=96.0)
      self.set_text(ab, id + "_fg", items[i])
      self.resize_node(ab, id + "_fg", 160.0, 20.0, 0.0)
      self.w_style(ab, id + "_fg", [("text_color", "#FFFFFF")])
    }
  }

  // 顶栏
  let _ = self.place_comp(ab, "rect", "topbar", x=224.0, y=0.0)
  self.resize_node(ab, "topbar", WIDE_W - 224.0, 64.0, 0.0)
  self.w_style(ab, "topbar", [("fill", "#FFFFFF"), ("stroke", "#E0E0E0")])
  let _ = self.place_comp(
    ab,
    "heading",
    "tb_title",
    variant="h4",
    x=252.0,
    y=22.0,
  )
  self.set_text(ab, "tb_title", "Overview")
  self.resize_node(ab, "tb_title", 200.0, 22.0, 0.0)
  let _ = self.place_comp(ab, "badge", "tb_avatar", x=1188.0, y=14.0)
  self.resize_node(ab, "tb_avatar", 36.0, 36.0, 999.0)

  // 统计卡 ×4
  let stats = ["Visitors", "Signups", "Revenue", "Churn"]
  let vals = ["24.5k", "1,204", "$38.2k", "2.1%"]
  let deltas = [
    "▲ 12% vs last week", "▲ 8% vs last week", "▲ 21% vs last week", "▼ 0.4 pts",
  ]
  let sx = [252.0, 508.0, 764.0, 1020.0]
  for i in 0..<4 {
    let cid = "wd_stat" + i.to_string()
    let _ = self.place_comp(
      ab,
      "card",
      cid,
      variant="elevated",
      x=sx[i],
      y=88.0,
    )
    // 116 而非 100:栈内容 16+18+8+30+8+16=96 + 底 pad 16 = 112 才装得下
    // 三行(issue #14:wd_sdN contained_in_parent)
    self.resize_node(ab, cid, 236.0, 116.0, 12.0)
    let lid = "wd_sl" + i.to_string()
    let _ = self.place_comp(
      ab,
      "body_text",
      lid,
      variant="caption",
      x=sx[i] + 20.0,
      y=102.0,
    )
    self.set_text(ab, lid, stats[i])
    self.resize_node(ab, lid, 160.0, 18.0, 0.0)
    let vid = "wd_sv" + i.to_string()
    let _ = self.place_comp(
      ab,
      "heading",
      vid,
      variant="h3",
      x=sx[i] + 20.0,
      y=124.0,
    )
    self.set_text(ab, vid, vals[i])
    self.resize_node(ab, vid, 180.0, 30.0, 0.0)
    let did = "wd_sd" + i.to_string()
    let _ = self.place_comp(
      ab,
      "body_text",
      did,
      variant="caption",
      x=sx[i] + 20.0,
      y=160.0,
    )
    self.set_text(ab, did, deltas[i])
    // 164 而非 196:卡内容宽 204(236-2×16),交叉轴居中下 196 会溢出
    self.resize_node(ab, did, 164.0, 16.0, 0.0)
    self.w_style(ab, did, [
      ("text_color", if i == 3 { "#E57373" } else { "#81C784" }),
    ])
  }

  // 图表区 + 侧面板
  let _ = self.place_comp(ab, "rect", "wd_chart", x=252.0, y=212.0)
  self.resize_node(ab, "wd_chart", 580.0, 360.0, 12.0)
  self.w_style(ab, "wd_chart", [("fill", "#FFFFFF"), ("stroke", "#E0E0E0")])
  let _ = self.place_comp(
    ab,
    "heading",
    "wd_ct",
    variant="h4",
    x=276.0,
    y=236.0,
  )
  self.set_text(ab, "wd_ct", "Weekly traffic")
  self.resize_node(ab, "wd_ct", 200.0, 22.0, 0.0)
  // 简易柱状图:7 根柱
  let bars = [120.0, 180.0, 150.0, 220.0, 260.0, 200.0, 280.0]
  for i in 0..<7 {
    let id = "wd_bar" + i.to_string()
    let _ = self.place_comp(
      ab,
      "rect",
      id,
      x=300.0 + i.to_double() * 72.0,
      y=536.0 - bars[i],
    )
    self.resize_node(ab, id, 40.0, bars[i], 4.0)
    self.w_style(ab, id, [("fill", "#4B6BFB")])
  }
  let _ = self.place_comp(ab, "rect", "wd_side", x=848.0, y=212.0)
  self.resize_node(ab, "wd_side", 408.0, 360.0, 12.0)
  self.w_style(ab, "wd_side", [("fill", "#FFFFFF"), ("stroke", "#E0E0E0")])
  let _ = self.place_comp(
    ab,
    "heading",
    "wd_st",
    variant="h4",
    x=872.0,
    y=236.0,
  )
  self.set_text(ab, "wd_st", "Recent activity")
  self.resize_node(ab, "wd_st", 200.0, 22.0, 0.0)
  let acts = [
    "Ada merged layout patch", "Bob moved hero_art", "Agent applied theme dark",
  ]
  for i in 0..<3 {
    let id = "wd_act" + i.to_string()
    let _ = self.place_comp(
      ab,
      "rect",
      id,
      x=872.0,
      y=276.0 + i.to_double() * 64.0,
    )
    self.resize_node(ab, id, 360.0, 52.0, 8.0)
    self.w_style(ab, id, [("fill", "#F4F5F7")])
    let tid = "wd_at" + i.to_string()
    let _ = self.place_comp(
      ab,
      "body_text",
      tid,
      x=888.0,
      y=292.0 + i.to_double() * 64.0,
    )
    self.set_text(ab, tid, acts[i])
    self.resize_node(ab, tid, 330.0, 20.0, 0.0)
  }

  // 底部表格区
  let _ = self.place_comp(ab, "rect", "wd_table", x=252.0, y=596.0)
  self.resize_node(ab, "wd_table", 1004.0, 180.0, 12.0)
  self.w_style(ab, "wd_table", [("fill", "#FFFFFF"), ("stroke", "#E0E0E0")])
  let heads = ["User", "Action", "Time"]
  let hx = [276.0, 700.0, 1050.0]
  for i in 0..<3 {
    let id = "wd_th" + i.to_string()
    let _ = self.place_comp(
      ab,
      "body_text",
      id,
      variant="caption",
      x=hx[i],
      y=616.0,
    )
    self.set_text(ab, id, heads[i])
    self.resize_node(ab, id, 120.0, 18.0, 0.0)
    self.w_style(ab, id, [("text_color", "#7C838C")])
  }
  let rows = [
    ["ada", "move hero_art", "2m"],
    ["bob", "update fill", "18m"],
    ["eve", "place button", "1h"],
  ]
  for i in 0..<3 {
    for j in 0..<3 {
      let id = "wd_td" + i.to_string() + "_" + j.to_string()
      let _ = self.place_comp(
        ab,
        "body_text",
        id,
        x=hx[j],
        y=648.0 + i.to_double() * 32.0,
      )
      self.set_text(ab, id, rows[i][j])
      self.resize_node(ab, id, 200.0, 20.0, 0.0)
    }
  }

  ab
}

// ---------------------------------------------------------------------------
// PC 桌面应用:标题栏 + 侧边栏 + 工具栏 + 三面板 + 状态栏
// ---------------------------------------------------------------------------

///|
fn Project::template_pc_app(self : Project, name : String) -> String {
  let ab = self.create_artboard(name~, width=WIDE_W, height=WIDE_H)

  // 标题栏(红黄绿交通灯)
  let _ = self.place_comp(ab, "rect", "title_bar", x=0.0, y=0.0)
  self.resize_node(ab, "title_bar", WIDE_W, 36.0, 0.0)
  self.w_style(ab, "title_bar", [("fill", "#111318")])
  let dots = ["#FF5F57", "#FEBC2E", "#28C840"]
  for i in 0..<3 {
    let id = "tl_dot" + i.to_string()
    let _ = self.place_comp(
      ab,
      "badge",
      id,
      x=20.0 + i.to_double() * 24.0,
      y=12.0,
    )
    self.resize_node(ab, id, 12.0, 12.0, 999.0)
    self.w_style(ab, id, [("fill", dots[i])])
  }
  let _ = self.place_comp(ab, "body_text", "tl_title", x=600.0, y=9.0)
  self.set_text(ab, "tl_title", "MoonViz Studio — login.mbt.md")
  self.resize_node(ab, "tl_title", 300.0, 18.0, 0.0)
  self.w_style(ab, "tl_title", [("text_color", "#9AA1AB")])

  // 侧边栏
  let _ = self.place_comp(ab, "rect", "pc_sb", x=0.0, y=36.0)
  self.resize_node(ab, "pc_sb", 240.0, 728.0, 0.0)
  self.w_style(ab, "pc_sb", [("fill", "#1E2126")])
  let files = ["login.mbt.md", "dashboard.mbt.md", "tokens.json", "history.log"]
  for i in 0..<4 {
    let id = "pc_f" + i.to_string()
    let _ = self.place_comp(
      ab,
      "body_text",
      id,
      x=36.0,
      y=72.0 + i.to_double() * 36.0,
    )
    self.set_text(ab, id, files[i])
    self.resize_node(ab, id, 180.0, 20.0, 0.0)
    self.w_style(
      ab,
      id,
      if i == 0 {
        [("text_color", "#FFFFFF")]
      } else {
        [("text_color", "#7C838C")]
      },
    )
  }
  let _ = self.place_comp(
    ab,
    "heading",
    "pc_sbh",
    variant="h4",
    x=24.0,
    y=232.0,
  )
  self.set_text(ab, "pc_sbh", "Pages")
  self.resize_node(ab, "pc_sbh", 120.0, 20.0, 0.0)
  self.w_style(ab, "pc_sbh", [("text_color", "#9AA1AB")])
  let pages = ["login", "signup", "dashboard"]
  for i in 0..<3 {
    let id = "pc_p" + i.to_string()
    let _ = self.place_comp(
      ab,
      "body_text",
      id,
      x=36.0,
      y=264.0 + i.to_double() * 32.0,
    )
    self.set_text(ab, id, pages[i])
    self.resize_node(ab, id, 160.0, 20.0, 0.0)
    self.w_style(ab, id, [("text_color", "#C8CCD2")])
  }

  // 工具栏
  let _ = self.place_comp(ab, "rect", "pc_tb", x=240.0, y=36.0)
  self.resize_node(ab, "pc_tb", WIDE_W - 240.0, 52.0, 0.0)
  self.w_style(ab, "pc_tb", [("fill", "#FFFFFF"), ("stroke", "#E0E0E0")])
  let tbtns = ["Select", "Move", "Text"]
  for i in 0..<3 {
    let id = "pc_tbn" + i.to_string()
    let _ = self.place_comp(
      ab,
      "button",
      id,
      variant="secondary",
      x=260.0 + i.to_double() * 96.0,
      y=46.0,
    )
    self.set_text(ab, id, tbtns[i])
    self.resize_node(ab, id, 84.0, 32.0, 6.0)
  }
  let _ = self.place_comp(
    ab,
    "button",
    "pc_run",
    variant="primary",
    x=1156.0,
    y=46.0,
  )
  self.set_text(ab, "pc_run", "Critique")
  self.resize_node(ab, "pc_run", 92.0, 32.0, 6.0)

  // 三面板:图层列表 / 画布 / 属性
  let _ = self.place_comp(ab, "rect", "pc_layers", x=240.0, y=88.0)
  self.resize_node(ab, "pc_layers", 280.0, 684.0, 0.0)
  self.w_style(ab, "pc_layers", [("fill", "#F4F5F7")])
  let _ = self.place_comp(
    ab,
    "heading",
    "pc_lh",
    variant="h4",
    x=260.0,
    y=108.0,
  )
  self.set_text(ab, "pc_lh", "Layers")
  self.resize_node(ab, "pc_lh", 120.0, 20.0, 0.0)
  let layers = ["root", "hero_title", "hero_btn1", "feat_card0"]
  for i in 0..<4 {
    let id = "pc_l" + i.to_string()
    let _ = self.place_comp(
      ab,
      "body_text",
      id,
      x=276.0,
      y=144.0 + i.to_double() * 30.0,
    )
    self.set_text(ab, id, layers[i])
    self.resize_node(ab, id, 200.0, 20.0, 0.0)
  }
  let _ = self.place_comp(ab, "rect", "pc_canvas", x=520.0, y=88.0)
  self.resize_node(ab, "pc_canvas", 560.0, 684.0, 0.0)
  self.w_style(ab, "pc_canvas", [("fill", "#FFFFFF")])
  // 画布内放一个居中手机画板缩略示意
  let _ = self.place_comp(ab, "rect", "pc_phone", x=640.0, y=150.0)
  self.resize_node(ab, "pc_phone", 180.0, 380.0, 16.0)
  self.w_style(ab, "pc_phone", [("fill", "#EDF0FE"), ("stroke", "#4B6BFB")])
  let _ = self.place_comp(ab, "rect", "pc_phone_hdr", x=656.0, y=174.0)
  self.resize_node(ab, "pc_phone_hdr", 148.0, 28.0, 6.0)
  self.w_style(ab, "pc_phone_hdr", [("fill", "#4B6BFB")])
  let _ = self.place_comp(ab, "rect", "pc_phone_btn", x=672.0, y=452.0)
  self.resize_node(ab, "pc_phone_btn", 116.0, 36.0, 18.0)
  self.w_style(ab, "pc_phone_btn", [("fill", "#4B6BFB")])
  let _ = self.place_comp(ab, "rect", "pc_props", x=1080.0, y=88.0)
  self.resize_node(ab, "pc_props", 200.0, 684.0, 0.0)
  self.w_style(ab, "pc_props", [("fill", "#F4F5F7")])
  let _ = self.place_comp(
    ab,
    "heading",
    "pc_ph",
    variant="h4",
    x=1096.0,
    y=108.0,
  )
  self.set_text(ab, "pc_ph", "Properties")
  self.resize_node(ab, "pc_ph", 150.0, 20.0, 0.0)
  let props = ["x: 96", "y: 150", "w: 560", "fill: none"]
  for i in 0..<4 {
    let id = "pc_pr" + i.to_string()
    let _ = self.place_comp(
      ab,
      "body_text",
      id,
      x=1096.0,
      y=144.0 + i.to_double() * 28.0,
    )
    self.set_text(ab, id, props[i])
    self.resize_node(ab, id, 160.0, 18.0, 0.0)
  }

  // 状态栏
  let _ = self.place_comp(ab, "rect", "pc_status", x=240.0, y=772.0)
  self.resize_node(ab, "pc_status", WIDE_W - 240.0, 28.0, 0.0)
  self.w_style(ab, "pc_status", [("fill", "#EDF0FE")])
  let _ = self.place_comp(
    ab,
    "body_text",
    "pc_st",
    variant="caption",
    x=256.0,
    y=778.0,
  )
  self.set_text(ab, "pc_st", "ops: 3   revision: 12   gate: AGENT ✓")
  self.resize_node(ab, "pc_st", 400.0, 18.0, 0.0)

  ab
}