Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
200 changes: 200 additions & 0 deletions .claude/commands/workshop-banner/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
---
name: workshop-banner
description: "Generate a workshop/event banner image. Auto-detects the event from arguments or git status, reads frontmatter for title, event type, and presenters, then renders a 1368x768 PNG saved into the event directory with frontmatter updated. Use when the user types /workshop-banner or asks to create, generate, or regenerate a workshop banner, event banner, or social image for an event. Accepts optional arguments like an event slug, file path, or overrides."
---

# `/workshop-banner` — Generate Workshop/Event Banner

You are generating a banner image for a Pulumi workshop or event. Follow these steps precisely.

**Skill directory**: `.claude/commands/workshop-banner/` — all paths below are relative to the project root unless noted.

## [Step 1/4] Find the workshop/event

Locate the event that needs a banner:

1. If `$ARGUMENTS` contains a file path (e.g., `content/events/foo/index.md`), use that directly.
2. If `$ARGUMENTS` contains a bare slug (e.g., `designing-reusable-infrastructure-as-code`), resolve it to `content/events/<slug>/index.md`.
3. Otherwise, run `git status` and `git diff --name-only` to find modified/untracked `content/events/*/index.md` files.
4. If multiple events are found, use `AskUserQuestion` to ask which one.
5. If no event is found, ask the user to specify a path or slug.

Read the full event file (frontmatter + body).

## [Step 2/4] Parse event frontmatter & user requests

### From the event frontmatter, extract:

| Config key | Frontmatter source | Parsing notes |
|---|---|---|
| `title` | `main.title` (fall back to top-level `title`) | Use as-is |
| `event_type` | `main.event_type` | Usually `"workshop"` or `"event"` |
| `speaker_name` | `main.presenters[].name` | See presenter selection in Step 3 |
| `speaker_title` | `main.presenters[].role` — portion **before** the last comma | e.g., `"Senior Solutions Architect, Pulumi"` → `"Senior Solutions Architect"` |
| `speaker_company` | `main.presenters[].role` — portion **after** the last comma | → `"Pulumi"`. If no comma in role, treat entire string as `speaker_title`, leave `speaker_company` empty |
| `speaker_photo` | `main.presenters[].photo` | Convert site-root path to absolute filesystem path: prepend `<project_root>/static`. E.g., `/images/team/engin-diri.jpg` → `<project_root>/static/images/team/engin-diri.jpg` |

### From `$ARGUMENTS`, parse user preferences:

- **CTA text**: e.g., `"Sign Up"`, `"Join Now"`
- **Title override**: if the user explicitly provides different text in quotes, use that instead of the frontmatter title
- **Presenter name**: if a specific presenter name is mentioned, pre-select that presenter

If `$ARGUMENTS` fully specifies everything needed (event path + any overrides) and the event has exactly one presenter, skip the interactive questions and go straight to Step 3's rendering section.

## [Step 3/4] Progressive questions & render

### Interactive selection (up to 4 progressive questions)

Ask the following questions **progressively** (one at a time) using `AskUserQuestion`. Skip any question that was already answered via `$ARGUMENTS` or is not applicable.

---

### Question 1: Featured speaker (only if multiple presenters)

If `main.presenters` contains more than one presenter, ask which one to feature. If there is exactly one presenter (or zero), skip this question and use that presenter automatically.

```
header: "Featured Speaker"
question: "Which presenter should be featured on the banner?"
options:
- label: "<presenter1.name>"
description: "<presenter1.role>"
- label: "<presenter2.name>"
description: "<presenter2.role>"
...
```

Build options dynamically from `main.presenters[]`.

---

### Question 2: CTA button text

Skip if already provided via `$ARGUMENTS`.

```
header: "CTA Button"
question: "What text for the call-to-action button?"
options:
- label: "Register"
description: "Default CTA for workshops"
- label: "Sign Up"
description: "Alternative registration CTA"
- label: "Join Now"
description: "For live events"
- label: "Watch Now"
description: "For replays (event has a youtube_url)"
- label: "Custom"
description: "Enter your own CTA text"
```

If the event has a `youtube_url` set, suggest "Watch Now" as the default. Otherwise default is "Register". If the user selects "Custom", follow up by asking for their text.

---

### Company logo (hard-coded)

Always use the Pulumi logo at `static/logos/brand/logo-on-black.png`. Convert to absolute path for the config: `<project_root>/static/logos/brand/logo-on-black.png`. Do **not** ask the user about this — it is always included.

---

### Question 3: Partner logos (optional)

Only ask this if `main.tags.clouds` or `main.tags.topics` suggest a partner context (e.g., clouds contains `"AWS"`, `"Azure"`, `"Google Cloud"`).

```
header: "Partner Logos"
question: "Add partner/technology logos to the bottom-right?"
options:
- label: "Skip"
description: "No partner logos"
- label: "Add logos"
description: "Provide paths to partner logo files"
```

If the user selects "Add logos", ask for:
1. Paths to logo files (absolute paths)
2. Optional `partner_text` (e.g., `"Powered by"`)
3. Whether logos go before or after the text

Suggest relevant logos based on tags — e.g., if `clouds: ["AWS"]`, mention the user could provide an AWS logo.

**Logo variant rule**: Partner logos render on a light (`#F9F9F9`) background. Always use the dark/color version of a logo — avoid files with `white`, `_white`, or `-white` in the name (e.g., use `aws.svg` not `logo-aws_white.png`). When searching `static/logos/tech/`, check the filename and prefer the variant without a white/light suffix.

---

### Build config and render

**Build the JSON config** with all collected values:

```json
{
"event_type": "<from main.event_type>",
"title": "<from main.title or override>",
"cta_text": "<from question or default 'Register'>",
"speaker_photo": "<absolute path: project_root/static + frontmatter photo>",
"speaker_name": "<from selected presenter>",
"speaker_title": "<parsed from role, before last comma>",
"speaker_company": "<parsed from role, after last comma>",
"company_logo": "<absolute path or empty>",
"partner_logos": ["<absolute paths>"],
"partner_text": "<if provided>",
"partner_logos_after_text": ["<absolute paths>"]
}
```

Write the config to `/tmp/banner_config_<event-slug>.json`.

**Determine output path**: `content/events/<event-slug>/meta.png` — saves the banner alongside the event's `index.md`.

**Render** using the HTML renderer (requires Playwright):

```bash
python3 <skill_dir>/scripts/render_banner_html.py \
--config /tmp/banner_config_<event-slug>.json \
--output content/events/<event-slug>/meta.png
```

Read the output PNG to show the result to the user.

## [Step 4/4] Confirm & update frontmatter

1. Verify the PNG was created successfully at the expected path.
2. Check the event's frontmatter for `meta_image:` and `meta_image_square:` — add or update both:
```yaml
meta_image: /events/<event-slug>/meta.png
meta_image_square: /events/<event-slug>/meta-square.png
```
These are Hugo site-root-relative paths (no `content/` prefix).
3. Report to the user:
- Which event was used (path and title)
- Which presenter was featured
- What config was applied (event type, CTA, logos)
- Where `meta.png` and `meta-square.png` were saved
- That `meta_image` and `meta_image_square` were updated in frontmatter
- Remind them to preview the images to make sure they look good
4. Ask if any adjustments are needed (text, layout, speaker choice, etc.).

## Layout reference

- **Left panel** (~65% width, `#F9F9F9` background): event type badge (top-left), title (vertically centered), partner logos + label (above CTA), CTA button (bottom-left)
- **Right dark panel** (~35% width, `#20054E`): company logo (top-center), circular speaker photo with decorative rings (center), speaker name / title / company (below photo)
- **Background decoration**: subtle gray curved lines (`#DFDFDF`) in the top-right and bottom-right corners of the left panel

## Notes

- The HTML renderer uses Google Fonts (Inter) for all text.
- Title auto-sizes based on character count — two separate scales for landscape and square.
- Speaker photo is displayed in a circle with a decorative ring; a placeholder silhouette shows when no photo is provided.
- Images are embedded as base64 data URIs — all paths must be absolute filesystem paths.
- Partner logos render on the light `#F9F9F9` background; always use dark/color variants.
- Requires `playwright` Python package (`pip install playwright && playwright install chromium`).
- Two PNGs are produced per run: `meta.png` (1200×628 landscape) and `meta-square.png` (628×628 square).

## Error handling

- If `render_banner_html.py` fails, read the error output and try to fix the config (common issues: invalid photo path, missing file).
- If a presenter has no `photo` field, the renderer will show a placeholder silhouette — this is fine.
- If the event has no presenters at all, skip the speaker fields entirely (the banner will show a placeholder).
- If the frontmatter title is very long (>65 characters), mention to the user that they can provide a shorter override for better visual results — at that length the font drops to its smallest size.
Loading
Loading