<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Python-Pptx on Nick Liu - Software Engineer</title><link>https://nick-liu.com/tags/python-pptx/</link><description>Recent content in Python-Pptx on Nick Liu - Software Engineer</description><generator>Hugo -- gohugo.io</generator><language>en</language><managingEditor>nickboy@users.noreply.github.com (Nick Liu)</managingEditor><webMaster>nickboy@users.noreply.github.com (Nick Liu)</webMaster><copyright>2026 Nick Liu</copyright><lastBuildDate>Sun, 02 Aug 2026 10:54:51 -0700</lastBuildDate><atom:link href="https://nick-liu.com/tags/python-pptx/index.xml" rel="self" type="application/rss+xml"/><item><title>Do not restyle a deck to look official. Wipe the template and keep its masters.</title><link>https://nick-liu.com/posts/python-pptx-official-template/</link><pubDate>Sat, 08 Aug 2026 00:00:00 +0000</pubDate><author>nickboy@users.noreply.github.com (Nick Liu)</author><guid>https://nick-liu.com/posts/python-pptx-official-template/</guid><description>&lt;div class="lead text-neutral-500 dark:text-neutral-400 !mb-9 text-xl"&gt;
 
The last deliverable of my summer project was a slide deck in the official university template. I generate slides from Python, and my first instinct was to rebuild the branding by hand: sample the navy, find the fonts, redraw the footer. That path produces decks that look almost right, which is worse than wrong. The trick that works is to load the official .pptx itself, delete every sample slide while keeping the slide masters, and add my own slides on the official layouts. Backgrounds, fonts, and logo come along for free.

&lt;/div&gt;

&lt;nav class="series-progress" aria-label="Series progress"&gt;
 &lt;span class="series-progress-label"&gt;Auditing AI-Solvability · Part 5 of 5&lt;/span&gt;
 &lt;ol class="series-progress-dots"&gt;&lt;li class="series-progress-dot is-done"&gt;
 &lt;a href="https://nick-liu.com/posts/ai-resistant-cs1-problems/" title="I went looking for AI-resistant CS1 problems. The search came up empty."&gt;1&lt;/a&gt;
 &lt;/li&gt;&lt;li class="series-progress-dot is-done"&gt;
 &lt;a href="https://nick-liu.com/posts/eval-harness-honesty-rules/" title="A crash is never a pass. Three rules that kept 1,095 eval runs honest."&gt;2&lt;/a&gt;
 &lt;/li&gt;&lt;li class="series-progress-dot is-done"&gt;
 &lt;a href="https://nick-liu.com/posts/cohens-kappa-zero-agreement/" title="Cohen&amp;#39;s kappa came back 0.00. The agreement was 6 out of 8."&gt;3&lt;/a&gt;
 &lt;/li&gt;&lt;li class="series-progress-dot is-done"&gt;
 &lt;a href="https://nick-liu.com/posts/obfuscated-spec-zero-width-characters/" title="I hid zero-width characters in a CS1 spec. The model did not blink."&gt;4&lt;/a&gt;
 &lt;/li&gt;&lt;li class="series-progress-dot is-current"&gt;
 &lt;a href="https://nick-liu.com/posts/python-pptx-official-template/" title="Do not restyle a deck to look official. Wipe the template and keep its masters." aria-current="step"&gt;5&lt;/a&gt;
 &lt;/li&gt;&lt;/ol&gt;
 &lt;/nav&gt;
&lt;p&gt;The generator is one Python file, roughly 470 lines of python-pptx, and it built the final presentation for the auditing project from &lt;a href="https://nick-liu.com/posts/ai-resistant-cs1-problems/" &gt;part 1&lt;/a&gt;. This post is about the two things in it worth stealing and the one bug worth laughing at.&lt;/p&gt;</description><content:encoded>&lt;div class="lead text-neutral-500 dark:text-neutral-400 !mb-9 text-xl"&gt;
 
The last deliverable of my summer project was a slide deck in the official university template. I generate slides from Python, and my first instinct was to rebuild the branding by hand: sample the navy, find the fonts, redraw the footer. That path produces decks that look almost right, which is worse than wrong. The trick that works is to load the official .pptx itself, delete every sample slide while keeping the slide masters, and add my own slides on the official layouts. Backgrounds, fonts, and logo come along for free.

&lt;/div&gt;

&lt;nav class="series-progress" aria-label="Series progress"&gt;
 &lt;span class="series-progress-label"&gt;Auditing AI-Solvability · Part 5 of 5&lt;/span&gt;
 &lt;ol class="series-progress-dots"&gt;&lt;li class="series-progress-dot is-done"&gt;
 &lt;a href="https://nick-liu.com/posts/ai-resistant-cs1-problems/" title="I went looking for AI-resistant CS1 problems. The search came up empty."&gt;1&lt;/a&gt;
 &lt;/li&gt;&lt;li class="series-progress-dot is-done"&gt;
 &lt;a href="https://nick-liu.com/posts/eval-harness-honesty-rules/" title="A crash is never a pass. Three rules that kept 1,095 eval runs honest."&gt;2&lt;/a&gt;
 &lt;/li&gt;&lt;li class="series-progress-dot is-done"&gt;
 &lt;a href="https://nick-liu.com/posts/cohens-kappa-zero-agreement/" title="Cohen&amp;#39;s kappa came back 0.00. The agreement was 6 out of 8."&gt;3&lt;/a&gt;
 &lt;/li&gt;&lt;li class="series-progress-dot is-done"&gt;
 &lt;a href="https://nick-liu.com/posts/obfuscated-spec-zero-width-characters/" title="I hid zero-width characters in a CS1 spec. The model did not blink."&gt;4&lt;/a&gt;
 &lt;/li&gt;&lt;li class="series-progress-dot is-current"&gt;
 &lt;a href="https://nick-liu.com/posts/python-pptx-official-template/" title="Do not restyle a deck to look official. Wipe the template and keep its masters." aria-current="step"&gt;5&lt;/a&gt;
 &lt;/li&gt;&lt;/ol&gt;
 &lt;/nav&gt;
&lt;p&gt;The generator is one Python file, roughly 470 lines of python-pptx, and it built the final presentation for the auditing project from &lt;a href="https://nick-liu.com/posts/ai-resistant-cs1-problems/" &gt;part 1&lt;/a&gt;. This post is about the two things in it worth stealing and the one bug worth laughing at.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Wiping slides without leaving corpses
 &lt;div id="wiping-slides-without-leaving-corpses" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#wiping-slides-without-leaving-corpses" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;python-pptx will happily open the official template as a presentation. The sample slides then need to go, and this is where the subtlety lives:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;wipe_slides&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;lst&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;prs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;slides&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_sldIdLst&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;sldId&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lst&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;prs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;drop_rel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sldId&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;qn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;r:id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="c1"&gt;# drop rel too, or you get orphan parts&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;lst&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sldId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;A .pptx is a zip of XML parts wired together by relationships. Removing a slide&amp;rsquo;s id from the slide list makes it disappear from the deck, but the underlying part is still referenced by a relationship from the presentation part. Skip the &lt;code&gt;drop_rel&lt;/code&gt; line and the file still opens, which is the trap: you ship a package carrying orphaned parts, and whether anything downstream chokes on them is up to tooling you do not control. Drop the relationship and the id together and the package stays clean.&lt;/p&gt;
&lt;p&gt;What survives the wipe is the point. Slide masters and layouts are separate parts, untouched by this loop. Every slide I add afterwards goes onto an official layout, which means the background art, the footer, the logo placement, and the typography all come from the university&amp;rsquo;s own files. Nothing to sample, nothing to eyeball, nothing to drift out of date when the brand team ships a new template. Load, wipe, rebuild.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Colors from the theme, not from the eyedropper
 &lt;div id="colors-from-the-theme-not-from-the-eyedropper" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#colors-from-the-theme-not-from-the-eyedropper" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;The template&amp;rsquo;s theme defines its palette, so the constants in my file are transcriptions with their theme slots noted, not guesses from a color picker:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;NAVY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RGBColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0x00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x57&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Tech Navy (dk1)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;GOLD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RGBColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xB3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0xA3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x69&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Tech Gold (accent1)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;GREY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RGBColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0x54&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x58&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x59&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Grey Matter (dk2)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;BODYFONT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Roboto&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;HEADFONT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Roboto Slab&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The comments carry the important part. When a shape needs explicit coloring, the value traces back to a named slot in the official theme, so a reviewer can check it and a future me can update it.&lt;/p&gt;

&lt;h2 class="relative group"&gt;The eight-inch hexagon gap
 &lt;div id="the-eight-inch-hexagon-gap" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-eight-inch-hexagon-gap" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Now the bug. Content slides lay out a row of hexagons, and the gap between them was computed from the full slide width:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# before (buggy): with n=2 this gives gap = 8.03 inches&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;gap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;13.33&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;0.9&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The formula spreads n hexagons across the entire 13.33 inch slide. With five hexagons that looks intentional. With two, the leftover width all becomes gap: 8.03 inches of it, so the two hexagons were flung to opposite edges of the slide with a hole in the middle, like a divorce rendered in vector graphics. The layout code was not wrong for the case I wrote it for. It was wrong for the case I never tested.&lt;/p&gt;
&lt;figure&gt;&lt;picture&gt;
 &lt;source
 type="image/webp"
 srcset="https://nick-liu.com/img/hexagons-before_hu_4229ac7a59650f34.webp 800w, https://nick-liu.com/img/hexagons-before_hu_ff2f0b0d634b4630.webp 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="A slide titled &amp;ldquo;Two stats, one row&amp;rdquo; where two hexagons sit pinned to the far left and far right edges with an 8-inch empty gap between them"
 width="1600"
 height="899"
 src="https://nick-liu.com/img/hexagons-before_hu_c28175d57b91d310.png"
 srcset="https://nick-liu.com/img/hexagons-before_hu_c28175d57b91d310.png 800w, https://nick-liu.com/img/hexagons-before_hu_24f5a45b9445d1a1.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="https://nick-liu.com/img/hexagons-before.png"&gt;
 &lt;/picture&gt;&lt;/figure&gt;
&lt;p&gt;That render is real: for this post I re-ran the generator with the buggy formula restored and two placeholder stats, and the template did exactly what it did in June.&lt;/p&gt;
&lt;p&gt;The fix abandons full-width justification for a fixed gap with true centering, and draws the hexagons larger when there are only one or two, since a sparse row can afford the space:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# after&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;2.9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2.35&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;2.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.95&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2.55&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;gap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;x0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;13.33&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;gap&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;figure&gt;&lt;picture&gt;
 &lt;source
 type="image/webp"
 srcset="https://nick-liu.com/img/hexagons-after_hu_3c306ef68c25aa10.webp 800w, https://nick-liu.com/img/hexagons-after_hu_4081f5fad673f547.webp 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="The same slide after the fix: two larger hexagons grouped side by side in the center of the slide"
 width="1600"
 height="899"
 src="https://nick-liu.com/img/hexagons-after_hu_661be3810963a239.png"
 srcset="https://nick-liu.com/img/hexagons-after_hu_661be3810963a239.png 800w, https://nick-liu.com/img/hexagons-after_hu_cf6738dad5402b0e.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="https://nick-liu.com/img/hexagons-after.png"&gt;
 &lt;/picture&gt;&lt;/figure&gt;
&lt;p&gt;Same slide source, current code. Justified layout distributes leftover space, and leftover space grows as items shrink in number. Any layout formula that divides remaining width by item count has this failure mode waiting at n=2 and n=1. Centered layout with a fixed gap degrades gracefully instead.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Lessons
 &lt;div id="lessons" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#lessons" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Inherit branding, never imitate it. Load the official file, wipe its slides, keep its masters, and build on its layouts.&lt;/li&gt;
&lt;li&gt;When you remove a part from an OOXML package, remove its relationship in the same breath. A file that still opens is not evidence the package is clean.&lt;/li&gt;
&lt;li&gt;Hardcoded colors are debt unless each one names the theme slot it came from.&lt;/li&gt;
&lt;li&gt;Justified layouts fail at low item counts because leftover space has nowhere to go but the gaps. Test layout math at n=1 and n=2, not only at the count in your mockup.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That closes the series. The auditor, the harness rules, the degenerate kappa, the failed sabotage, and the deck it was all presented on. The run records stay stamped with model and date, so when someone reruns these problems against next year&amp;rsquo;s models, disagreement with my numbers will be a finding and not a contradiction.&lt;/p&gt;

&lt;h2 class="relative group"&gt;References
 &lt;div id="references" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#references" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://python-pptx.readthedocs.io/" target="_blank" rel="noreferrer"&gt;python-pptx documentation&lt;/a&gt; (presentations, slide masters, and layout objects)&lt;/li&gt;
&lt;li&gt;Code shown above: &lt;code&gt;gt-slides/build_deck.py&lt;/code&gt; in the project repo, excerpted verbatim&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>