4th layout: Radial Timeline (Circular List) — items arranged around a ring with numbered dots and editable labels/notes. Fully native PPT shapes, brand-color cycling. Minimal touches only.

1) UI: add layout option (no center title needed)

src/pages/business-assets/templates/Infographics.tsx

- type LayoutType = "hubSpoke" | "stepsHorizontal" | "mindmap";
+ type LayoutType = "hubSpoke" | "stepsHorizontal" | "mindmap" | "circularList";

- const LAYOUTS = [
+ const LAYOUTS = [
   { id: "hubSpoke", name: "Hub & Spoke (6)", blurb: "Center topic with nodes around" },
   { id: "stepsHorizontal", name: "Steps (6)", blurb: "Process steps in a row" },
   { id: "mindmap", name: "Mindmap (6)", blurb: "Branches from a central idea" },
+  { id: "circularList", name: "Radial Timeline (6)", blurb: "Items around a circle" },
 ];


Hide Center Title for this layout too:

- {selected !== "stepsHorizontal" && (
+ {(selected !== "stepsHorizontal" && selected !== "circularList") && (
    /* Center Title input block */
  )}


Everything else stays the same.

2) Backend (single-file route): /api/infographics/generate

Add a new case that draws a ring + items around it.

server/api/infographics/generate.ts (inside the handler, after other layouts)

if (b.layout === "circularList") {
  const cx = W / 2, cy = H / 2;
  const R  = Math.min(W, H) * 0.30;     // ring radius
  const dot = 0.35;                      // dot diameter
  const labelW = 2.8;

  // outer ring (thin)
  s.addShape(pptx.ShapeType.ellipse, {
    x: cx - R, y: cy - R, w: R * 2, h: R * 2,
    line: { color: "CBD5E1", width: 1.2 }
  });

  for (let i = 0; i < count; i++) {
    const a = (Math.PI * 2 * i) / count - Math.PI / 2; // start top
    const nx = cx + R * Math.cos(a);
    const ny = cy + R * Math.sin(a);

    // dot
    s.addShape(pptx.ShapeType.ellipse, {
      x: nx - dot/2, y: ny - dot/2, w: dot, h: dot,
      fill: { color: hex(palette[i % palette.length]) },
      line: { color: "FFFFFF", width: 1 }
    });

    // number
    s.addText(String(i + 1), {
      x: nx - 0.18, y: ny - 0.18, w: 0.36, h: 0.36,
      align: "center", fontFace: fonts.heading, fontSize: 12, bold: true, color: "FFFFFF"
    });

    // label position slightly outside circle
    const lx = cx + (R + 0.6) * Math.cos(a) - labelW / 2;
    const ly = cy + (R + 0.6) * Math.sin(a) - 0.4;
    const align = Math.cos(a) > 0.3 ? "left" : (Math.cos(a) < -0.3 ? "right" : "center");

    const lbl  = b.labels?.[i] ?? `Item ${i + 1}`;
    const note = b.notes?.[i]   ?? "";

    s.addText(
      [
        { text: `${lbl}\n`, options: { bold: true, fontFace: fonts.heading, fontSize: 14, color: "1F2937" } },
        ...(note ? [{ text: note, options: { fontFace: fonts.body, fontSize: 12, color: "475569" } }] : [])
      ],
      { x: lx, y: ly, w: labelW, h: 0.9, align: align as any }
    );

    // small tick line to the label
    s.addShape(pptx.ShapeType.line, {
      x: nx, y: ny,
      w: (lx + labelW/2) - nx, h: (ly + 0.45) - ny,
      line: { color: "CBD5E1", width: 0.75 }
    });
  }

  addLogo(s);
}


No other changes needed in this route.

3) Template builder: render detailed circular list too

In server/api/ppt/build-template.ts, extend the helper that draws detailed infographics.

Find function addInfographicDetailed(...) and append:

if (item.layout === "circularList") {
  const count = Math.max(2, Math.min(item.count || 6, 12));
  const cx = slideW / 2, cy = slideH / 2;
  const R  = Math.min(slideW, slideH) * 0.30;
  const dot = 0.35, labelW = 2.8;
  // ring
  s.addShape(pptx.ShapeType.ellipse, {
    x: cx - R, y: cy - R, w: R * 2, h: R * 2,
    line: { color: "CBD5E1", width: 1.2 }
  });
  for (let i = 0; i < count; i++) {
    const a = (Math.PI * 2 * i) / count - Math.PI / 2;
    const nx = cx + R * Math.cos(a), ny = cy + R * Math.sin(a);
    s.addShape(pptx.ShapeType.ellipse, {
      x: nx - dot/2, y: ny - dot/2, w: dot, h: dot,
      fill: { color: col(i) }, line: { color: "FFFFFF", width: 1 }
    });
    s.addText(String(i + 1), {
      x: nx - 0.18, y: ny - 0.18, w: 0.36, h: 0.36,
      align: "center", fontFace: fonts.heading, fontSize: 12, bold: true, color: "FFFFFF"
    });
    const lx = cx + (R + 0.6) * Math.cos(a) - labelW / 2;
    const ly = cy + (R + 0.6) * Math.sin(a) - 0.4;
    const align = Math.cos(a) > 0.3 ? "left" : (Math.cos(a) < -0.3 ? "right" : "center");
    const lbl  = (item.labels?.[i] ?? `Item ${i+1}`); const note = (item.notes?.[i] ?? "");
    s.addText(
      [
        { text: `${lbl}\n`, options: { bold: true, fontFace: fonts.heading, fontSize: 14, color: "1F2937" } },
        ...(note ? [{ text: note, options: { fontFace: fonts.body, fontSize: 12, color: "475569" } }] : [])
      ],
      { x: lx, y: ly, w: labelW, h: 0.9, align: align as any }
    );
    s.addShape(pptx.ShapeType.line, {
      x: nx, y: ny, w: (lx + labelW/2) - nx, h: (ly + 0.45) - ny,
      line: { color: "CBD5E1", width: 0.75 }
    });
  }
}


This uses the same col() and variables defined earlier in that helper.

Done

New Radial Timeline (Circular List) option in the Infographics picker ✅

Works in Download PPTX and in the Presentation Template export via pt.infographicsDetailed ✅

All content is editable (shapes/text), color-cycled by your brand accents ✅