From de13c8306b88adfb602e6cd08f6e2818150f54b6 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sun, 13 Sep 2026 18:05:41 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20templates,=20not=20just=20palettes=20?= =?UTF-8?q?=E2=80=94=20colour=20title=20and=20section=20pages,=20heading?= =?UTF-8?q?=20bands,=20striped=20callouts,=20question=20slides=20with=20an?= =?UTF-8?q?swers,=20key-point=20strips,=20page=20footers;=20Grand=20Rounds?= =?UTF-8?q?=20and=20Board=20Review=20join=20the=20catalogue?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU --- assets/deck-themes.json | 113 +++++++++- docs/my-resources.md | 28 +++ .../__pycache__/render_pptx.cpython-313.pyc | Bin 44145 -> 58729 bytes scripts/render_pptx.py | 209 +++++++++++++++++- src/utils/deckSample.js | 7 + src/utils/deckSchema.js | 28 ++- src/utils/docSpec.js | 7 + test/deck-theme-sample.test.js | 3 +- 8 files changed, 373 insertions(+), 22 deletions(-) diff --git a/assets/deck-themes.json b/assets/deck-themes.json index 8e0a7f80..086f5a4d 100644 --- a/assets/deck-themes.json +++ b/assets/deck-themes.json @@ -4,7 +4,7 @@ { "id": "clinical-blue", "name": "Clinical Blue", - "description": "The default. Calm, high-contrast, reads well on a projector.", + "description": "The default. Calm, high-contrast, reads well on a projector. Key-point strips under slides, question panels for check-yourself moments.", "accent": "2563EB", "ink": "1F2937", "muted": "4B5563", @@ -13,12 +13,21 @@ "font": "Calibri", "tint": "EFF6FF", "tint_alt": "FEF3C7", - "accent_alt": "D97706" + "accent_alt": "D97706", + "style": { + "title": "band", + "section": "quiet", + "heading": "rule", + "callout": "card", + "question": "panel", + "footer": false, + "takeaway": true + } }, { "id": "teaching-amber", "name": "Teaching Amber", - "description": "Warmer and less clinical. Good for sessions with parents or students.", + "description": "Warmer and less clinical, for sessions with parents or students. Split title page, numbered section bands, striped callouts.", "accent": "D97706", "ink": "1C1917", "muted": "57534E", @@ -27,12 +36,21 @@ "font": "Calibri", "tint": "FEF3C7", "tint_alt": "EFF6FF", - "accent_alt": "2563EB" + "accent_alt": "2563EB", + "style": { + "title": "split", + "section": "band", + "heading": "bar", + "callout": "stripe", + "question": "panel", + "footer": false, + "takeaway": true + } }, { "id": "ward-teal", "name": "Ward Teal", - "description": "Quieter than blue, still clearly clinical.", + "description": "Quieter than blue, still clearly clinical. Page numbers on every slide; questions get a spotlight slide of their own.", "accent": "0F766E", "ink": "134E4A", "muted": "4B5563", @@ -41,12 +59,21 @@ "font": "Calibri", "tint": "CCFBF1", "tint_alt": "FEF3C7", - "accent_alt": "D97706" + "accent_alt": "D97706", + "style": { + "title": "band", + "section": "quiet", + "heading": "rule", + "callout": "card", + "question": "spotlight", + "footer": true, + "takeaway": true + } }, { "id": "slate", "name": "Slate", - "description": "Almost monochrome. Lets figures and tables carry the colour.", + "description": "Almost monochrome, so figures and tables carry the colour. Striped callouts, page numbers, no key-point strips.", "accent": "475569", "ink": "0F172A", "muted": "64748B", @@ -55,12 +82,21 @@ "font": "Calibri", "tint": "F1F5F9", "tint_alt": "E2E8F0", - "accent_alt": "64748B" + "accent_alt": "64748B", + "style": { + "title": "band", + "section": "quiet", + "heading": "rule", + "callout": "stripe", + "question": "panel", + "footer": true, + "takeaway": false + } }, { "id": "high-contrast", "name": "High Contrast", - "description": "For a bright room or a poor projector. Heavier ink, stronger rules.", + "description": "For a bright room or a poor projector: full-colour title and section pages, headings on bands, heavier ink.", "accent": "B91C1C", "ink": "000000", "muted": "27272A", @@ -69,7 +105,62 @@ "font": "Calibri", "tint": "FEE2E2", "tint_alt": "F3F4F6", - "accent_alt": "111827" + "accent_alt": "111827", + "style": { + "title": "full", + "section": "band", + "heading": "band", + "callout": "card", + "question": "spotlight", + "footer": true, + "takeaway": true + } + }, + { + "id": "grand-rounds", + "name": "Grand Rounds", + "description": "For a lecture theatre: deep navy title and section pages with big numbers, page numbers, striped callouts, key points under each slide.", + "ink": "0F172A", + "muted": "475569", + "accent": "1E3A8A", + "accent_alt": "B45309", + "rule": "CBD5E1", + "paper": "FFFFFF", + "tint": "E0E7FF", + "tint_alt": "FEF3C7", + "font": "Calibri", + "style": { + "title": "full", + "section": "band", + "heading": "bar", + "callout": "stripe", + "question": "spotlight", + "footer": true, + "takeaway": true + } + }, + { + "id": "board-review", + "name": "Board Review", + "description": "Built around checking yourself: question panels with lettered options and an answer slide, a key point under every slide, headings on bands.", + "ink": "1F2937", + "muted": "6B7280", + "accent": "7C3AED", + "accent_alt": "059669", + "rule": "E5E7EB", + "paper": "FFFFFF", + "tint": "F3E8FF", + "tint_alt": "D1FAE5", + "font": "Calibri", + "style": { + "title": "split", + "section": "band", + "heading": "band", + "callout": "card", + "question": "panel", + "footer": true, + "takeaway": true + } } ] -} +} \ No newline at end of file diff --git a/docs/my-resources.md b/docs/my-resources.md index 0189d46c..c0be160f 100644 --- a/docs/my-resources.md +++ b/docs/my-resources.md @@ -172,6 +172,34 @@ falls back to asking for markdown; a malformed slide degrades to bullets; a comparison with one column is not a comparison; JSON wrapped in fences or a covering sentence is read rather than refused. +## Templates + +A template is a theme plus a layout style, all in `assets/deck-themes.json` +and all drawn by `scripts/render_pptx.py` — nothing about the look depends on +the model, which only ever writes the slide content. Seven ship: + +| Template | What it does beyond its colours | +|---|---| +| Clinical Blue | Plain title page, rule under headings, amber callout card, key-point strips, question panels | +| Teaching Amber | Split title page, numbered colour section pages, accent bar beside headings, striped callouts | +| Ward Teal | Page numbers on every slide, questions on a dark spotlight slide | +| Slate | Monochrome; striped callouts, page numbers, no key-point strips | +| High Contrast | Full-colour title and section pages, headings on bands, spotlight questions | +| Grand Rounds | Deep navy title and numbered section pages, striped callouts, key points, page numbers | +| Board Review | Built around checking yourself: question panels, an answer slide after each, a key point under every slide | + +The `style` block of a theme has seven keys, each with the value the renderer +always had as its default: `title` (`band` / `full` / `split`), `section` +(`quiet` / `band`), `heading` (`rule` / `bar` / `band`), `callout` (`card` / +`stripe`), `question` (`panel` / `spotlight`), `footer` and `takeaway` +(booleans). Two things the content can carry for the templates to draw: a +`question` slide (`question`, lettered `options`, an `answer` and its +`explanation`, drawn as a "?" slide followed by an answer slide) and a +`takeaway` line on any content slide, drawn as a key-point strip under the +body where the template allows it and carried into the Word and PDF exports +as a bold closing line either way. The sample deck under every theme shows +all of it. + ## Illustrations `resourceImages.js`, **not** the shared `imageTool.dispatch` — that one permits diff --git a/scripts/__pycache__/render_pptx.cpython-313.pyc b/scripts/__pycache__/render_pptx.cpython-313.pyc index 06a570e296edc46e2f214bbc76640e1494cf4617..9f804af028ad11248103b3ad49a3107a7620fc0d 100644 GIT binary patch delta 22022 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 J@2#N${(n;oj(`9F delta 9468 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 F_ ' + slide.notes.replace(/\n/g, ' ')); out.push(''); } + if (slide.takeaway) { out.push('**Key point:** ' + slide.takeaway); out.push(''); } }); return out.join('\n').replace(/\n{3,}/g, '\n\n').trim() + '\n'; } diff --git a/src/utils/docSpec.js b/src/utils/docSpec.js index 78972e05..8db7f5f1 100644 --- a/src/utils/docSpec.js +++ b/src/utils/docSpec.js @@ -27,6 +27,13 @@ function fromDeck(deck, images) { if (slide.type === 'callout' && slide.text) { blocks.push({ type: 'callout', text: text(slide.text) }); + } else if (slide.type === 'question' && slide.question) { + var letters = 'ABCDEFGH'; + blocks.push({ type: 'callout', text: 'Check yourself: ' + text(slide.question) }); + if ((slide.options || []).length) { + blocks.push({ type: 'bullets', items: slide.options.map(function (o, i) { return { text: letters[i] + '. ' + text(o, 300), level: 0 }; }) }); + } + if (slide.answer) blocks.push({ type: 'callout', text: 'Answer: ' + text(slide.answer) + (slide.explanation ? ' — ' + text(slide.explanation) : '') }); } else if (slide.type === 'table') { blocks.push({ type: 'table', header: (slide.header || []).map(function (c) { return text(c, 300); }), rows: (slide.rows || []).map(function (row) { diff --git a/test/deck-theme-sample.test.js b/test/deck-theme-sample.test.js index 40716c04..ed59fb96 100644 --- a/test/deck-theme-sample.test.js +++ b/test/deck-theme-sample.test.js @@ -14,8 +14,7 @@ const deckSchema = require('../src/utils/deckSchema'); // The vocabulary the renderer can draw, from the schema itself, so a layout // added later fails this until the sample shows it too. -const SCHEMA_TYPES = ['title', 'section', 'bullets', 'two', 'compare', 'table', - 'callout', 'figure', 'image', 'custom', 'flow']; +const SCHEMA_TYPES = ['title', 'section', 'bullets', 'two', 'compare', 'table', 'callout', 'question', 'figure', 'image', 'custom', 'flow']; test('the sample shows every layout the renderer can draw', () => { const deck = deckSample.build({ id: 'clinical-blue', name: 'Clinical Blue' });