Added helloprint-presentation-creator skill

This commit is contained in:
2026-04-16 13:38:38 +02:00
parent 044313eed7
commit ffc7dd8162
7 changed files with 1579 additions and 0 deletions
+546
View File
@@ -0,0 +1,546 @@
---
name: helloprint-presentation-creator
description: >
Helloprint Presentation Creator — generates on-brand Helloprint .pptx files by copying
slide templates from a curated baseline and swapping text content. Use whenever someone asks
to "make a presentation", "create slides", "build a deck", "make a pptx", or any variation
involving a Helloprint internal or external presentation. Also trigger for: "quarterly review",
"team update", "strategy deck", "board presentation", "pitch deck", "section update slides",
or "present this as slides".
---
# Helloprint Presentation Creator
## HOW IT WORKS
This skill uses a **template-copy engine**. The baseline PPTX (`HP_MainTemplate_baseline.pptx`)
contains 15 perfectly designed slide templates. The generator copies selected slides from that
file, swaps placeholder text, and outputs a new `.pptx` — preserving all fonts, colors, shapes,
and layouts exactly as designed.
**Do NOT use python-pptx or pptxgenjs.** The generator is a raw ZIP/XML engine.
---
## SETUP (run once per session)
Before generating, ensure the generator and baseline are available:
```python
import os, shutil
SKILL_DIR = os.path.dirname(os.path.abspath(__file__)) # adapt to actual skill path
BASELINE_SRC = os.path.join(SKILL_DIR, "HP_MainTemplate_baseline.pptx")
GENERATOR_SRC = os.path.join(SKILL_DIR, "generate_presentation.py")
# Copy to working session directory
shutil.copy(BASELINE_SRC, "/sessions/brave-bold-cerf/hp_baseline.pptx")
shutil.copy(GENERATOR_SRC, "/sessions/brave-bold-cerf/generate_presentation.py")
```
Then run the generator:
```python
import sys
sys.path.insert(0, "/sessions/brave-bold-cerf")
from generate_presentation import generate
```
---
## THE API
```python
generate(slide_plan, output_path)
```
`slide_plan` is a list of `(template_name, replacements)` tuples, in the order you want slides
to appear in the final deck.
`replacements` is a list of `(old_text, new_text)` pairs. Pass raw characters — the engine
handles XML escaping. **Never pass `&` in your strings — pass `&` directly.**
Example:
```python
generate([
("title-dark", [
("Q1 2025 Business Review", "My Presentation Title"),
("Kwartaalresultaten, strategische prioriteiten en roadmap H2", "Subtitle goes here"),
("Team Strategy & Leadership", "Author Name / Team"),
]),
("closing", []),
], "/sessions/brave-bold-cerf/mnt/outputs/MyPresentation.pptx")
```
---
## AVAILABLE SLIDE TEMPLATES
15 templates are available. Below is each template's name, purpose, and **exact placeholder
text strings** that can be replaced. Always copy the exact string shown — these are the
literal XML text values in the baseline.
---
### `title-dark`
Dark background title slide. Always first.
| Placeholder text | What it is |
|---|---|
| `Q1 2025 Business Review` | Main title |
| `Kwartaalresultaten, strategische prioriteiten en roadmap H2` | Subtitle / tagline |
| `Team Strategy & Leadership` | Author / team (bottom right) |
> Note: The date (`15 april 2025`) is auto-filled from the slide and can optionally be replaced.
> `Making print easier, for everyone, everywhere.` is the permanent brand tagline — leave it.
---
### `hero-split-photo`
Left-side dark photo panel with headline overlay. For bold opening statements.
| Placeholder text | What it is |
|---|---|
| `ANNUAL REPORT 2024` | Section label (top) |
| `Building Europe's` | Display headline line 1 |
| `largest print` | Display headline line 2 |
| ` platform.` | Display headline line 3 (note leading space) |
| `300+ suppliers · 14 markets · millions of products` | Sub-caption |
---
### `content-bullets-kpis`
White content slide with 3 bullet rows on the left and 4 KPI cards on the right.
| Placeholder text | What it is |
|---|---|
| `STRATEGIE` | Section label |
| `Drie strategische prioriteiten voor Q2` | Slide title |
| `Revenue Growth` | Bullet 1 title (appears 3× — first occurrence is replaced) |
| ` — target €18M voor FY2025, nu tracking op €16.2M (+12% YoY)` | Bullet 1 body (note leading space) |
| `€16.2M` | KPI 1 number |
| `Q1 Revenue` | KPI 1 label |
| `4.6/5` | KPI 2 number |
| `Supplier NPS` | KPI 2 label |
| `12` | KPI 3 number |
| `AI Skills live` | KPI 3 label |
| `3` | KPI 4 number |
| `New markets H1` | KPI 4 label |
> **3 distinct bullets** are supported via nth-occurrence tuples `(old, new, n)`.
> Because all 3 bullet rows share identical placeholder text in the baseline, use n=1, 2, 3
> to target each row individually. The engine automatically adjusts for already-replaced
> occurrences, so n=2 always means "the 2nd bullet", not "the 2nd remaining one".
```python
("content-bullets-kpis", [
("STRATEGIE", "STRATEGIE Q2"),
("Drie strategische prioriteiten voor Q2", "Drie prioriteiten voor Q2"),
# Bullet 1
("Revenue Growth", "Marketplace Expansie", 1),
(" — target €18M voor FY2025, nu tracking op €16.2M (+12% YoY)",
" — uitrol naar IT en ES gepland voor juni, pipeline 40+ leveranciers", 1),
# Bullet 2
("Revenue Growth", "AI-First Operations", 2),
(" — target €18M voor FY2025, nu tracking op €16.2M (+12% YoY)",
" — 20 Skills in productie, doel is 35 actieve Skills voor einde Q2", 2),
# Bullet 3
("Revenue Growth", "Supplier Kwaliteit", 3),
(" — target €18M voor FY2025, nu tracking op €16.2M (+12% YoY)",
" — NPS van 4.6 naar 4.8 via geautomatiseerde kwaliteitscontrole", 3),
# KPI cards
("€16.2M", "€14.2M"), ("Q1 Revenue", "Q1 Omzet"),
...
]),
```
> **Character limit:** Bullet title: max **25 chars**. Bullet body (after the dash): max **100 chars**.
---
### `impact-statement`
Full dark-green impact slide. For key takeaways. Max 1 per 5 slides.
| Placeholder text | What it is |
|---|---|
| `€1B` | Hero number / stat |
| `Disrupting` | Bold word in headline |
| ` the print industry by building Earth's ` | Normal weight headline text |
| `largest infrastructure` | Bold word in headline |
| ` for customised products.` | Normal weight headline ending |
> Note: `Making print easier, for everyone, everywhere.` is the permanent tagline — leave it.
---
### `chart-kpi-cards`
White slide with chart area on the left and 4 KPI cards on the right.
| Placeholder text | What it is |
|---|---|
| `FINANCIALS` | Section label |
| `Revenue & Margin — Q1 2025` | Slide title |
| `€12.4M` | KPI 1 number |
| `▲ +18% YoY` | KPI 1 delta (use ▲ for positive, ▼ for negative) |
| `Total Revenue Q1` | KPI 1 label |
| `34.2%` | KPI 2 number |
| `▲ +2.1pp` | KPI 2 delta |
| `Gross Margin` | KPI 2 label |
| `1.2M` | KPI 3 number |
| `▼ -3.1%` | KPI 3 delta |
| `Orders Processed` | KPI 3 label |
| `€10.33` | KPI 4 number |
| `▲ +22%` | KPI 4 delta |
| `Revenue per Order` | KPI 4 label |
---
### `two-column-product`
White slide with text on the left and product image placeholder on the right.
Good for product spotlights or feature introductions.
| Placeholder text | What it is |
|---|---|
| `PRODUCT LAUNCH` | Section label |
| `Premium Hardcover Boekjes` | Slide title |
| `Professionele uitstraling voor elk merk` | Left column headline |
| `Ons nieuwe hardcover-programma biedt premium kwaliteit...` | Left column body text |
| `Vanaf 1 stuk bestelbaar` | USP bullet 1 |
| `Gratis ontwerpcheck` | USP bullet 2 |
| `FSC-gecertificeerd papier` | USP bullet 3 |
---
### `usp-4-cards`
Light gray background with 4 equal feature/USP cards.
| Placeholder text | What it is |
|---|---|
| `WAAROM HELLOPRINT` | Section label |
| `Vier redenen om voor ons te kiezen` | Slide title |
| `Laagste prijs` | Card title line 1 (all 4 cards identical in baseline) |
| `garantie` | Card title line 2 (all 4 cards identical in baseline) |
| `All-inclusive prijzen zonder verborgen kosten...` | Card body (all 4 identical in baseline) |
| `30%` | Card stat (all 4 identical in baseline) |
| `goedkoper dan traditioneel` | Card stat label (all 4 identical in baseline) |
**Per-card content** is supported via nth-occurrence tuples `(old, new, n)`. Because all 4
cards share identical placeholder text, pass `n=1..4` to target each card individually:
```python
("usp-4-cards", [
("WAAROM HELLOPRINT", "COMMUNITY MOMENTS"),
("Vier redenen om voor ons te kiezen", "Wat er deze maand gebeurde"),
# Card 1
("Laagste prijs", "Armeense lunch", 1),
("garantie", "Tolma & Ghapama", 1),
("All-inclusive prijzen zonder verborgen kosten...", "Sona nodigde iedereen uit voor een Armeense lunch op 10 april.", 1),
# Card 2
("Laagste prijs", "Paas taart", 2),
("garantie", "van Marte", 2),
("All-inclusive prijzen zonder verborgen kosten...", "Marte verraste het team met een zelfgemaakte paastaart.", 2),
# Card 3
("Laagste prijs", "Chinese snacks", 3),
("garantie", "van Irene", 3),
("All-inclusive prijzen zonder verborgen kosten...", "Irene bracht authentieke Chinese snacks mee voor het hele kantoor.", 3),
# Card 4
("Laagste prijs", "Boek tip", 4),
("garantie", "van Dennis", 4),
("All-inclusive prijzen zonder verborgen kosten...", "Dennis deelde zijn favoriete boek: Zero to One van Peter Thiel.", 4),
# Remove stat elements (replace with empty or relevant values)
("30%", ""),
("goedkoper dan traditioneel", ""),
]),
```
> **Character limits:** Card title line 1: max **20 chars**. Card title line 2: max **20 chars**.
> Card body: max **100 chars** (≈2 lines). Exceeding these causes text to overflow the card box.
---
### `team-photo`
Dark slide with full-bleed photo and stat overlay. For team events or company culture.
| Placeholder text | What it is |
|---|---|
| `TEAM EVENT` | Section label |
| `Rotterdam Summit 2025` | Slide title |
| `150+ Helloprinters. 14 markten.` | Sub-headline |
| `Twee dagen strategie, innovatie en teambuilding.` | Body text |
| `150+` | Stat 1 number |
| `Helloprinters` | Stat 1 label |
| `14` | Stat 2 number |
| `Markets` | Stat 2 label |
| `2` | Stat 3 number |
| `Days` | Stat 3 label |
---
### `process-timeline`
White slide with 4 horizontal process steps. For workflows, onboarding, roadmaps.
| Placeholder text | What it is |
|---|---|
| `WORKFLOW` | Section label |
| `Van ontwerp tot levering` | Slide title |
| `1` | Step 1 number |
| `Upload` | Step 1 title |
| `Upload je ontwerp of gebruik onze online editor.` | Step 1 description |
| `2` | Step 2 number |
| `Designcheck` | Step 2 title |
| `Automatische controle op resolutie, afloop en kleuren.` | Step 2 description |
| `3` | Step 3 number |
| `Productie` | Step 3 title |
| `We matchen je order aan de beste supplier.` | Step 3 description |
| `4` | Step 4 number |
| `Levering` | Step 4 title |
| `Track & trace tot aan je deur. Gemiddeld 5 werkdagen.` | Step 4 description |
| `Gemiddelde levertijd: ` | Footer note line 1 (note trailing space) |
| `5 werkdagen in NL/BE` | Footer note line 2 |
| `Express: 2 werkdagen →` | Footer note line 3 |
---
### `before-after`
White slide comparing two states side by side (gray left panel, dark right panel).
| Placeholder text | What it is |
|---|---|
| `IMPACT` | Section label |
| `AI-First Operations: Voor en Na` | Slide title |
| `Handmatig proces` | Left panel title |
| `Content creation duurde 2–4 uur per product per markt. Handmatige keyword research, copy schrijven, vertalen en publiceren.` | Left panel body |
| `4h` | Left stat 1 |
| `per product per markt` | Left stat 1 label |
| `50` | Left stat 2 |
| `producten per week max` | Left stat 2 label |
| `3 FTE` | Left stat 3 |
| `nodig` | Left stat 3 label |
| `Geautomatiseerd` | Right panel title |
| `Claude Skills handelen keyword research, copy generation en Contentful publishing af. Mens reviewt alleen het eindresultaat.` | Right panel body |
| `15min` | Right stat 1 |
| `500+` | Right stat 2 |
| `producten per week` | Right stat 2 label |
| `0.5 FTE` | Right stat 3 |
| `menselijke review` | Right stat 3 label |
> Note: `⬤ VOORHEEN` and `⬤ NU MET AI SKILLS` are colored dot labels — best left as-is.
---
### `big-number`
Dark background with one giant number. Maximum impact for a single key metric. Max 1 per 5 slides.
| Placeholder text | What it is |
|---|---|
| `Q1 HIGHLIGHT` | Section label (top right, green) |
| `+42%` | The hero number/stat |
| `Year-over-year growth in new customer acquisition` | Label below the number |
| `Driven by marketplace expansion into 3 new markets and a 28% improvement in organic search visibility across all locales.` | Context paragraph |
---
### `section-divider`
Green full-bleed slide announcing a new section.
| Placeholder text | What it is |
|---|---|
| `02` | Section number (appears twice: large decorative + small label — both get replaced) |
| `Financial Performance` | Section title |
| `Omzet, marges en key metrics over het afgelopen kwartaal met een vooruitblik op de rest van het jaar.` | Section description |
> The number `02` appears twice in the XML (visible number + large background decoration).
> Replacing it changes both at once. Always replace with a two-digit number like `01`, `02`, `03`.
---
### `closing`
Dark closing slide with centered logo, tagline, and contact footer. Always last.
No replacements needed — all text is permanent brand content:
- "Making print easier, for everyone, everywhere."
- helloprint.com | info@helloprint.com | ★★★★★ Uitstekend — 32.000+ reviews
```python
("closing", [])
```
---
### `agenda`
White agenda slide with numbered items (up to 5), time allocations, and section label.
| Placeholder text | What it is |
|---|---|
| `TEAM UPDATE — AI & GROWTH` | Section label (top left, green) |
| `Premium Hardcover Boekjes` | Slide title |
| `Q1 Resultaten` | Item 1 title |
| `Revenue, margins, order volume en key highlights` | Item 1 description |
| `5 min` | Item 1 time (right side) |
| `Financial Performance` | Item 2 title |
| `Deep dive in de cijfers per markt en categorie` | Item 2 description |
| `10 min` | Item 2 time (2nd occurrence) |
| `Product & Platform` | Item 3 title |
| `Nieuwe features, marketplace expansie en tech roadmap` | Item 3 description |
| `AI & Automation` | Item 4 title |
| `Skill updates, voortgang en planning voor HY1` | Item 4 description |
> Note: Item 5 (`Q&A` / `Open vragen en discussie` / `15 min`) is at the bottom and uses
> unique text — replace as needed. The item numbers (01–05) are visual icons, not plain text.
---
### `testimonial`
Dark background with large quote, avatar initials, name, and title.
| Placeholder text | What it is |
|---|---|
| `Helloprint heeft ons de flexibiliteit gegeven om snel te schakelen. Van 500 visitekaartjes tot 50.000 brochures — alles wordt op tijd en perfect geleverd.` | Quote text |
| `MV` | Avatar initials |
| `Martine Verhoeven` | Person name |
| `Marketing Director — TechCorp BV` | Person title and company |
---
## RECOMMENDED SLIDE ORDER
```
title-dark → agenda → section-divider → [content slides] → closing
```
Content slides to mix and match between dividers:
- `content-bullets-kpis` — text + KPIs summary
- `before-after` — transformation/impact
- `big-number` — single hero metric
- `chart-kpi-cards` — data-heavy
- `usp-4-cards` — features/benefits
- `process-timeline` — workflow steps
- `two-column-product` — product/feature spotlight
- `impact-statement` — bold statement
- `testimonial` — social proof
- `team-photo` — culture/team
**Rule:** No more than 3 content slides in a row without a visual break (section-divider, big-number, or impact-statement).
---
## WRITING STYLE
All text in presentations must follow Helloprint's tone: **direct, professional, no fluff**.
- Start with the main message — no preamble
- Short titles (max 8 words)
- Body text max 3 lines per block
- KPI numbers should be specific and real
- Avoid filler words: "We are proud to...", "Please note that..."
- Never use ALL CAPS except section labels (those are auto-uppercase in the template)
**Language:** Default to Dutch for internal NL/BE audiences, English for international. Match
the language of the brief — don't mix languages in one presentation.
**Ampersand:** Use `&` in your Python strings. The engine handles XML escaping automatically.
Never write `&` in replacement text.
**Special characters:** Use Unicode directly — `—` (em dash), `→`, `▲`, `▼`, `✓`, `€`, `+`.
See `references/writing-style-guide.md` for detailed Helloprint tone of voice guidelines.
See `references/internal-language.md` for internal Helloprint terminology.
---
## FULL EXAMPLE
```python
import sys, shutil, os
# 1. Setup
SKILL_DIR = "/path/to/helloprint-presentation-creator"
shutil.copy(f"{SKILL_DIR}/HP_MainTemplate_baseline.pptx", "/sessions/brave-bold-cerf/hp_baseline.pptx")
shutil.copy(f"{SKILL_DIR}/generate_presentation.py", "/sessions/brave-bold-cerf/generate_presentation.py")
sys.path.insert(0, "/sessions/brave-bold-cerf")
from generate_presentation import generate
# 2. Build slide plan
plan = [
("title-dark", [
("Q1 2025 Business Review", "AI & Automation — Q2 2025 Update"),
("Kwartaalresultaten, strategische prioriteiten en roadmap H2",
"Voortgang, resultaten en roadmap voor het tweede kwartaal"),
("Team Strategy & Leadership", "AI & Growth Team"),
]),
("agenda", [
("Premium Hardcover Boekjes", "Wat bespreken we vandaag"),
("Q1 Resultaten", "AI Skills Voortgang"),
("Revenue, margins, order volume en key highlights", "20 skills live, impact en learnings Q1"),
("Financial Performance", "Content Automation"),
("Deep dive in de cijfers per markt en categorie", "SEO output, kwaliteit en schaalresultaten"),
]),
("section-divider", [
("02", "01"),
("Financial Performance", "Content Automation"),
("Omzet, marges en key metrics over het afgelopen kwartaal met een vooruitblik op de rest van het jaar.",
"Hoe we 500+ pagina's per maand genereren — on-brand en meertalig."),
]),
("big-number", [
("Q1 HIGHLIGHT", "Q1 RESULTAAT"),
("+42%", "500+"),
("Year-over-year growth in new customer acquisition",
"Productpagina's gegenereerd in Q1 via AI Skills"),
("Driven by marketplace expansion into 3 new markets and a 28% improvement in organic search visibility across all locales.",
"Uitgerold in NL, BE, DE en FR. Gemiddeld 15 min per pagina. Schaalbaar naar alle 14 markten in Q2."),
]),
("closing", []),
]
# 3. Generate
generate(plan, "/sessions/brave-bold-cerf/mnt/outputs/MyPresentation.pptx")
```
---
## CHARACTER LIMITS
Text boxes in the baseline have fixed widths. Exceeding these limits causes text to wrap to a
second line or overflow the element, breaking the visual design. **Always stay within these limits.**
| Template | Field | Max chars |
|---|---|---|
| All slides | Section label (top left, green) | 40 |
| All slides | Slide title | 45 |
| `section-divider` | Section title | 25 |
| `section-divider` | Section description | 130 |
| `usp-4-cards` | Card title line 1 | 20 |
| `usp-4-cards` | Card title line 2 | 20 |
| `usp-4-cards` | Card body text | 100 |
| `agenda` | Item title | 25 |
| `agenda` | Item description | 70 |
| `big-number` | Hero label (below number) | 55 |
| `big-number` | Context paragraph | 200 |
| `process-timeline` | Step title | 15 |
| `process-timeline` | Step description | 80 |
| `testimonial` | Quote text | 210 |
| `testimonial` | Person name | 30 |
| `testimonial` | Person title | 45 |
| `content-bullets-kpis` | Bullet title | 25 |
| `content-bullets-kpis` | Bullet body | 100 |
> **Template fix recommended:** If you need more room, open `HP_MainTemplate_baseline.pptx` in
> PowerPoint, select the relevant text box, go to **Format Shape → Text Options → enable
> "Shrink text on overflow"**. This makes the layout robust against occasional overruns.
> After editing, save as the new baseline and re-upload to Gitea via skill-sync.
---
## TROUBLESHOOTING
**Text not replaced?** Check that your `old_text` exactly matches the placeholder (copy-paste
from the table above). The match is case-sensitive and whitespace-sensitive.
**`&` showing literally?** You passed `&` as new_text — change it to `&`.
**USP cards all same content?** Known limitation — see usp-4-cards note above.
**Slide out of order?** The order in `slide_plan` determines the output order, not the
template index. Put slides in the list in the order you want them.
@@ -0,0 +1,550 @@
"""
Helloprint Presentation Generator — Keep-and-Delete Engine v2
-------------------------------------------------------------
Strategy: copy full baseline → remove unwanted slides → replace text.
All original file numbers / internal references stay intact.
Unused media files are stripped. Slide order matches slide_plan order.
NEW in v2:
- Auto page numbering (replaces hardcoded "03" footer numbers)
- Nth-occurrence replacement: (old, new, n) replaces only the nth match
- Duplicate template support: use same template multiple times in one deck
- Media replacement: swap image files via generate(..., media={})
Slide index map (0-based, matches presentation order in baseline):
0 title-dark 8 process-timeline
1 hero-split-photo 9 before-after
2 content-bullets-kpis 10 big-number
3 impact-statement 11 section-divider
4 chart-kpi-cards 12 closing
5 two-column-product 13 agenda
6 usp-4-cards 14 testimonial
7 team-photo
"""
import zipfile, re, os
from io import BytesIO
BASELINE = "/sessions/brave-bold-cerf/hp_baseline.pptx"
SLIDE_IDX = {
"title-dark": 0,
"hero-split-photo": 1,
"content-bullets-kpis": 2,
"impact-statement": 3,
"chart-kpi-cards": 4,
"two-column-product": 5,
"usp-4-cards": 6,
"team-photo": 7,
"process-timeline": 8,
"before-after": 9,
"big-number": 10,
"section-divider": 11,
"closing": 12,
"agenda": 13,
"testimonial": 14,
}
SLIDE_REL = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide"
SLIDE_CT = "application/vnd.openxmlformats-officedocument.presentationml.slide+xml"
IMAGE_REL = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image"
# Slides that display a footer page number (sz=600 small text in bottom-right)
SLIDES_WITH_PAGE_NUM = {
"content-bullets-kpis", "chart-kpi-cards", "two-column-product",
"usp-4-cards", "process-timeline", "before-after", "agenda",
}
# ── Core helpers ──────────────────────────────────────────────────────────────
def get_slide_file_order(src_zip):
"""Returns list of (slide_path, rels_path, rId) in presentation order."""
prs_xml = src_zip.read('ppt/presentation.xml').decode('utf-8')
prs_rels = src_zip.read('ppt/_rels/presentation.xml.rels').decode('utf-8')
rid_order = re.findall(r'<p:sldId\b[^>]*r:id="([^"]+)"', prs_xml)
rid_to_target = {}
for m in re.finditer(
r'<Relationship\s+Id="([^"]+)"\s+Type="([^"]+)"\s+Target="([^"]+)"', prs_rels
):
if m.group(2) == SLIDE_REL:
rid_to_target[m.group(1)] = m.group(3)
order = []
for rid in rid_order:
if rid in rid_to_target:
target = rid_to_target[rid]
slide_path = 'ppt/' + target
rels_path = re.sub(
r'ppt/slides/(slide\d+\.xml)',
r'ppt/slides/_rels/\1.rels',
slide_path
)
order.append((slide_path, rels_path, rid))
return order
def collect_used_media(src_zip, drop_paths):
"""
Collect media files referenced from any rels file NOT in drop_paths.
Returns set of ZIP paths like 'ppt/media/image1.png'.
"""
used = set()
for name in src_zip.namelist():
if name in drop_paths or not name.endswith('.rels'):
continue
try:
rels_xml = src_zip.read(name).decode('utf-8')
except Exception:
continue
rels_dir = name.rsplit('/', 1)[0]
part_dir = rels_dir.rsplit('/', 1)[0]
for m in re.finditer(r'Target="([^"]+)"', rels_xml):
target = m.group(1)
if target.startswith('http') or target.startswith('#'):
continue
resolved = os.path.normpath(part_dir + '/' + target).replace('\\', '/')
if '/media/' in resolved:
used.add(resolved)
return used
def replace_nth(s, old, new, n):
"""Replace only the nth occurrence (1-indexed) of old with new."""
count, idx = 0, 0
while True:
pos = s.find(old, idx)
if pos == -1:
break
count += 1
if count == n:
return s[:pos] + new + s[pos + len(old):]
idx = pos + 1
return s # nth occurrence not found — leave unchanged
def replace_text(xml_bytes, replacements):
"""
Apply text replacements to slide XML.
replacements: list of (old, new) or (old, new, n)
- (old, new) → replaces ALL occurrences
- (old, new, n) → replaces the nth occurrence (1-indexed, logical card/row number)
Handles XML escaping automatically — pass raw & characters, not &amp;
nth-occurrence tracking: when multiple entries target the same old_text with
different n values (e.g. cards 1-4), the engine adjusts for already-consumed
occurrences so n=2 always means 'the 2nd card', not 'the 2nd remaining one'.
"""
s = xml_bytes.decode('utf-8')
# Track how many nth-replacements have been applied per old_text,
# so later entries with higher n are adjusted to target the correct occurrence.
consumed: dict = {}
for entry in replacements:
old, new = entry[0], entry[1]
occurrence = entry[2] if len(entry) > 2 else None
old_esc = old.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')
new_esc = new.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')
if occurrence is None:
# Replace all occurrences (and reset any consumed counter for this key)
s = s.replace(f'>{old_esc}<', f'>{new_esc}<')
s = s.replace(f'>{old}<', f'>{new_esc}<')
consumed.pop(old, None)
else:
# Adjust n: subtract how many occurrences of this text were already replaced
already = consumed.get(old, 0)
adjusted = occurrence - already
if adjusted >= 1:
replaced_esc = replace_nth(s, f'>{old_esc}<', f'>{new_esc}<', adjusted)
if replaced_esc != s:
s = replaced_esc
else:
s = replace_nth(s, f'>{old}<', f'>{new_esc}<', adjusted)
consumed[old] = already + 1
return s.encode('utf-8')
def apply_page_number(xml_bytes, page_num):
"""
Replace the footer page number in slide XML.
Footer numbers are identified by font size sz="600" (6pt) — distinct from
larger decorative numbers like agenda item badges (sz="1000"+).
"""
s = xml_bytes.decode('utf-8')
num = f"{page_num:02d}"
def replacer(m):
# Check the 300 chars before this text run for sz="600" (footer font size)
start = max(0, m.start() - 300)
context = s[start:m.start()]
# Only replace if this is footer-size text
if re.search(r'sz="600"', context):
return f'>{num}<'
return m.group(0)
s = re.sub(r'>0[1-9]<', replacer, s)
return s.encode('utf-8')
# ── Main generator ────────────────────────────────────────────────────────────
def generate(slide_plan, output_path, media=None, page_numbers=True):
"""
Generate a Helloprint presentation.
slide_plan : list of (template_name, replacements)
replacements items: (old, new) or (old, new, n) for nth-occurrence
output_path : destination .pptx file path
media : dict mapping baseline image filename → local file path
e.g. {"image20.jpg": "/path/to/office_photo.jpg"}
Use for slides with photo slots: hero-split-photo (image20.jpg),
team-photo (image21.jpg), two-column-product (add your own).
page_numbers: if True (default), auto-number slide footers
"""
if media is None:
media = {}
with zipfile.ZipFile(BASELINE, 'r') as src:
slide_order = get_slide_file_order(src)
total = len(slide_order)
# Find max existing slide number for naming copies
existing_nums = [
int(re.search(r'slide(\d+)\.xml', s[0]).group(1))
for s in slide_order
if re.search(r'slide(\d+)\.xml', s[0])
]
next_copy_num = max(existing_nums, default=21) + 100 # safe gap
# ── Build wanted dict + plan_order ────────────────────────────────────
# wanted maps baseline_idx → (slide_info, replacements, template)
# copies maps copy_rid → (source_slide_info, replacements, template, copy_num)
wanted = {}
copies = {} # extra slides for duplicate templates
plan_order = [] # rIds in slide_plan order (for sldIdLst reordering)
seen_rids = set()
copy_counter = 0
for template, replacements in slide_plan:
idx = SLIDE_IDX.get(template)
if idx is None:
print(f" ✗ Unknown template: {template}")
continue
if idx >= total:
print(f" ✗ Index {idx} out of range for {template}")
continue
if idx not in wanted:
# First use of this template
wanted[idx] = (slide_order[idx], replacements, template)
rId = slide_order[idx][2]
plan_order.append(rId)
seen_rids.add(rId)
else:
# Duplicate — create a copy with a new filename and rId
copy_num = next_copy_num + copy_counter
copy_counter += 1
copy_rid = f"rIdCopy{copy_num}"
copies[copy_rid] = (slide_order[idx], replacements, template, copy_num)
plan_order.append(copy_rid)
seen_rids.add(copy_rid)
print(f" ↳ Duplicate '{template}' → new slide{copy_num}.xml")
# Slide number lookup: rId → 1-based position in output
slide_num_map = {rId: i + 1 for i, rId in enumerate(plan_order)}
# ── Path sets ─────────────────────────────────────────────────────────
keep_paths = {slide_order[i][0] for i in wanted}
keep_rids = {slide_order[i][2] for i in wanted}
drop_paths = (
{slide_order[i][0] for i in range(total) if i not in wanted} |
{slide_order[i][1] for i in range(total) if i not in wanted}
)
used_media = collect_used_media(src, drop_paths)
# Also add media from copy sources (they reference same files as originals)
# (already captured above since originals' rels are not in drop_paths)
# Collect media for copied slides (same source rels → same media)
# Already included in used_media since source slides are in keep_paths.
out_buf = BytesIO()
with zipfile.ZipFile(out_buf, 'w', zipfile.ZIP_DEFLATED) as dst:
for item in src.namelist():
# ── Drop unwanted slide XML + rels ────────────────────────────
if item in drop_paths:
continue
# ── Strip unused media; apply optional overrides ───────────────
if item.startswith('ppt/media/'):
fname = item.split('/')[-1]
if item not in used_media:
continue
if fname in media:
# User-provided replacement image
with open(media[fname], 'rb') as f:
dst.writestr(item, f.read())
print(f" 🖼 Replaced {fname}")
else:
dst.writestr(item, src.read(item))
continue
# ── Process kept slides ───────────────────────────────────────
if item in keep_paths:
for idx, (info, replacements, template) in wanted.items():
if info[0] == item:
data = replace_text(src.read(item), replacements)
if page_numbers and template in SLIDES_WITH_PAGE_NUM:
rId = info[2]
pg = slide_num_map.get(rId, 0)
data = apply_page_number(data, pg)
dst.writestr(item, data)
print(f" ✓ {template:30s} ({item})")
break
continue
# ── Patch ppt/presentation.xml — reorder sldIdLst + add copies ─
if item == 'ppt/presentation.xml':
data = src.read(item).decode('utf-8')
# Extract existing sldId elements → map rId → element
rid_to_elem = {}
existing_ids = []
for m in re.finditer(r'<p:sldId\b[^/]*/>', data):
rid_m = re.search(r'r:id="([^"]+)"', m.group(0))
id_m = re.search(r'\bid="(\d+)"', m.group(0))
if rid_m:
rid_to_elem[rid_m.group(1)] = m.group(0)
if id_m:
existing_ids.append(int(id_m.group(1)))
next_sld_id = max(existing_ids, default=256) + 1
# Build sldId elements for copies
for copy_rid, (src_info, _, _, copy_num) in copies.items():
elem = (
f'<p:sldId id="{next_sld_id}"'
f' r:id="{copy_rid}"/>'
)
rid_to_elem[copy_rid] = elem
next_sld_id += 1
# Reorder sldIdLst to match plan_order
ordered = ''.join(
rid_to_elem[rid] for rid in plan_order if rid in rid_to_elem
)
data = re.sub(
r'(<p:sldIdLst>)(.*?)(</p:sldIdLst>)',
lambda m: m.group(1) + ordered + m.group(3),
data, flags=re.DOTALL
)
dst.writestr(item, data.encode('utf-8'))
continue
# ── Patch _rels/presentation.xml.rels — add copy relationships ─
if item == 'ppt/_rels/presentation.xml.rels':
data = src.read(item).decode('utf-8')
# Remove unwanted slide rels
def keep_rel(m):
rid = re.search(r'Id="([^"]+)"', m.group(0))
rtype = re.search(r'Type="([^"]+)"', m.group(0))
if rtype and rtype.group(1) == SLIDE_REL:
return m.group(0) if (rid and rid.group(1) in keep_rids) else ''
return m.group(0)
data = re.sub(r'<Relationship[^>]*/>', keep_rel, data)
# Add relationships for copied slides
copy_rels = ''.join(
f'<Relationship Id="{copy_rid}" '
f'Type="{SLIDE_REL}" '
f'Target="slides/slide{copy_num}.xml"/>'
for copy_rid, (_, _, _, copy_num) in copies.items()
)
data = data.replace('</Relationships>', copy_rels + '</Relationships>')
dst.writestr(item, data.encode('utf-8'))
continue
# ── Patch [Content_Types].xml — add copy overrides ────────────
if item == '[Content_Types].xml':
data = src.read(item).decode('utf-8')
# Remove unwanted slide overrides
def keep_ct(m):
ct_type = re.search(r'ContentType="([^"]+)"', m.group(0))
part = re.search(r'PartName="([^"]+)"', m.group(0))
if ct_type and ct_type.group(1) == SLIDE_CT:
if part:
fname = part.group(1).lstrip('/')
return m.group(0) if fname in keep_paths else ''
return m.group(0)
data = re.sub(r'<Override[^>]*/>', keep_ct, data)
# Add content types for copied slides
copy_cts = ''.join(
f'<Override PartName="/ppt/slides/slide{copy_num}.xml"'
f' ContentType="{SLIDE_CT}"/>'
for _, (_, _, _, copy_num) in copies.items()
)
data = data.replace('</Types>', copy_cts + '</Types>')
dst.writestr(item, data.encode('utf-8'))
continue
# ── Copy everything else verbatim ─────────────────────────────
dst.writestr(item, src.read(item))
# ── Write copied slide files ──────────────────────────────────────
for copy_rid, (src_info, replacements, template, copy_num) in copies.items():
src_path = src_info[0] # e.g. ppt/slides/slide12.xml
src_rels = src_info[1] # e.g. ppt/slides/_rels/slide12.xml.rels
dest_path = f'ppt/slides/slide{copy_num}.xml'
dest_rels = f'ppt/slides/_rels/slide{copy_num}.xml.rels'
data = replace_text(src.read(src_path), replacements)
if page_numbers and template in SLIDES_WITH_PAGE_NUM:
pg = slide_num_map.get(copy_rid, 0)
data = apply_page_number(data, pg)
dst.writestr(dest_path, data)
print(f" ✓ {template:30s} (copy → slide{copy_num}.xml)")
# Copy the rels file too (same media references)
if src_rels in src.namelist():
dst.writestr(dest_rels, src.read(src_rels))
with open(output_path, 'wb') as f:
f.write(out_buf.getvalue())
n_total = len(wanted) + len(copies)
print(f"\n✓ {n_total} slides → {output_path}")
size_mb = os.path.getsize(output_path) / 1024 / 1024
print(f" File size: {size_mb:.1f} MB")
# ── Example: Rotterdam monthly overview ──────────────────────────────────────
if __name__ == '__main__':
plan = [
("title-dark", [
("Q1 2025 Business Review",
"Office Rotterdam — Maandoverzicht"),
("Kwartaalresultaten, strategische prioriteiten en roadmap H2",
"Highlights, events en community · maart–april 2026"),
("Team Strategy & Leadership", "Office Rotterdam"),
]),
("agenda", [
("Premium Hardcover Boekjes", "Wat gebeurde er deze maand"),
("Q1 Resultaten", "Strategy Days"),
("Revenue, margins, order volume en key highlights",
"Strategische afstemming op 14 & 15 april"),
("5 min", "10 min"),
("Financial Performance", "CEO Bezoek"),
("Deep dive in de cijfers per markt en categorie",
"17 CEO's bij Michael's AI-talk op 2 april"),
("Product & Platform", "HelloBoxing"),
("Nieuwe features, marketplace expansie en tech roadmap",
"Bokslessen terug — elke 2 weken op donderdag"),
("AI & Automation", "Community Moments"),
("Skill updates, voortgang en planning voor HY1",
"Eten, cultuur en verbinding in het kantoor"),
]),
("section-divider", [
("02", "01"),
("Financial Performance", "Office Highlights"),
("Omzet, marges en key metrics over het afgelopen kwartaal met een vooruitblik op de rest van het jaar.",
"De belangrijkste momenten van de afgelopen maand op een rij."),
]),
("process-timeline", [
("WORKFLOW", "MAART \u2192 APRIL 2026"),
("Van ontwerp tot levering", "Een maand vol events en verbinding"),
("Upload", "Ronald McDonald"),
("Upload je ontwerp of gebruik onze online editor.",
"Team kookte op 25 maart voor gezinnen bij het Ronald McDonald Huis."),
("Designcheck", "HelloBoxing"),
("Automatische controle op resolutie, afloop en kleuren.",
"Bokslessen hernomen \u2014 bi-weekly op donderdag, start 16 april."),
("Productie", "CEO Bezoek"),
("We matchen je order aan de beste supplier.",
"17 CEO\u2019s aanwezig op 2 april voor Michael\u2019s AI-talk in the living."),
("Levering", "Strategy Days"),
("Track & trace tot aan je deur. Gemiddeld 5 werkdagen.",
"Twee dagen strategische afstemming op 14 & 15 april."),
("Gemiddelde levertijd: ", "Keynote: "),
("5 werkdagen in NL/BE", "vrijdag 17 april om 16:30"),
("Express: 2 werkdagen \u2192", "Met de laatste strategy updates \u2192"),
]),
("big-number", [
("Q1 HIGHLIGHT", "CEO BEZOEK"),
("+42%", "17"),
("Year-over-year growth in new customer acquisition",
"CEO\u2019s aanwezig bij Michael\u2019s AI-talk in the Rotterdam living"),
("Driven by marketplace expansion into 3 new markets and a 28% improvement in organic search visibility across all locales.",
"Op 2 april verwelkomde het Rotterdam kantoor 17 CEO\u2019s voor een sessie over AI en de toekomst van ons vak."),
]),
# USP cards — each card now has individual content (nth-occurrence targeting)
("usp-4-cards", [
("WAAROM HELLOPRINT", "MENSEN & CULTUUR"),
("Vier redenen om voor ons te kiezen",
"Community moments deze maand"),
# Card 1 — Armeense lunch
("Laagste prijs", "Armeense lunch", 1),
("garantie", "Tolma & Ghapama", 1),
("All-inclusive prijzen zonder verborgen kosten. Vind je het goedkoper? Wij matchen het.",
"Sona nodigde iedereen uit voor een Armeense lunch op 10 april.", 1),
("30%", "10 apr", 1),
("goedkoper dan traditioneel", "Armeens lunchen", 1),
# Card 2 — Easter cake
("Laagste prijs", "Easter Cake", 2),
("garantie", "Carrot Cake", 2),
("All-inclusive prijzen zonder verborgen kosten. Vind je het goedkoper? Wij matchen het.",
"Saskia bakte een worteltaart voor Pasen \u2014 op beiden verdiepingen!",2),
("30%", "2 apr", 2),
("goedkoper dan traditioneel", "Paastraktatie", 2),
# Card 3 — Chinese snacks
("Laagste prijs", "Chinese Snacks", 3),
("garantie", "Sachima & Mahua", 3),
("All-inclusive prijzen zonder verborgen kosten. Vind je het goedkoper? Wij matchen het.",
"Yaqi bracht traditionele Chinese snacks mee van haar vakantie.", 3),
("30%", "16 apr", 3),
("goedkoper dan traditioneel", "Internationaal genieten", 3),
# Card 4 — Dennis' book
("Laagste prijs", "Kinderboek Launch", 4),
("garantie", "My New Parents", 4),
("All-inclusive prijzen zonder verborgen kosten. Vind je het goedkoper? Wij matchen het.",
"Dennis\u2019 vrouw lanceerde haar eerste kinderboek \u2014 met de hand geschreven en getekend!",
4),
("30%", "9 apr", 4),
("goedkoper dan traditioneel", "NL & EN editie", 4),
]),
("testimonial", [
("Helloprint heeft ons de flexibiliteit gegeven om snel te schakelen. Van 500 visitekaartjes tot 50.000 brochures \u2014 alles wordt op tijd en perfect geleverd.",
"Rolling dolma boosts productivity by 89%. No proof\u2026 but worth testing! Kom naar boven om mee te helpen met Tolma en Ghapama voor de Armeense lunch."),
("Martine Verhoeven", "Sona Hovhannisyan"),
("Marketing Director \u2014 TechCorp BV",
"People & Culture \u2014 Helloprint Rotterdam"),
("MV", "SH"),
]),
# Second section-divider — duplicate template, now supported
("section-divider", [
("02", "02"),
("Financial Performance", "Upcoming"),
("Omzet, marges en key metrics over het afgelopen kwartaal met een vooruitblik op de rest van het jaar.",
"Wat staat er op de planning voor de komende weken."),
]),
("closing", []),
]
print("Generating: Office Rotterdam — Maandoverzicht\n")
generate(plan, "/sessions/brave-bold-cerf/mnt/outputs/OfficeRotterdam_Maandoverzicht_Apr2026.pptx")
@@ -0,0 +1,277 @@
# Helloprint Internal Language Guide
Source: Helloprint Internal Language Guide v3 (April 2026) — use these terms exactly as defined in all presentations.
---
## 1. People & Roles
| Term | Definition |
|------|-----------|
| Helloprinter | Employee at Helloprint — the standard internal term for all colleagues |
| CX | Customer Experience — customer service team |
| Supplier Management | Team responsible for suppliers |
| Bespoke team | Team that handles manual quotes for custom orders |
| Process Lead | Project lead / owner of a core Helloprint process |
| RevOps | Revenue Operations — alignment between Sales, Marketing and CX |
| Traffic | CX sub-team responsible for the artwork and file flow |
| CJT | Customer Journey Tech — Helloprint's internal product development team |
| PSM | Procurement & Supplier Management — team responsible for supplier relationships |
---
## 2. Systems & Tools
| Term | Definition |
|------|-----------|
| Presta | PrestaShop — e-commerce & order management system (transactional source) |
| PCM | Product Content Management — system for core product data & SKU attributes |
| Contentful | CMS for merchant content: titles, descriptions, images |
| HubSpot | CRM and ticketing system for customer service and sales |
| Cube | Semantic layer for certified metrics and dashboards |
| HelloMatch | Tool that determines the best supplier/carrier combination per product SKU |
| BigQuery | Data warehouse — central analytics platform |
| NetSuite | ERP and financial system — invoice processing |
| Supplier Central | Management system for supplier data and IDs |
| Algolia | Search platform for product indices on the webshops |
| Partner HUB | Portal where suppliers receive, confirm and process orders |
| 360 (platform) | Internal operations/artwork platform for order and file management |
| Sendcloud | Carrier/shipping platform for tracking and label management |
| Lox | Logistics platform / carrier partner (used alongside Sendcloud) |
| Jira | Project management tool for sprint planning and issue tracking |
| Confluence | Knowledge management and documentation tool, linked to Jira |
| GitLab | Version control system for code, CI/CD pipelines and MRs |
| Sentry | Error monitoring tool — real-time detection of production issues |
| Marker.io | Visual bug reporting tool: screenshots + annotations directly from browser |
| SmartSpy | Internal monitoring and tracking tool for behavior and conversions |
| ORQ / orq.ai | AI orchestration platform for managing and monitoring AI workflows |
| FastEditor / Design Online | Online design tool for customers to personalise products |
| Logo Editor | Simplified online tool for customers to upload and position their logo |
| Studio Tool | Internal design tool for the bespoke team |
| Drukbaas | Name of a specific supplier/partner within the supplier network |
---
## 3. AI Systems & Agents
| Term | Definition |
|------|-----------|
| Quotifyer | AI agent that automatically processes bespoke quote requests and creates quotes (also referred to internally as 'bespoke GPT') |
| Anna | AI chatbot for first customer contact; collects information before Quotifyer takes over |
| Content Engine | AI system for generating product content (copy, print icons, SEO) |
| Skill | Claude AI capability that automates a specific internal process |
| MCP | Model Context Protocol — connection between Claude and external tools/systems |
| Knowledge Hub | Internal knowledge base where skills, governance cards & documentation are stored |
| Guardrail | Restriction or rule that keeps an AI system within defined boundaries |
| Engaige | CX platform linked to Quotifyer for re-orders and customer flows |
---
## 4. Processes & Concepts
| Term | Definition |
|------|-----------|
| Bespoke | Custom order outside the standard catalog; requires a manual quote |
| Handover | Transfer of a ticket or conversation from AI to the human |
| SSOT | Single Source of Truth — one authoritative data source per domain |
| PoC | Proof of Concept — first working version to validate an idea |
| Reprint | Reprint of an order due to a confirmed production or quality error |
| Divert | Rerouting of an order to an alternative supplier |
| Autocheck | Automated file check on upload — verifies print readiness |
| cPDF | Certified/Corrected PDF — print-ready file after prepress processing |
| Prepress | File preparation before production: color conversion, bleed, resolution |
| Goodwill | Customer compensation outside warranty — voucher or refund as a gesture |
| Feature Flag | Mechanism to enable or disable features per segment, market or rollout phase |
| MR | Merge Request — GitLab equivalent of a Pull Request (code review step) |
| Pub/Sub | Google Pub/Sub — asynchronous messaging architecture between microservices |
| Self-Onboarding | Process where new suppliers onboard independently via Partner HUB |
| Print Management | Enterprise segment where Helloprint takes over complete print management for a client |
| Reciprocity | Principle: suppliers receive volume in exchange for pricing agreements |
| Savings | Seasonal cost reduction target from supply-side negotiations |
| Supply Cluster | Geographic or product-based grouping of suppliers |
---
## 5. Planning & Rhythm
| Term | Definition |
|------|-----------|
| Season | Planning period of ~6 months; replaces 'quarter' in Helloprint terminology |
| HY1 / HY2 | Half Year 1 / Half Year 2 — the two seasons per year (e.g. 'HY1 Season Goals') |
| OKR | Objectives & Key Results — seasonal targets with measurable results |
| Season prep week | Week(s) in which the team prepares for the next season: OKRs, priorities, planning |
| Delete week | Week reserved for clearing technical debt and cleaning up across the organisation |
| Value Stream | Organisational unit or workflow — used in all official templates |
| HelloDays | Internal Helloprint event for company-wide updates, culture and connection |
---
## 6. Document Templates
Helloprint uses three fixed document templates for developing initiatives and projects:
| Document | Step | Purpose |
|----------|------|---------|
| Opportunity Brief | Step 1 | Problem statement, business case & success metrics |
| Project Charter | Step 2 | Strategy, solution, scope & milestones |
| Blueprint | Step 3 | Functional + technical specs for development |
---
## 7. Business Model Taxonomy
Use these terms exactly as defined — in Looker, presentations and reports.
| Term | Definition |
|------|-----------|
| Business Solutions | Overarching category for all direct customer solutions from Helloprint |
| End Label | Direct Helloprint webshops (helloprint.nl, .fr, .es, Drukzo.nl, etc.) |
| Business Portal | Portal for corporate/enterprise clients with organisation management & budgets |
| Reseller Solutions | Overarching category for all reseller-oriented solutions |
| Connect | B2B platform for resellers: exclusive pricing, broad assortment |
| Print Store / WLS | White Label Shop — reseller-branded webshop on the Helloprint platform |
| Reseller API | Programmatic integration for resellers via helloprintapi.com |
| RMH | Reseller Management Hub — management platform for resellers (resellerhub.io) |
| M&A | Mergers & Acquisitions — revenue distinction: via acquisitions vs. organic growth |
| Organic | Organically grown revenue, separate from acquired brands or shops |
---
## 8. Product Categories (Looker order — use this order consistently)
| Term | Definition |
|------|-----------|
| Commercial Print | Core products: flyers, folders, business cards, letterheads etc. |
| Signage & Outdoor | Outdoor advertising: banners, roll-ups, name boards, display stands etc. |
| Services | Services such as design, digital proofing etc. |
| Corporate Gifts | Promotional gifts & personalised gift items |
| Labels | Stickers and self-adhesive labels |
| Photo products | Photo products: canvas, photo frames, photo gifts etc. |
| Clothing & Textiles | Branded clothing and textile products |
| Packaging | Packaging products: boxes, bags, labels etc. |
---
## 9. Metrics & KPIs
| Term | Definition |
|------|-----------|
| GP | Gross Profit — gross profit in euros (absolute, e.g. '€1.63M GP') |
| GPM | Gross Profit Margin — gross profit margin in % (e.g. '30% GPM target') |
| Contribution Margin | Net revenue minus supplier price minus logistics costs per order line |
| AOV | Average Order Value — average value per order |
| NRR | Net Revenue Retention — measure of customer retention and upsell |
| NSM | North Star Metric — the single core indicator measuring organisational success |
| CPA / CPO | Cost Per Acquisition / Cost Per Order — efficiency of marketing spend |
| ROAS | Return on Ad Spend — return on advertising expenditure |
| NPS | Net Promoter Score — customer loyalty measure: 'how likely are you to recommend us?' |
| CSAT | Customer Satisfaction Score — satisfaction score after customer contact |
| OTD | On-Time Dispatch — KPI for timely shipment from the supplier |
| Activation Rate | % of new customers placing a second order — retention KPI |
| Deflection Rate | % of customer queries resolved without a human agent |
| Automation Rate | % of CX interactions fully handled without human intervention |
| Complaint Rate | % of orders with a complaint — supply-side quality KPI |
| Supplier Quality Score | Quality rating per supplier based on complaints and OTD |
| RFM | Recency / Frequency / Monetary — customer segmentation model based on order behaviour |
| YoY | Year over Year — comparison versus the same period last year |
| WoW / MoM | Week-over-Week / Month-over-Month — short-term period comparisons |
| Match rate | % of bespoke tickets successfully matched by Quotifyer |
| Handover rate | % of tickets transferred to the human bespoke team |
---
## 10. Brand & Identity
| Term | Definition |
|------|-----------|
| Brand Promise | "Making print easier, for everyone, everywhere." |
| Positioning | Building Earth's largest infrastructure for customised products. |
| Personality | Friendly · Pragmatic · Expert help |
| Certifications | B Corp · FSC · ISO · Great Place to Work — as trust signals, not buzzwords |
| Fightclub | Helloprint's external marketing agency |
---
## 11. Shop & Data Taxonomy
### Country Codes (internal use)
| Code | Country / Market |
|------|----------------|
| NL | Netherlands — largest market, HQ Rotterdam |
| BE | Belgium (incl. fr.helloprint.be for French-speaking Belgium) |
| UK | United Kingdom (helloprint.co.uk, Quinns) |
| FR | France |
| ES | Spain |
| DE | Germany |
| IT | Italy |
| SE | Sweden |
| IE | Ireland |
| US | United States (helloprint.us — growth market) |
| PL | Poland |
| API | Reseller API channel (no country; orders via helloprintapi.com) |
### Shop Names by Type
| Category | Examples |
|----------|---------|
| End Label | helloprint.nl · helloprint.fr · helloprint.es · helloprint.co.uk · helloprint.de · helloprint.it · helloprint.se · helloprint.ie · helloprint.us · fr.helloprint.be · Drukzo.nl · Drukzo.be |
| Connect | connect.helloprint.nl · connect.helloprint.fr · connect.helloprint.es · connect.helloprint.co.uk · connect.helloprint.de · connect.helloprint.it · connect.helloprint.se |
| White Label (WLS) | Drukwerkmax.nl · Drukwerktijger.nl · quinnstheprinters.com · allgifts.nl · ZPressPrint.nl · Drukwerkbestellen.be · Event-print.nl · snelindruk.be · printking.be · uprint.be |
| Reseller API | eur.helloprintapi.com · gbp.helloprintapi.com · usd.helloprintapi.com |
| Reseller Hub | eur.resellerhub.io · gbp.resellerhub.io |
### Business Model Structure (Looker)
| Business Model | Sub-model | Description |
|---------------|-----------|-------------|
| Business Solutions | End Label | Direct HP webshops (helloprint.nl etc.) |
| Business Solutions | Business Portal | Enterprise portal for organisations |
| Reseller Solutions | Connect | B2B pricing access for resellers |
| Reseller Solutions | Print Store | White Label Shop (WLS) |
| Reseller Solutions | Reseller API | Programmatic integration |
| Reseller Solutions | Reseller Mgmt. Hub | Management platform (RMH) |
### Reporting Distinction
| Label | Definition |
|-------|-----------|
| Organic | Revenue from organically growing shops (no acquisition) |
| M&A | Revenue from acquired brands or shops (Mergers & Acquisitions) |
---
## 12. Operations & Supply Chain
| Term | Definition |
|------|-----------|
| Partner HUB | Portal where suppliers receive, confirm and process orders |
| Supplier Hub | Overarching supplier platform (synonym for Partner HUB in broader context) |
| Self-Onboarding | Process where new suppliers onboard independently via Partner HUB |
| OTD | On-Time Dispatch — KPI for timely shipment from the printer |
| Reprint | Reprint of an order when a production or quality error is confirmed |
| Divert | Rerouting of an order to an alternative supplier |
| Savings | Seasonal cost reduction target from supply-side negotiations |
---
## 13. Tech & Development
| Term | Definition |
|------|-----------|
| CJT | Customer Journey Tech — Helloprint's internal product development team |
| MR | Merge Request — GitLab equivalent of a Pull Request (code review step) |
| Feature Flag | Mechanism to enable or disable features per segment, market or rollout phase |
| Pub/Sub | Google Pub/Sub — asynchronous messaging architecture between microservices |
| ORQ / orq.ai | AI orchestration platform for orchestrating and monitoring AI workflows |
---
## 14. Strategy & Initiatives
| Term | Definition |
|------|-----------|
| Moonshot | Helloprint's long-term ambition: €1B revenue with minimal headcount growth (€1M revenue per FTE) |
| Print Management | Enterprise segment where Helloprint takes over complete print management for a client |
| AI-first | Helloprint's operating model: AI handles the bulk of work, humans focus on judgment and strategy |
| HY1 / HY2 | Half Year 1 / Half Year 2 — the two planning seasons per year |
| Road to Independence | Strategic initiative name — used in leadership presentations |
Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

@@ -0,0 +1,206 @@
# Helloprint Writing Style Guide for Internal Presentations
Source: Helloprint Writing Style Guide (April 2026) — applies to ALL internal presentations.
Scope: Internal only. Not for external customer or partner communications.
---
## 1. Brand Personality — Four Pillars (always active)
| Pillar | What it means |
|--------|---------------|
| Friendly | Accessible and warm — but never casually informal. Write for busy professionals, not consumers. |
| Pragmatic | No-nonsense. No woolly language, no corporate jargon. If it isn't needed, leave it out. |
| Expert | Knowledge-driven and helpful. Plain language. Don't assume everyone knows everything — but treat the reader as intelligent. |
| Direct | Always lead with the main message. Conclusion at the top — not after three paragraphs of build-up. |
---
## 2. Green List & Red List
### ✅ Do this
- **Conclusion-first titles.** The slide title is the takeaway, not the topic. Good: "Q1 above target — but one risk needs attention"
- **Specific numbers.** A number makes a claim credible; an adjective does not. Good: "NPS rose from 42 to 57"
- **Honest about limitations and misses.** Internal trust is built through transparency, not spin. If something didn't work, say so directly.
- **Tables for comparisons.** Use tables when comparing 3 or more items across multiple dimensions.
- **One core idea per slide.** If you're unsure whether a slide says two things — split it.
- **Active and personal writing.** 'We decide', 'You do X', 'I propose'.
- **Document metadata at the top.** Always include For / Author / Date / Status.
- **Use → for direction or impact.** 'Days → hours', '42 → 57 NPS', 'Weeks → 1 run'.
### ❌ Avoid this
- **Topic titles with no point.** Titles like 'Strategic Update' or 'Q1 Review' say nothing.
- **Vague claims.** Words like 'enormous', 'great', 'a lot' without evidence. No number = no argument.
- **Glossing over or omitting bad news.** Internal readers see through it.
- **Nested bullet points deeper than 2 levels.** Restructure instead of nesting.
- **Multiple messages on one slide.** The reader remembers the first or the last — rarely both.
- **Passive and impersonal writing.** 'It has been decided', 'It is being considered', 'One aims to'.
- **Documents without a metadata header.**
- **Adjectives as proof.** 'Significant', 'large', 'many'. Always quantify.
---
## 3. Tone by Context
| Context | Tone | Real Helloprint Example |
|---------|------|------------------------|
| Strategy / Leadership Deck | Clear, data-first, ambitious but realistic | "Helloprint is at a decisive inflection point. The ambition: €1B in revenue, minimal headcount growth." |
| Kickoff / All-Hands | Energetic and confident — but concrete, never hype | "We are rolling out Claude. Not as a buzzword — as a real competitive advantage." |
| OKR Update / Retrospective | Honest, constructive, data-driven. Own your misses explicitly. | "Goal not reached (❌). Cause: supplier delay. Fix: new ETA 22 Apr, owner: Tim." |
| Operational Update | Concise, status-driven, action-oriented | "Status: 🟢. Blocker: Cube data validation. Owner: Joao. ETA: week 16." |
| Team Email | Warm, direct, goal stated upfront | "Hi everyone — we're taking a major step in our AI-first transformation. Here's what it means for you." |
---
## 4. Standard Presentation Structure
Use this sequence. Not every deck needs all blocks — but the order is always the same.
| # | Block | What goes here |
|---|-------|---------------|
| 1 | Metadata | For / Author / Date / Status (Draft / Final) at the top of every document. |
| 2 | Title / Conclusion | The takeaway goes in the title — not the topic. Write the title as if you already know the conclusion. Good: "Q1 above target — structural risk in NL requires a decision" Bad: "Q1 Review" |
| 3 | Executive Summary | Max 3–5 sentences. What's happening, what you're proposing, what you need from the reader. |
| 4 | Data / Core | Tables for comparisons. Bullets for lists of 3+ items. Always concrete numbers. Use → to show direction. |
| 5 | Implication | What does this mean? One sentence, active and assertive. Good: "This requires a decision on X before 30 April." |
| 6 | Next Step | Format: Owner · Action · Deadline. Always specific. No action item without a name on it. |
---
## 5. Language & Style Rules
**English or Dutch?** Choose one language per document. Dutch for Dutch-only teams. English when the audience is international or at leadership level. Mixing languages within a single sentence is always wrong.
**Capitalisation & Punctuation:**
- NO ALL CAPS, except abbreviations (OKR, B2B, NPS, FTE)
- NO exclamation marks in titles or conclusions
- Title Case only for section headers, not in running text
**Emojis — Functional, not Decorative:**
- Use ✅ ❌ 🟢 🟡 🔴 for status in tables or OKR updates
- Maximum 1–2 emojis per document for tone or atmosphere
- Never in a formal conclusion or Executive Summary
**Bold & Formatting:**
- Bold only for key terms or KPIs
- Italics for quotes or examples
- Never underline (it looks like a hyperlink)
- Never bold and italics at the same time
---
## 6. Before vs After — Concrete Examples
| Type | ❌ Don't | ✅ Do |
|------|---------|------|
| Slide title | Q1 Strategic Update | Q1 above target — NL margin requires a decision before 30 April |
| Conclusion | The results are positive and we've learned a lot this quarter. | NPS +15pt. Revenue +8% vs target. One critical risk: NL churn up 3%. |
| Action item | We will endeavour to address this in the coming period. | Owner: Derk. Action: complete NL margin analysis. Deadline: 22 April. |
| Limitation | The data is not yet fully available at this point in time. | Cube pricing data is partial. Cross-check with Looker before deciding. |
| Impact | This will save the team a lot of time. | This reduces manual content creation: weeks → hours per market. |
---
## 7. Slide Templates by Type
### Template 1 — Opening Slide
- **Purpose:** Introduce the presentation and orient the audience immediately.
- **When:** First slide of every presentation.
- **Layout:** Visually dominant, minimal text. Dark background (Thamar Black #191919).
- **Required:** Title (central question or assertion — not the topic) · Subtitle or occasion (max 1 sentence) · Presenter(s) + role · Date and audience (e.g. "MT — 15 April 2026")
- **Optional:** Helloprint logo · Short agenda overview (max 3 items)
- **Example:** TITLE: "Road to Independence — Where do we stand, and what decisions need to be made?" SUBTITLE: MT update Q1 2026 BY: Derk Disselhoff, AI & Growth DATE: 15 April 2026 | Management Team
- **Writing rules:** Title is a question or assertion — never just a topic. No welcome sentence. Get straight to it.
### Template 2 — Video or Full-Screen Visual Slide
- **Purpose:** Capture attention, set the scene, or demonstrate something without distraction.
- **When:** Opener, interlude, or product/tool demonstration.
- **Layout:** Full screen. Video or image fills the entire canvas.
- **Required:** Nothing — the visual or video is the content.
- **Optional:** Maximum one line of text as context, at the top or bottom (small font, high contrast). Video duration goes in speaker notes — not on the slide.
- **Writing rules:** No title on the slide if the image speaks for itself. If text is needed: maximum 6 words. Put all explanation in the speaker notes.
### Template 3 — Agenda Slide
- **Purpose:** Show the structure of the presentation so the audience knows what's coming.
- **When:** Directly after the opening slide for presentations of 20+ minutes.
- **Required:** Numbered list of topics (max 6) · Time indication per block (recommended)
- **Example:** 1. Situation & context (5 min) 2. Q1 performance (10 min) 3. Three strategic choices (15 min) 4. Decision & next steps (5 min)
- **Writing rules:** Agenda items are topics — this is the exception to the conclusion-first rule. Short noun or verb phrases. No full sentences.
### Template 4 — Company Update
- **Purpose:** Deliver a broad organisational update on strategy, direction, or major developments.
- **When:** All-hands, MT update, quarterly update.
- **Required:** Title = the central message (conclusion-first) · What changed or was decided? · What does it mean for the organisation? · What do you need from the audience?
- **Structure:** TITLE: [Central message / decision] CONTEXT: Why is this relevant now? (max 2 sentences) UPDATE: What has concretely changed or been achieved? IMPACT: What does this mean for teams / individuals? NEXT STEP: Owner · Action · Deadline
- **Writing rules:** Warm but to the point. No management-speak ('synergies', 'alignment', 'strategic pillars'). If there's bad news: put it in the title, not buried at the end.
### Template 5 — Performance Update
- **Purpose:** Report results against targets.
- **When:** Weekly/monthly/quarterly reports, OKR reviews.
- **Required:** Title = the conclusion of the numbers, not 'Performance Update' · Status per KPI: 🟢 on track / 🟡 attention needed / 🔴 action required · Actual vs Target (always both) · Commentary on deviations
- **Structure per KPI:** METRIC: [KPI name] STATUS: 🟢/🟡/🔴 ACTUAL: [Number] TARGET: [Number] TREND: [Comparison with prior period] COMMENTARY: [One sentence on cause or expectation — only for 🟡 and 🔴]
- **Writing rules:** Use → for trends. Never 'the results are positive'. State the number, the target, the gap. For a 🔴: always include the cause and the corrective action.
### Template 6 — OKR / Goals Update
- **Purpose:** Transparently report progress on objectives and key results.
- **When:** End of a month, quarter, or season.
- **Required per Objective:** Objective (ambition in one sentence) · Key Results with status (✅ / ❌ / 🔄) · Percentage or number achieved · For ❌: cause + corrective action
- **Structure:** OBJECTIVE: [Ambition in one active sentence] KR 1: [Description] | Target: X | Actual: Y | Status: ✅ KR 2: [Description] | Target: X | Actual: Y | Status: 🔄 KR 3: [Description] | Target: X | Actual: Y | Status: ❌ Cause: [one sentence] Fix: [action + owner + date] OVERALL: [X]% achieved
- **Writing rules:** Be honest about misses. ❌ is not a shame — a ❌ without explanation is. Write the Objective actively. No long narratives per KR. Commentary only on deviations.
### Template 7 — Team Update
- **Purpose:** Share team results and developments with a broader audience.
- **When:** All-hands, MT presentation, season review.
- **Required:** Team name + mission statement (one sentence) · What did we do? (max 5 bullets, facts + numbers) · What did we learn? · Focus for the next period
- **Structure:** TEAM: [Name] MISSION: [One sentence — what is this team's reason for existing?] WHAT WE DID: - [Result 1 + number] WHAT WE LEARNED: - [Insight 1] NEXT PERIOD: - [Focus 1] BLOCKER: [What's blocking us? Who needs to do what to resolve it?]
- **Writing rules:** Mission statement in one sentence, active and specific. Not 'we aim to...'. Results always with a number or comparison. Be honest about blockers.
### Template 8 — Decision Slide
- **Purpose:** Request or present a decision to the audience.
- **When:** MT updates, stakeholder presentations, go/no-go moments.
- **Required:** Title = the decision being requested (as a question or assertion) · Context (why now?) · Max 3 options with pros/cons · A recommendation · An explicit ask to the audience
- **Structure:** TITLE: [Decision question — e.g. 'Do we proceed with market X?'] CONTEXT: [Why now? Max 2 sentences.] OPTIONS: Option A: [Description] | Pro: ... | Con: ... Option B: [Description] | Pro: ... | Con: ... Option C: Do nothing | Pro: ... | Con: ... RECOMMENDATION: Option [X], because [one-sentence rationale]. ASK: [What do you need? Approval / input / decision?] DEADLINE: [When does the decision need to be made?]
- **Writing rules:** Always give a recommendation. Options without a position is not a decision slide. Always include the 'do nothing' option. State the ask to the audience explicitly.
### Template 9 — Closing Slide / Next Steps
- **Purpose:** Close the presentation with a clear landing.
- **When:** Last slide of every presentation.
- **Required:** Core message of the presentation (1 sentence — the takeaway) · Next steps: Owner · Action · Deadline
- **Optional:** Open questions for discussion · Contact / follow-up meeting
- **Example:** CORE MESSAGE: 'Q1 is above target, but the NL margin requires a decision before 30 April.' NEXT STEPS: - Derk → Complete NL margin analysis → 22 April - Tim → Escalate with supplier → 18 April
- **Writing rules:** Repeat the core message in one sentence — not the full summary. Every next step has a name on it. No 'thank you for your attention'. Close with the actions.
---
## 8. Brand Visual Identity
**Colours:**
- Primary: Charmed Green #008539 · Link Green #049E46 · Thamar Black #191919 · Snowflake #F0F0F0 · Orochimaru #D9D9D9 · White #FFFFFF · Dark Green #005c26
- Secondary: Heroic Red #D64545 · Honey Teriyaki #F36D13 · Ripe Mango #FDC325 · Hampton Beach #9D6639 · Seaside #67A5B1
**Font:** Inter (weights 300, 400, 500, 600, 700, 900)
**Slide layouts:**
- Title slide: Thamar Black (#191919) background, white text, green accent line
- Content slide: White background, green section label, footer with logo and page number
- Impact / Statement slide: Dark Green (#005c26) background — use sparingly, max 1 per 5 slides
- Data / KPI slide: White background, KPI cards with green accent bar on left
**Google Slides canvas:** 1920×1080 (16:9)
---
## 9. Pre-Flight Checklist — Before You Share
Run through this before finalising any presentation:
1. Does the document include For / Author / Date / Status at the top?
2. Is the title of every slide the conclusion, not the topic? (exception: agenda slide)
3. Is the main message in the first sentence?
4. Are all claims backed by specific numbers?
5. Have misses or limitations been reported honestly?
6. Is the language active and personal (no passive constructions)?
7. Are emojis used functionally (not decoratively)?
8. Are ALL CAPS, exclamation marks, and jargon avoided?
9. Is every next step clear: owner · action · deadline?
10. Is the language consistent throughout (English or Dutch — not mixed)?
11. Does each slide type match the correct template from section 7?