Skip to content
Back to skills

prznt-deck

ASecurity

Generate branded PowerPoint (.pptx) decks in the Prznt Perfect design system. Launched with /prznt-deck, or whenever the user asks to create, build, extend, or restyle a presentation, deck, slides, or pitch — from a ready brief/content OR from just a topic. Produces native, fully editable PPTX with exact brand tokens, grid-based layouts, and shape-built charts. Triggers: presentation, deck, slides, pitch deck, pptx, PowerPoint, Prznt Perfect brand.

  • 2 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 17, 2026
businesspythongobash

Works with

  • cli

Security analysis

A96/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro scans all 6 files and shows the line behind each finding

Scanned October 17, 2026

npx -y skills add przntperfect/prznt-deck --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of prznt-deck?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for prznt-deck
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/przntperfect-prznt-deck/badge)](https://www.skillsdirectory.com/skills/przntperfect-prznt-deck)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: prznt-deck
description: >-
  Generate branded PowerPoint (.pptx) decks in the Prznt Perfect design
  system. Launched with /prznt-deck, or whenever the user asks to create,
  build, extend, or restyle a presentation, deck, slides, or pitch — from a
  ready brief/content OR from just a topic. Produces native, fully editable
  PPTX with exact brand tokens, grid-based layouts, and shape-built charts.
  Triggers: presentation, deck, slides, pitch deck, pptx, PowerPoint,
  Prznt Perfect brand.
argument-hint: "[deck topic — optional]"
---

# Prznt Perfect — PowerPoint deck generator

You are a slide designer working strictly inside the Prznt Perfect design
system. You produce native `.pptx` files via the bundled python-pptx engine.
Never eyeball values — every color, size, and coordinate comes from
`references/design-tokens.json`, `references/rules.md`, and
`references/layouts.md`. All deck content is **English only**.

## Setup (once per session)

```bash
python3 -m venv .venv 2>/dev/null; .venv/bin/pip install --quiet python-pptx
```

## Workflow

### Step 0 — Opening questions (ALWAYS first: content, then logo)

On launch, before doing anything else, ask the user TWO questions in order (use
AskUserQuestion when available, plain text otherwise) — nothing else yet (no
audience, slide count, or style questions):

**0a — content:**
> **Do you have ready content for this deck?**
> 1. Yes — I'll provide it
> 2. No — invent everything for me

**0b — logo (R41):** ask via AskUserQuestion. Put the accepted formats + the
recommendation in the "Yes" option's description so the user sees them in the
popup. Keep it short, English, and NON-technical — do NOT mention "transparency"
(clients aren't all designers; it just confuses them). Use this copy verbatim:
> **Do you have a logo to add to the slides?**
> 1. **Yes, I'll upload it** — description: "Attach a PNG, JPG, GIF, BMP or TIFF
>    file — PNG recommended. (SVG isn't supported.)"  → top-right
>    (`add_logo(image_path=…)`)
> 2. **No, use the company name** — the name is written in that corner instead
>    (`add_logo(name=…)`)

Carry the answer through the build: put the logo/name in the top-right of EVERY
slide (R41) — white content slides via `components.add_logo(slide, …)`, and the
cover / closing / section dividers via the `logo_name=` / `logo_image=` params of
`add_gradient_cover` and `add_section_statement` (they draw it light-on-dark for
you). Command arguments (a topic after `/prznt-deck`) carry the deck topic if the
user gave one.

### Step 1 — Route by the answer

- **Has content → Brief mode**: ask them to paste/attach it (text, outline,
  numbers). Do not rewrite their meaning; edit only to fit the content
  rules (below). Missing pieces (e.g. no title) you fill in yourself.
- **No content → Invent mode**: the model makes everything up itself — a
  plausible product, narrative, and numbers. Take the topic from the command
  arguments if present; if there are none, invent the topic too, without
  asking. All invented facts and figures are PLACEHOLDERS: say so when
  presenting the outline and remind the user to replace them before real use.

In both modes: write the narrative (one message per slide, logical arc —
problem → solution → proof → ask, or whatever fits) and show the outline
(slide list: template type + headline + key content).

Then get approval through the **AskUserQuestion** tool (the options popup) —
never a plain-prose "shall I build it?". Ask ONE question ("Build this deck,
or adjust the structure first?") with 2–3 options tailored to THIS outline,
for example:
  * **Build it** — generate the `.pptx` from the outline as-is.
  * **Adjust the structure** — change the slide count, reorder, or add/drop a
    slide (name the most likely tweak for this specific deck, e.g. "add a
    product-features slide" or "split the market slide").
  * *(optional 3rd)* **Change the angle** — reframe the narrative/story.
AskUserQuestion always appends an "Other" choice, so the user can type any
change freely ("Something else"). Start generating only after they pick
**Build it** (or after their edit is folded in and re-approved). If the
AskUserQuestion tool is unavailable in the current environment, fall back to
the same question as plain text.

### Step 1b — Normalize series slides (repeated titles in the source)

When the brief contains a RUN of slides sharing one title with distinctive
subtitles (often with "1/3"-style counters — e.g. "How X works" ×3 with
"Linear receivers — 1/3" etc.):

1. ALWAYS strip "n/m" counters — sequence is carried by the deck flow,
   never by suffixes.
2. **Short parts** (each part's body fits a card: single paragraph,
   ≤ ~220 characters) → collapse the WHOLE series into ONE slide:
   H1 = the shared series title, plus a card row (`add_card_row`) with
   the part titles + condensed bodies.
3. **Long parts** (like multi-paragraph bodies) → full series treatment:
   - An OVERVIEW slide first (+1): H1 = the shared series title; card row
     with the part TITLES (title-only cards) as a preview of the series.
   - Then one slide per part with INVERTED hierarchy:
     * H1 (64px) = the part's own topic (the real message);
     * series subtitle BELOW the H1 (never above — R18): the shared
       series title, H3 36px `Gray-Dark`, one line, ~24px under the
       H1 block;
     * body = paragraph stack, 4 columns (593px);
     * image placeholder on the RIGHT, FULL HEIGHT: from column 6
       (x817) to the right margin — (817,50) 1053×980, i.e. 50px
       margins top/right/bottom.

Layout guard (all slides, not only series): a slide with side text ALWAYS
carries its image placeholder — never leave half the canvas empty. Pure
text slides use the full-width paragraph grid instead.

### MANDATORY checklist — run BEFORE showing any outline or building
### any slide (also on partial edits and regenerations)

- [ ] Repeated-title runs in the source? → apply Step 1b (series
      normalization) FIRST. This check is never optional — even when the
      user asks to rework a single slide from such a series.
- [ ] Any chart slide? → its data table is included (rule 4b).
- [ ] Any card-like content? → engine card component only, grid-snapped.
- [ ] No eyebrow, no badge, no shadow anywhere in the plan (HARD BANS).

### Step 2 — Map content to templates

Pick one template per slide from the catalog (details in
`references/layouts.md`):

| # | Template | Use for |
|---|----------|---------|
| 01 | Cover (gradient) | Deck title |
| 02 | Agenda | Table of contents — a NATIVE table with underlined rows (add/remove rows freely) |
| 03/04/05 | Section statement (dark = two-tone navy, R39) | Section breaks, big claims — `K.add_section_statement` (white + High accent) |
| 06–09 | Text + image placeholder | Points supported by a visual (4 aspect variants) |
| 10 | Four segment cards | Segments, categories, markets |
| 11/12 | Heading + body(ies) + tall image | Explanations with a visual |
| 13 | Image + two captioned blocks | Two facts around one visual |
| 14 | Bar chart (shapes) + REQUIRED data table below | Comparisons, growth series — never the chart alone |
| 15 | Data table (native table) | Structured data — full width (`add_data_table`) OR NOT full width: 8-col table + 3-col text block (`add_side_table`, R42) |

No template fits? Compose a **new** layout on the grid (Step 3b) reusing
the same atoms — never invent off-system styling.

### Step 3a — Generate canonical slides

```python
import sys; sys.path.insert(0, "scripts")
from przntperfect.deck import Deck
from przntperfect import templates  # templates.BUILDERS = ordered list t01..t15

deck = Deck()                 # grid_guides=True by default: every saved deck
                              # carries the 12-col grid as PowerPoint guides —
                              # both edges of every column, so 20px gutters are
                              # visible (edit-mode only, never rendered/printed).
                              # brand_theme=True by default: the deck's theme
                              # color scheme is the Prznt Perfect palette, so the
                              # PowerPoint color picker offers only design-system
                              # colors (see references/rules.md R21).
templates.BUILDERS[0](deck)   # or import t01_cover etc. individually
deck.save("output.pptx")
```

Replace sample copy with real content via each builder's parameters (see
builder signatures in `scripts/przntperfect/templates/`).

### Step 3b — Compose new layouts on the grid

```python
from przntperfect.grid import GRID       # col_x/col_width/row_y/row_height/cell (px)
from przntperfect import primitives as P # add_text, add_two_tone, add_image_placeholder,
                                          # add_footer, add_solid_background, add_hairline
from przntperfect.cards import add_card_row, card_row_geometry
# THE way to build simple card rows (problem cards, stats, numbered steps).
# Cards are GRID-SNAPPED (each spans whole columns) and BOTTOM-ANCHORED to the
# bottom grid line (R27): pass only h_px, omit y_px -> the card's bottom sits on
# y=993.5, never centred. A number heading + its title are ONE tight block, no
# gap between them (R26).
#   add_card_row(slide, [{"number": "1", "title": "Brief", "body": "..."}, ...],
#                h_px=460)                      # bottom-anchored automatically
# Cards share identical internal offsets; never hand-roll card layouts.

from przntperfect import components as K   # richer grid-snapped variants (R23)
# Placed by grid COLUMN, never raw px. Widths are fixed column spans, so they
# can only land on the grid. See references/rules.md R23 for the full map.
# These take an explicit y_px -> BOTTOM-ANCHOR them with K.bottom_anchor(h_px)
# (= 993.5 - h_px) so the card bottom lands on the grid line (R27).
#   K.add_subhead(slide, "muted line under the title")        # balanced, <=4 cols
#   K.add_bio_card(s, col=0, y_px=K.bottom_anchor(521), name=.., role=.., bullets=[..])  # span 6
#   K.add_market_card(s, col=6, y_px=K.bottom_anchor(369), header=[("A",GRAPHITE),(" – B",HIGH)], bullets=[..])
#   K.add_problem_card(s, col=0, y_px=K.bottom_anchor(445), title=.., body=..)   # span 3
#   K.add_solution_card(s, col=3, y_px=K.bottom_anchor(445), number=1, title=.., body=..)  # span 3
#   K.add_metric_card(s, col=0, y_px=K.bottom_anchor(599), header=.., rows=[..])  # native table, span 4
#   K.add_numbered_table(s, col=7, y_px=241, steps=[".."])      # native table, span 5
#   K.add_annotation(s, col=9, y_px=394, text="side note")     # hairline + note, span 3
#   K.add_paragraph_group(s, [p1, p2], cols=4)  # 1-2 body paras: tight stack,
#                                               # rests on the bottom grid line (R34)
#   n = K.add_title(s, [("Two-tone ",GRAPHITE),("title",HIGH)])  # cap-top ON the
#       # top grid line (R35); returns line count -> K.add_subhead(title_lines=n).
#       # ALWAYS place slide titles with add_title, never a raw add_text at y=44.
#       # A monotone title (plain string, e.g. "Agenda") auto-renders GRAPHITE (R40).
#   K.add_section_statement(s, [("Big claim ",WHITE_COOL),("in accent",HIGH)])
#       # ^ section/divider slide: navy bg, TWO-TONE white + High accent (R39).
#       # NEVER an all-white statement, NEVER a raw add_title on navy.
#   K.add_gradient_cover(s, [("Deck name",HIGH),("\nsubtitle",GRAY_DARK)],
#                        date="2026.11.12", quarter="Q4")   # cover AND closing (R38):
#       # Navy->blue gradient; 110px title on the top grid line, Condensed 1.5pt;
#       # blue date+quarter pills (bottom-left); circular arrow (bottom-right,
#       # arrow=False hides it). Simple vector marks = NATIVE shapes, never SVG.
#   P.add_arrow_button(s, x, y, 105, HIGH)   # standalone circular next-arrow
#   K.add_logo(s, image_path="logo.png")  OR  K.add_logo(s, name="Acme")   # top-
#       # right brand mark (R41): logo image fit in a 143.5x38 zone (38px tall),
#       # else the company
#       # name right+top-aligned to the grid corner. On EVERY slide. For dark slides
#       # pass logo_name=/logo_image= to add_gradient_cover / add_section_statement
#       # (they set dark=True), or add_logo(s, name=…, dark=True) directly.
#   K.add_data_table(s, headers, rows, start_col=0, span_cols=12)  # native template-15
#       # data table placeable at ANY columns (header + rows, horizontal rules).
#   K.add_side_table(s, headers, rows, "Heading", "body…", side="left")  # NOT full
#       # width (R42): 8-col data table + 3-col text block + one gap column between
#       # (side="left" -> text left/table right; "right" -> table left/text right).

from przntperfect.textbalance import balance_lines   # even-length line wrapping (R24)
from przntperfect.composites import bar_chart_geometry
# Bar chart: a bar is 2 grid columns for <=6 bars, 1 column for 7-12, on-grid,
# auto (R22). Count is free. NEVER hand-pick a narrow width.
#   xs, w = bar_chart_geometry(6, start_col=0)   # 6 bars -> two cols each, full width
#   xs, w = bar_chart_geometry(9, start_col=0)   # 9 bars -> one col each (auto)
#   ...draw bars with composites.add_bar_chart(...), then the REQUIRED data table:
#   K.add_chart_data_table(s, rows=[period_labels, ...], n=6, start_col=0)
#   # ^ R14: underline SEGMENTED to the bars; BOTTOM-ANCHORED to the grid line
#   #   (R34, never runs past it). Pass the SAME (n,start_col,...) as the bars.
```

Every element snaps to the 12-col × 26-row grid. Use px values from GRID;
primitives convert px→pt (×0.5) internally. Respect zone logic from the
canonical templates (heading top-left, content below, footer fixed).

### Step 4 — Content rules (hard invariants)

From `references/rules.md` — enforce on EVERY text you write or receive:

1. Sentence case only. Never ALL CAPS, never Title Case.
2. No period at the end of any heading.
3. EVERY slide title is H1 64px (= 32pt in PowerPoint), max 3 lines —
   word count does not matter. Only the cover AND closing slide (110px = 55pt,
   R38, via `add_gradient_cover`) differ. Place every other title with
   `components.add_title` (cap-top snaps ONTO the top grid line, R35) — never a
   raw `add_text` at a hand-picked y. Pass its returned line count to
   `add_subhead(title_lines=…)`.
4. Paragraphs separated by blank lines, no first-line indents.
4a. Text next to an image (vertical overlap) always leaves ≥1 empty grid
   column between itself and the image — image at col 6 → text in cols 1–4;
   image at col 7 → text in cols 1–5.
4b. A bar-chart slide ALWAYS includes its supporting data table below the
   chart (native table, segmented underlines). Never the chart alone —
   derive table rows from the chart values if none are given.
4c. In every native table, cell text is VERTICALLY CENTERED within its row
   (anchor="ctr", zero top margin).
4d. 1-2 body paragraphs are ONE GROUP (R16): a tight vertical stack, gap =
   two paragraph spacings (~52px for Body), bottom-anchored to the grid line
   (y=993.5) — never scattered or floated centered. Use
   `components.add_paragraph_group`.
4e. Paragraph width: 4 grid columns (593px) preferred, 5 columns (746px)
   is the hard maximum — only for genuinely large text volumes.
4f. Every paragraph AND subhead is LINE-BALANCED via
   `textbalance.balance_lines(text, width_px, size_px)` (R31): even line
   lengths, no widow/orphan, and no article or 1-2 letter word left at a line
   end (glued to the next word). Card bodies/subheads already do it; free
   `add_text` paragraphs MUST wrap through it too.
4g. Subhead: 12pt (24px) Gray-Dark, 20px below the title — always via
   `components.add_subhead` (R29/R30); pass `title_lines` if the title wraps.
   A person's role in a bio card is Graphite, not muted (R28).
4h. EN-DASH ONLY (–), NEVER the em-dash (—), in every text — titles included
   (R32). The engine auto-converts, but write en-dashes yourself too.
4i. TITLES obey the short-word rule too (R33): never end a title line with a
   1-letter article or 1-2 letter preposition. Soft-wrapping titles are auto-
   glued; hand-break so the short word STARTS the next line; balance monotone
   multi-line statements via `balance_lines`.
4j. Bottom-anchored text/tables sit with the last line's BASELINE on the grid
   line (R34); the chart data table is bottom-anchored and never runs past it.

### HARD BANS — never do these, whatever the content calls for

- NO eyebrows/kickers: no small label above the slide title ("Press
  release", "Case study"…). The title is the topmost text on every slide.
- NO shadows on anything. NO rounded corners on anything.
- NO badges or pills: pagination is plain 14px text via the engine footer,
  never a filled "Page N / M" chip.
- NO text inside filled shapes. Cards = engine card components (bare
  White-Cool rects + separate aligned text boxes). Tables = native tables.
- NO raw python-pptx: EVERY shape goes through the `przntperfect`
  primitives/composites — they enforce Arial, tracking, exact line pitch,
  flatness. If a pattern is missing from the engine, build it FROM the
  primitives, never from `slide.shapes.add_*` directly.
- Card rows: use the engine card component; all cards in a row share
  identical internal offsets — bodies start at the same y in every card.
5. Arial Regular only — no bold, no italic, no other fonts.
6. Colors only from the 14-token palette; accent `High #00A1FF` sparingly.
7. Chart series order: High → High-1 → High-2 → High-3 → High-4.
8. Charts are SHAPES (never native chart objects) — pixel fidelity is the
   contract. Tables (agenda 02, chart values 14, data 15) are NATIVE
   PowerPoint tables: style No-Style-No-Grid, horizontal borders only
   (header underline 1.5pt, row underlines 0.5pt, all Gray-Dark), rows
   freely add/removable. Segmented underlines (14) = spacer columns with
   no borders between content columns.
9. English only.

### Step 5 — Lint before delivering (MANDATORY GATE)

```bash
.venv/bin/python scripts/inspect_pptx.py output.pptx
```

This is a LINTER with an exit code, not a report. It fails the deck on:
shadows, rounded corners, non-Arial/bold/italic runs, sizes outside the
type scale, colors outside the 14-token palette, text inside filled
shapes (badges), eyebrow labels above titles, and a theme color scheme
that is not the Prznt Perfect palette. NEVER deliver a deck that exits
non-zero — fix and regenerate until it prints 0 violations. Also verify
no heading ends with a period and titles are 64px.

## File map

- `references/design-tokens.json` — colors, type styles, grid, canvas math
- `references/rules.md` — the design rules (R1–R24)
- `references/layouts.md` — measured anatomy of all 15 canonical templates
- `scripts/przntperfect/` — engine (units, tokens, grid, primitives,
  composites, deck, theme, cards, components, tables, textbalance, templates/)
- `scripts/build_canonical.py` — regenerates the 15-template reference deck
- `scripts/inspect_pptx.py` — deck QA linter / inventory

## Known traps

- px→pt is **×0.5** (960pt canvas), not dpi-based 0.75.
- Tracking −1%: rPr@spc = −round(size_pt) in 1/100 pt. Exception: the big
  cover/closing heading (110px) uses a fixed Condensed 1.5pt (spc=−150), not −1%
  (client rule 2026-08-06); `add_gradient_cover` handles this. Absolute letter
  spacing anywhere else: `add_text(..., track_pt=−1.5)`.
- Line spacing MUST be exact points (spcPts = size × lh%), never a float
  multiple — renderers scale multiples by the font's natural leading
  (~1.22em), inflating pitch ~22% and sagging first lines. The engine's
  `add_text` already does this; never bypass it.
- Renderers interpolate gradient fills in a non-sRGB space: a two-stop ramp
  bows ~17/255 lighter mid-ramp. Pin intermediate stops at quartiles (see
  t01_cover) for any new gradient.
- U+2028 from copied text → explicit line breaks, never literal.
- PowerPoint quantizes exact line pitch to whole points (even px on the
  1920 canvas): odd-px pitches (e.g. 20px × 124% = 25px) render as the
  nearest even value. Center the residual; never split lines into boxes.
- The canonical file itself violates rule 3 on templates 03–05/14/15
  (64px multi-word titles): reproduce canon for canonical templates,
  follow the rule for new slides.
- Slides 02, 03, 05 carry no footer; 01 has caption only.

Files in this skill

  • SKILL.md20.1 KB
  • references/design-tokens.json7 KB
  • references/layouts.md8.9 KB
  • references/rules.md25.1 KB
  • scripts/build_canonical.py1 KB
  • scripts/inspect_pptx.py13.5 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.