You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Default decks to light navy-band style; add math rendering and thesis-defence authoring
- Flip ExportOptions.dark_mode default to False: the project deck is now
the light navy-band style (white slides, navy header band, navy cover),
with --dark-mode / GUI checkbox opting into the dark palette.
- Render inline $...$ math as real subscripts/superscripts in pptx.
- Add the thesis-deck-author task agent for oral-defence decks built from
the candidate's own thesis, plus post-author-audit math/metadata passes.
- Add dark-text and overflow audit scripts with regression tests.
- Sync rule docs, CLI/GUI/MCP surfaces, and Sphinx docs to the new default.
Copy file name to clipboardExpand all lines: .claude/agents/rules/deck-design.md
+53-40Lines changed: 53 additions & 40 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -48,23 +48,25 @@ Already pinned in `pptx.py`:
48
48
| Constant | RGB | Use |
49
49
|---|---|---|
50
50
|`_BRAND_DARK`|`#1F3A66` (deep navy) | Primary text + accent bar |
51
-
|`_BRAND_HIGHLIGHT`|`#0E7490` (teal-700) | Text emphasis — KPI values, RQ question callout, "this stands out" headlines. The sanctioned replacement for the banned red accent. |
51
+
|`_BRAND_HIGHLIGHT`|`#2563EB` (academic blue-600) | Text emphasis — KPI values, RQ question callout, "this stands out" headlines. The sanctioned replacement for the banned red accent. Same blue family as `_BRAND_DARK` navy for a cohesive white + blue academic look. |
52
52
|`_BRAND_ACCENT`|`#C0392B` (warm red) | BANNED for text (see "No red text" contract). Reserved for potential future non-text accent shapes only. |
53
53
|`_BRAND_GREY`|`#555555`| Metadata, captions, secondary text, placeholder/error states |
54
54
|`_BRAND_LIGHT`|`#AAAAAA`| Rule lines, dividers |
55
55
56
56
Do NOT introduce new brand colours casually — every additional colour
57
57
fights for attention. Reuse the five above unless the user explicitly
58
-
adds one. Note the deliberate split: **teal is the headline emphasis,
59
-
grey is the label/chrome emphasis** — picking the wrong one (e.g. teal
58
+
adds one. Note the deliberate split: **blue is the headline emphasis,
59
+
grey is the label/chrome emphasis** — picking the wrong one (e.g. blue
60
60
for a figure caption) makes captions compete with KPIs for the eye.
|`_BRAND_HIGHLIGHT` text |`#0E7490` → `#2DD4BF`|Teal-700 → teal-400; brighter teal reads on the dark slide bg without losing the accent identity |
80
+
|`_BRAND_HIGHLIGHT` text |`#2563EB` → `#60A5FA`|Blue-600 → blue-400, brighter blue reads on the dark slide bg without losing the accent identity |
79
81
|`_BRAND_ACCENT`|`#C0392B` (unchanged) | BANNED for text in both modes (see "No red text" contract). If reused for a non-text shape, kept as-is for brand consistency. |
80
82
|`_BRAND_DARK` fill (accent bars / table header) |`#1F3A66` → `#3B5AA0`| Lighter navy reads against the dark slide background |
81
83
|`_TABLE_ROW_ALT`|`#F4F6F9` → `#1F232C`| Dark stripe |
@@ -141,9 +143,9 @@ manual inspection of a single rendered deck.
141
143
constant ``_BRAND_ACCENT`` (= ``#C0392B`` warm red) stays in the palette
142
144
for potential future non-text accent shapes (sparkline highlight,
143
145
status badge, etc.), but every TEXT call site has been migrated off it.
144
-
The sanctioned text-emphasis colour is **``_BRAND_HIGHLIGHT`` (teal-700,
145
-
``#0E7490``)** — pair with ``run.font.bold = True``. Use ``_BRAND_GREY``
146
-
for chrome / label / placeholder emphasis (never teal — teal is reserved
146
+
The sanctioned text-emphasis colour is **``_BRAND_HIGHLIGHT`` (academic
147
+
blue-600, ``#2563EB``)** — pair with ``run.font.bold = True``. Use ``_BRAND_GREY``
148
+
for chrome / label / placeholder emphasis (never blue — blue is reserved
147
149
for "this matters", grey is for "this is context").
148
150
149
151
Why banned:
@@ -164,8 +166,8 @@ is closest at hand. The four migrated sites split:
164
166
165
167
| Call site | Role | Replacement |
166
168
|---|---|---|
167
-
| KPI value (`_add_kpi_lines`) | "the slide's punch line" headline |`_BRAND_HIGHLIGHT` (teal) |
168
-
| RQ question (`_add_rq_result_slide`) | "the question being answered" headline |`_BRAND_HIGHLIGHT` (teal) |
169
+
| KPI value (`_add_kpi_lines`) | "the slide's punch line" headline |`_BRAND_HIGHLIGHT` (blue) |
170
+
| RQ question (`_add_rq_result_slide`) | "the question being answered" headline |`_BRAND_HIGHLIGHT` (blue) |
- Fill: `_BRAND_DARK` solid; name `accent_left` (kept for the cover-accent
291
+
semantic name even though it now spans the whole slide).
292
+
- Cover title is WHITE; subtitle / meta are near-white (`_DARK_BODY_TEXT`) —
293
+
these light colours are correct in BOTH modes because the cover stays
294
+
navy either way.
282
295
283
296
Section-divider slides may use a larger top band (`height=Inches(0.6)`)
284
297
with the section title overlaid in light text — but this is optional
@@ -314,26 +327,26 @@ provided every slide ends up with:
314
327
315
328
The exporter inserts figures as PNGs via the `figures=` field (`_add_figure_image`); it does **not** draw native charts. So figure *quality* is an authoring responsibility — a default-matplotlib plot or a low-res screenshot undoes the brand discipline the rest of the deck earns.
316
329
317
-
-**Dark-mode adaptation is mandatory.**The slide background is `_DARK_SLIDE_BG` (`#12151B`) by default. A white-background PNG dropped onto it shows a glaring white rectangle — the figure equivalent of `rgb=None` text on dark. Export plots with a **transparent background** (`savefig(..., transparent=True)`) and light foreground (axes / labels / lines in near-white or brand teal`#2DD4BF`), OR place the figure on a card whose fill has a `_LIGHT_TO_DARK_FILL` entry. Never a bare white PNG on the dark slide.
330
+
-**Dark-mode adaptation is mandatory when the deck is rendered dark.**In dark mode the slide background is `_DARK_SLIDE_BG` (`#12151B`). A white-background PNG dropped onto it shows a glaring white rectangle — the figure equivalent of `rgb=None` text on dark. Export plots with a **transparent background** (`savefig(..., transparent=True)`) and light foreground (axes / labels / lines in near-white or brand blue`#60A5FA`), OR place the figure on a card whose fill has a `_LIGHT_TO_DARK_FILL` entry. Never a bare white PNG on the dark slide.
318
331
-**Strip chartjunk.** No default matplotlib grey panel, no spines on all four sides, no dense gridlines, no 3-D bars / pies, no drop shadows. Top + right spines off, at most one light horizontal gridline set. Data-ink first.
319
-
-**Brand palette, not library defaults.** Series colours come from the deck palette (navy / teal / grey), never matplotlib's `C0` blue / `C1` orange — default colours read as "pasted from a notebook". (Red stays banned here too, per the no-red contract.)
332
+
-**Brand palette, not library defaults.** Series colours come from the deck palette (navy / blue / grey), never matplotlib's `C0` blue / `C1` orange — default colours read as "pasted from a notebook". (Red stays banned here too, per the no-red contract.)
320
333
-**Don't encode meaning by colour alone.** Teal vs navy is hard for some colour-blind viewers and *indistinguishable* in a black-and-white printout. When two series must be told apart, encode them twice — colour **plus** a marker shape / line style (solid vs dashed) or a direct end-of-line label. The winning series can also be the only solid/heavy one. (The 4-colour brand palette is small precisely so it can't carry many simultaneous distinctions — lean on shape and labels.)
321
334
-**Readable when projected.** Axis labels + tick labels + legend ≥ ~14pt *in the rendered figure* (a 6pt matplotlib label is unreadable from row 10). Label every axis with its quantity AND unit ("Latency (ms)"), per `paper_rule`'s number-reporting rule.
322
335
-**Export at print DPI.**`dpi >= 150` (200 for line-heavy plots). A 72-DPI screenshot pixelates on a projector.
323
336
-**Paper screenshots are a last resort.** Re-plotting your own data beats screenshotting the paper's figure — a screenshot carries the paper's off-brand fonts / colours, JPEG artefacts, and usually a white background. Crop tightly; only screenshot when re-plotting is impossible (e.g. a qualitative architecture diagram).
324
337
325
-
**Anti-pattern:**`plt.savefig("fig.png")` with defaults → grey panel, blue/orange series, 6pt labels, white border, dropped onto the dark slide. **Pattern:**`savefig("fig.png", dpi=200, transparent=True, bbox_inches="tight")` with teal / navy series, 14pt labels, top + right spines removed.
338
+
**Anti-pattern:**`plt.savefig("fig.png")` with defaults → grey panel, blue/orange series, 6pt labels, white border, dropped onto the dark slide. **Pattern:**`savefig("fig.png", dpi=200, transparent=True, bbox_inches="tight")` with blue / navy series, 14pt labels, top + right spines removed.
326
339
327
340
### Visual hierarchy & focal point
328
341
329
342
Each slide needs one element the eye lands on first — the takeaway from `slide-deck-rules` §9. Size, weight, colour and position build that hierarchy; without it every element competes and the audience reads top-to-bottom hunting for the point.
330
343
331
-
-**One focal point per slide.** The biggest / boldest / most-saturated element *is* the takeaway — usually the KPI value (teal, bold, large) or the winning row of a table. Exactly one.
332
-
-**Hierarchy by size, not just order.** Title > headline number > evidence > caption, each visibly smaller. A KPI value at the same size as its label has no hierarchy. Caption / provenance text uses `_BRAND_GREY` so it recedes — the palette already encodes this (teal emphasises, grey recedes); don't invert it.
344
+
-**One focal point per slide.** The biggest / boldest / most-saturated element *is* the takeaway — usually the KPI value (blue, bold, large) or the winning row of a table. Exactly one.
345
+
-**Hierarchy by size, not just order.** Title > headline number > evidence > caption, each visibly smaller. A KPI value at the same size as its label has no hierarchy. Caption / provenance text uses `_BRAND_GREY` so it recedes — the palette already encodes this (blue emphasises, grey recedes); don't invert it.
333
346
-**Whitespace is not wasted space.** A slide filled edge-to-edge has no focal point. Leave margins and let the KPI block breathe. The `FOOTER_GUARD` (7.05") and per-slide content caps exist partly so content can't sprawl across the whole canvas.
334
347
-**Reading order follows the layout.** Assertion title on top, evidence beneath it, provenance / caption last. Don't bury the conclusion in a footnote while the setup sits in the headline.
335
348
336
-
**Anti-pattern:** title, three KPIs, a table and a caption all the same size and colour — no focal point, the eye wanders. **Pattern:** one KPI value ~2× the size of its label in teal, the table muted beneath it, caption small and grey.
349
+
**Anti-pattern:** title, three KPIs, a table and a caption all the same size and colour — no focal point, the eye wanders. **Pattern:** one KPI value ~2× the size of its label in blue, the table muted beneath it, caption small and grey.
0 commit comments