# Examples — Concrete code patterns

These examples show the exact code patterns to follow for each format.

**On Claude Code**: use the helpers from `scripts/` via `require()`.
**On claude.ai / Claude Desktop**: use the **standalone examples** below — they have
all constants inlined and no `require()` to local files. Generate these as artifacts.

---

## PPTX: Standalone script (claude.ai / Claude Desktop)

This is a **complete, self-contained** Node.js script. No local imports.
The user runs `npm install pptxgenjs && node generate-deck.js`.

```javascript
const pptxgen = require("pptxgenjs");
const fs = require("fs");

// ── Pysae brand constants (DO NOT MODIFY) ──────────────────────────
const COLORS = {
  primary: "00b871",
  dark: "0a4b4d",
  body: "1a1a1a",
  caption: "6b7a74",
  white: "FFFFFF",
};
const FONT = "Poppins";

// Topographic background — read from the skill's assets/fond-topo-b64.txt
// For standalone scripts, inline it directly here:
const TOPO_B64 = fs.readFileSync("fond-topo-b64.txt", "utf-8").trim();

// ── Helper functions ───────────────────────────────────────────────

function addDarkSlide(pptx, { title, subtitle } = {}) {
  const slide = pptx.addSlide();
  slide.background = { data: `image/png;base64,${TOPO_B64}` };

  // 2 green blobs — brand signature
  slide.addShape("ellipse", {
    x: 8.8, y: 0.3, w: 2.8, h: 4.5,
    fill: { color: COLORS.primary, transparency: 85 },
    line: { width: 0 },
  });
  slide.addShape("ellipse", {
    x: 9.5, y: 3.0, w: 2.0, h: 3.5,
    fill: { color: COLORS.primary, transparency: 85 },
    line: { width: 0 },
  });

  if (title) {
    slide.addText(title, {
      x: 0.5, y: 2.0, w: 8.0, h: 1.2,
      fontSize: 44, bold: true, color: COLORS.white, fontFace: FONT,
      align: "left",
    });
  }
  if (subtitle) {
    slide.addText(subtitle, {
      x: 0.5, y: 3.2, w: 8.0, h: 0.8,
      fontSize: 18, color: COLORS.white, fontFace: FONT,
      align: "left",
    });
  }
  return slide;
}

function addContentSlide(pptx, { title, titleKeywords = [], bullets = [] } = {}) {
  const slide = pptx.addSlide();
  slide.background = { color: COLORS.white };

  // Bicolor title: main text in dark, keywords in green
  if (title) {
    const parts = buildBicolorText(title, titleKeywords);
    slide.addText(parts, {
      x: 0.5, y: 0.4, w: 9.0, h: 0.7,
      fontSize: 18, bold: true, fontFace: FONT,
    });
  }

  if (bullets.length > 0) {
    const items = bullets.map(b => ({
      text: b,
      options: { fontSize: 11, color: COLORS.body, fontFace: FONT, bullet: { indent: 15 }, paraSpaceAfter: 6 },
    }));
    slide.addText(items, { x: 0.5, y: 1.3, w: 9.0, h: 4.5, valign: "top" });
  }
  return slide;
}

function addKpiCards(pptx, kpis, { darkBackground = false } = {}) {
  const slide = pptx.addSlide();
  if (darkBackground) {
    slide.background = { data: `image/png;base64,${TOPO_B64}` };
    slide.addShape("ellipse", {
      x: 8.8, y: 0.3, w: 2.8, h: 4.5,
      fill: { color: COLORS.primary, transparency: 85 }, line: { width: 0 },
    });
    slide.addShape("ellipse", {
      x: 9.5, y: 3.0, w: 2.0, h: 3.5,
      fill: { color: COLORS.primary, transparency: 85 }, line: { width: 0 },
    });
  } else {
    slide.background = { color: COLORS.white };
  }

  const count = Math.min(kpis.length, 4);
  const cardW = 2.2;
  const gap = (9.0 - count * cardW) / (count + 1);

  kpis.slice(0, 4).forEach((kpi, i) => {
    const x = 0.5 + gap * (i + 1) + cardW * i;
    const y = 1.8;

    // ALWAYS roundedRectangle — never plain rectangle
    slide.addShape("roundedRectangle", {
      x, y, w: cardW, h: 1.8,
      rectRadius: 0.12,
      fill: { color: darkBackground ? COLORS.white : "f7f7f7" },
      shadow: { type: "outer", blur: 6, offset: 2, color: "000000", opacity: 0.1 },
    });
    slide.addText(kpi.stat, {
      x, y: y + 0.2, w: cardW, h: 0.9,
      fontSize: 28, bold: true, color: COLORS.primary, fontFace: FONT, align: "center",
    });
    slide.addText(kpi.label, {
      x, y: y + 1.1, w: cardW, h: 0.5,
      fontSize: 9.5, color: COLORS.dark, fontFace: FONT, align: "center", wrap: true,
    });
  });
  return slide;
}

function addCtaSlide(pptx, { title, ctaText = "Demander une démo" } = {}) {
  const slide = addDarkSlide(pptx, { title });
  slide.addShape("roundedRectangle", {
    x: 0.5, y: 4.0, w: 3.0, h: 0.6,
    rectRadius: 0.12, fill: { color: COLORS.primary },
  });
  slide.addText(ctaText, {
    x: 0.5, y: 4.0, w: 3.0, h: 0.6,
    fontSize: 11.5, bold: true, color: COLORS.dark, fontFace: FONT,
    align: "center", valign: "middle",
  });
  return slide;
}

function buildBicolorText(text, keywords) {
  if (!keywords.length) return [{ text, options: { color: COLORS.dark, fontFace: FONT, bold: true } }];
  const parts = [];
  let remaining = text;
  for (const kw of keywords) {
    const idx = remaining.toLowerCase().indexOf(kw.toLowerCase());
    if (idx === -1) continue;
    if (idx > 0) parts.push({ text: remaining.slice(0, idx), options: { color: COLORS.dark, fontFace: FONT, bold: true } });
    parts.push({ text: remaining.slice(idx, idx + kw.length), options: { color: COLORS.primary, fontFace: FONT, bold: true } });
    remaining = remaining.slice(idx + kw.length);
  }
  if (remaining) parts.push({ text: remaining, options: { color: COLORS.dark, fontFace: FONT, bold: true } });
  return parts;
}

// ── Build the deck ─────────────────────────────────────────────────

const pptx = new pptxgen();
pptx.author = "Pysae";
pptx.title = "Ponctualité en temps réel";
pptx.layout = "LAYOUT_16x9";

addDarkSlide(pptx, {
  title: "Ponctualité en temps réel",
  subtitle: "Anticipez les retards avant qu'ils n'impactent vos voyageurs",
});

addContentSlide(pptx, {
  title: "Le défi quotidien des exploitants",
  titleKeywords: ["défi quotidien"],
  bullets: [
    "6h47 — un conducteur signale une panne sur la ligne 12",
    "Le système reroute 12 bus en 90 secondes",
    "Aucun voyageur n'a attendu plus de 4 minutes",
  ],
});

addKpiCards(pptx, [
  { stat: "98.5%", label: "Ponctualité moyenne" },
  { stat: "-40%", label: "Temps d'attente" },
  { stat: "12s", label: "Temps de réaction" },
], { darkBackground: true });

addCtaSlide(pptx, {
  title: "Prêt à transformer votre exploitation ?",
  ctaText: "Demander une démo",
});

pptx.writeFile({ fileName: "deck-ponctualite.pptx" })
  .then(() => console.log("✓ deck-ponctualite.pptx generated"))
  .catch(err => console.error("✗", err));
```

**Critical pptxgenjs API notes** (common source of errors):
- Shape names are **strings**, not enum constants: `"ellipse"`, `"roundedRectangle"`, `"rectangle"`
- Do NOT use `pptx.ShapeType.OVAL` or `pptx.shapes.OVAL` — these vary by version and break
- Always use: `slide.addShape("ellipse", {...})`, `slide.addShape("roundedRectangle", {...})`
- `fontFace: "Poppins"` must appear on **every** `addText()` call
- Background image format: `{ data: "image/png;base64,<b64string>" }` — the MIME prefix is required
- The `fond-topo-b64.txt` file must be in the same directory as the script (or adjust the path)

---

## PPTX: With helpers (Claude Code only)

```javascript
const { createPres, addDarkSlide, addKpiCards, addContentSlide, addCtaSlide } = require('<path-to-skill>/scripts/pysae-pptx-helpers');

const pptx = createPres("Ponctualité en temps réel");

addDarkSlide(pptx, {
  title: "Ponctualité en temps réel",
  subtitle: "Anticipez les retards avant qu'ils n'impactent vos voyageurs",
});

addContentSlide(pptx, {
  title: "Le défi quotidien des exploitants",
  titleKeywords: ["défi quotidien"],
  bullets: [
    "6h47 — un conducteur signale une panne sur la ligne 12",
    "Le système reroute 12 bus en 90 secondes",
    "Aucun voyageur n'a attendu plus de 4 minutes",
  ],
});

addKpiCards(pptx, [
  { stat: "98.5%", label: "Ponctualité moyenne" },
  { stat: "-40%", label: "Temps d'attente" },
  { stat: "12s", label: "Temps de réaction" },
], { darkBackground: true });

addCtaSlide(pptx, {
  title: "Prêt à transformer votre exploitation ?",
  ctaText: "Demander une démo",
  ctaSubtext: "contact@pysae.com",
});

await pptx.writeFile({ fileName: "deck-ponctualite.pptx" });
```

---

## DOCX: Standalone script (claude.ai / Claude Desktop)

```javascript
const { Document, Paragraph, TextRun, Table, TableRow, TableCell,
  HeadingLevel, BorderStyle, AlignmentType, WidthType,
  ShadingType, Header, Footer, PageNumber, Packer } = require("docx");
const fs = require("fs");

// ── Pysae brand constants ──────────────────────────────────────────
const COLORS = { primary: "00b871", dark: "0a4b4d", body: "1a1a1a", white: "FFFFFF", lightGreen: "e8f5ef" };
const FONT = "Poppins";

// ── Helper functions ───────────────────────────────────────────────

function heading1(text) {
  return new Paragraph({
    heading: HeadingLevel.HEADING_1,
    children: [new TextRun({ text, font: FONT, bold: true, color: COLORS.primary, size: 32 })],
    border: { bottom: { style: BorderStyle.SINGLE, size: 4, color: COLORS.primary } },
    spacing: { before: 240, after: 120 },
  });
}

function heading2(text) {
  return new Paragraph({
    heading: HeadingLevel.HEADING_2,
    children: [new TextRun({ text, font: FONT, bold: true, color: COLORS.dark, size: 26 })],
    spacing: { before: 200, after: 100 },
  });
}

function bodyPara(text) {
  return new Paragraph({
    children: [new TextRun({ text, font: FONT, size: 22, color: COLORS.body })],
    spacing: { after: 120 },
  });
}

function docMgmtTable({ version = "1.0", date, author = "Pysae", validator = "" }) {
  const cw = 2256; // 9026 / 4
  const hStyle = { font: FONT, size: 18, bold: true, color: COLORS.white };
  const bStyle = { font: FONT, size: 18, color: COLORS.body };
  const hShading = { type: ShadingType.CLEAR, fill: COLORS.dark };

  const hCell = (t) => new TableCell({
    children: [new Paragraph({ children: [new TextRun({ text: t, ...hStyle })] })],
    width: { size: cw, type: WidthType.DXA }, shading: hShading,
  });
  const bCell = (t) => new TableCell({
    children: [new Paragraph({ children: [new TextRun({ text: t, ...bStyle })] })],
    width: { size: cw, type: WidthType.DXA },
  });

  return new Table({
    width: { size: 9026, type: WidthType.DXA },
    rows: [
      new TableRow({ children: [hCell("Version"), hCell("Date"), hCell("Auteur"), hCell("Valideur")] }),
      new TableRow({ children: [bCell(version), bCell(date || new Date().toISOString().slice(0, 10)), bCell(author), bCell(validator)] }),
    ],
  });
}

// ── Build the document ─────────────────────────────────────────────

const doc = new Document({
  creator: "Pysae", title: "Rapport de ponctualité Q1 2026",
  styles: { default: { document: { run: { font: FONT, size: 22, color: COLORS.body } } } },
  sections: [{
    properties: { page: { margin: { top: 1200, right: 1200, bottom: 1200, left: 1200 } } },
    headers: { default: new Header({ children: [
      new Paragraph({ children: [new TextRun({ text: "Pysae", font: FONT, size: 18, color: COLORS.dark, italic: true })], alignment: AlignmentType.LEFT }),
    ] }) },
    footers: { default: new Footer({ children: [
      new Paragraph({ children: [
        new TextRun({ text: "Page ", font: FONT, size: 16, color: "6b7a74" }),
        new TextRun({ children: [PageNumber.CURRENT], font: FONT, size: 16, color: "6b7a74" }),
      ], alignment: AlignmentType.CENTER }),
    ] }) },
    children: [
      heading1("Rapport de ponctualité Q1 2026"),
      docMgmtTable({ version: "1.0", date: "2026-03-24", author: "Marketing Pysae", validator: "Direction" }),
      heading2("Synthèse"),
      bodyPara("Ce rapport présente l'évolution de la ponctualité sur le réseau."),
    ],
  }],
});

Packer.toBuffer(doc).then(buffer => {
  fs.writeFileSync("rapport.docx", buffer);
  console.log("✓ rapport.docx generated");
});
```

**Critical docx-js API notes:**
- Always use `ShadingType.CLEAR` with `fill` — never `ShadingType.SOLID` (renders black)
- Table widths in `WidthType.DXA` (value `9026` for A4) — never `WidthType.PERCENTAGE`
- `font: "Poppins"` on every `TextRun` — plus `styles.default.document.run` as fallback
- Background is always white — no page-level coloring

---

## HTML: Direct artifact (claude.ai — no Node.js needed)

On claude.ai, generate the **complete HTML** directly as an artifact. No script needed.
Use this structure:

```html
<!DOCTYPE html>
<html lang="fr">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Ponctualité en temps réel — Pysae</title>
  <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;700&display=swap" rel="stylesheet">
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body { font-family: 'Poppins', sans-serif; color: #1a1a1a; }
  </style>
</head>
<body>

  <!-- Hero — ALWAYS use topo background image, never bare #0a4b4d -->
  <section style="
    background-image: url('data:image/png;base64,...TOPO_B64_HERE...');
    background-size: cover; padding: 100px 80px; color: #fff; min-height: 60vh;
    display: flex; flex-direction: column; justify-content: center;
  ">
    <h1 style="font-size: 42px; font-weight: 700; max-width: 600px;">Ponctualité en temps réel</h1>
    <p style="font-size: 18px; margin-top: 16px; opacity: 0.9;">Anticipez les retards</p>
    <a href="#contact" style="
      display: inline-block; margin-top: 32px; padding: 14px 32px;
      background: #00b871; color: #fff; font-weight: 700; border-radius: 8px;
      text-decoration: none; width: fit-content;
    ">Demander une démo</a>
  </section>

  <!-- Content section — white background -->
  <section style="background: #fff; padding: 60px 80px;">
    <h2 style="font-size: 24px; color: #0a4b4d;">
      Le <span style="color: #00b871;">défi quotidien</span> des exploitants
    </h2>
    <p style="margin-top: 16px; line-height: 1.7;">...</p>
  </section>

  <!-- Content section — alternating light background -->
  <section style="background: #f4f9f6; padding: 60px 80px;">
    <!-- ... -->
  </section>

  <!-- CTA footer — ALWAYS topo background -->
  <section style="
    background-image: url('data:image/png;base64,...TOPO_B64_HERE...');
    background-size: cover; padding: 60px 80px; text-align: center; color: #fff;
  ">
    <h2 style="font-size: 28px; font-weight: 700;">Prêt à commencer ?</h2>
    <a href="#contact" style="
      display: inline-block; margin-top: 24px; padding: 14px 32px;
      background: #00b871; color: #fff; font-weight: 700; border-radius: 8px;
      text-decoration: none;
    ">Demander une démo</a>
  </section>

</body>
</html>
```

**Notes:**
- Replace `...TOPO_B64_HERE...` with the content of `assets/fond-topo-b64.txt`
- Alternate section backgrounds: `#fff` → `#f4f9f6` → `#fff` → ...
- Keywords in titles wrapped in `<span style="color: #00b871;">`
- Badges: `<span style="background: #00b871; color: white; border-radius: 50%; width: 32px; height: 32px; line-height: 32px; text-align: center; font-weight: 700; display: inline-block;">01</span>`
