rewrite: refresh all blog posts for public audience

Merged overlapping posts:
- forking-gitea + fork-dont-build → one post about the fork philosophy
- fal-image-generation + image-generation-fal → one post about AI art pipeline

Rewrote all posts with external/public voice:
- Stronger hooks, concrete examples, punchier language
- agentic-workflow: restructured around soul files + skills + numbers
- agent-harness: clearer framing of 'what an agent harness is'
- cloud-harness: tighter narrative about overnight agents
- godot-optimisation: same depth, sharper opening
- pre-commit-agent: clearer architecture, cost breakdown
- studio-cli: reframed around identity/cold-start problem
- blog-visual-upgrade: tightened the restyle story

10 posts total (9 markdown + 1 hand-authored HTML)
This commit is contained in:
2026-06-03 03:06:41 +01:00
parent bf42a76cf9
commit f01036c646
23 changed files with 933 additions and 2546 deletions
+41 -174
View File
@@ -4,27 +4,27 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Our Blog Just Got a Visual Upgrade — Here's How We Did It — Tinqs Blog</title>
<meta name="description" content="We gave the Tinqs blog a visual refresh, borrowing the dark, gradient-heavy aesthetic from our internal team guides. No external CSS tinkering, no framework, no build-step drift — just two template files and a Node script.">
<title>How We Restyled Our Blog with Two Template Files and Zero Dependencies — Tinqs Blog</title>
<meta name="description" content="Gradient titles, dark code panels, amber callouts — we gave the Tinqs blog a visual refresh borrowing our internal team guide aesthetic. Two template files, one Node script, no framework.">
<meta name="robots" content="index, follow">
<link rel="canonical" href="https://www.tinqs.com/blog/blog-visual-upgrade">
<meta property="og:type" content="article">
<meta property="og:url" content="https://www.tinqs.com/blog/blog-visual-upgrade">
<meta property="og:title" content="Our Blog Just Got a Visual Upgrade — Here's How We Did It">
<meta property="og:description" content="How we restyled the Tinqs blog with a gradient-first, dark-code-panel aesthetic — using only two template files and a markdown build script.">
<meta property="og:title" content="How We Restyled Our Blog with Two Template Files and Zero Dependencies">
<meta property="og:description" content="Blog restyle: gradient titles, dark code panels, amber callouts — two template files, zero deps.">
<meta property="og:image" content="https://www.tinqs.com/img/og-cover.jpg">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Our Blog Just Got a Visual Upgrade — Here's How We Did It">
<meta name="twitter:description" content="How we restyled the Tinqs blog with a gradient-first, dark-code-panel aesthetic — using only two template files and a markdown build script.">
<meta name="twitter:title" content="How We Restyled Our Blog with Two Template Files and Zero Dependencies">
<meta name="twitter:description" content="Blog restyle: gradient titles, dark code panels, amber callouts — two template files, zero deps.">
<meta name="twitter:image" content="https://www.tinqs.com/img/og-cover.jpg">
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BlogPosting",
"headline": "Our Blog Just Got a Visual Upgrade — Here's How We Did It",
"headline": "How We Restyled Our Blog with Two Template Files and Zero Dependencies",
"datePublished": "2026-06-03",
"author": {
"@type": "Person",
@@ -35,7 +35,7 @@
"name": "Tinqs Limited",
"url": "https://www.tinqs.com"
},
"description": "We gave the Tinqs blog a visual refresh, borrowing the dark, gradient-heavy aesthetic from our internal team guides. No external CSS tinkering, no framework, no build-step drift — just two template files and a Node script."
"description": "Gradient titles, dark code panels, amber callouts — we gave the Tinqs blog a visual refresh borrowing our internal team guide aesthetic. Two template files, one Node script, no framework."
}
</script>
@@ -218,182 +218,49 @@
<article class="post">
<a href="/blog/" class="post__back">&larr; All Posts</a>
<span class="post__date">3 June 2026</span>
<h1 class="post__title">Our Blog Just Got a Visual Upgrade — Here's How We Did It</h1>
<p class="post__lead">Until yesterday, the Tinqs blog looked... fine. Readable. Semantic. It had the brand amber accent, proper typography, and all the SEO metadata in the right places. But it didn't have much <em>personality</em>. The code blocks were unstyled. The headings sat flat. And the design said "competent" more than "intentional".</p>
<h1 class="post__title">How We Restyled Our Blog with Two Template Files and Zero Dependencies</h1>
<p class="post__lead">Our blog looked fine. Readable, semantic, proper typography. But it didn't have much personality. Code blocks were unstyled. Headings sat flat. The design said "competent" more than "intentional."</p>
<div class="post__body">
<p>Then we looked at our own internal team guide — the onboarding doc we keep at <code>docs/team/dev-basics-env-secrets-git.html</code>. It had gradient titles that clip to transparent. Dark, crisp code panels. Callout boxes with coloured left borders. Pill-shaped date labels. A restrained four-colour palette that felt cohesive without screaming.</p>
<p>We wanted the blog to feel like it came from the same shop. So we restyled it.</p>
<h2>The design source</h2>
<p>Our team guide is a single self-contained HTML file with a dark theme — background <code>#0d1117</code>, panels <code>#161b22</code>, ink <code>#e6edf3</code>. It uses a four-accent palette:</p>
<ul>
<li><strong>Green</strong> <code>#22c55e</code> — for <code>.env</code> and environment topics</li>
<li><strong>Blue</strong> <code>#38bdf8</code> — secondary links, kickers, syntax table headers</li>
<li><strong>Purple</strong> <code>#a855f7</code> — git topics, hover states</li>
<li><strong>Amber</strong> <code>#f59e0b</code> — warnings, emphasis, callouts</li>
</ul>
<p>The title is the star: an <code>h1</code> filled with a <code>linear-gradient</code> across all four colours, clipped to the text via <code>-webkit-background-clip: text</code>. Code blocks live in dark <code>#0a0e14</code> panels with <code>#2a3340</code> borders. Blockquotes become amber-tinted callouts. The whole thing radiates a "well-maintained developer doc" energy without feeling like a Bootstrap template.</p>
<p>We wanted that energy on the public-facing blog.</p>
<h2>The build system (and why it mattered)</h2>
<p>The blog is generated by a zero-dependency Node script — <code>build.js</code> — that converts markdown posts into HTML. The pipeline is:</p>
<p>Then we looked at our internal team guide — a self-contained HTML doc with gradient titles that clip to transparent, dark code panels, and callout boxes with coloured borders. It radiated a "well-maintained developer doc" energy. We wanted the blog to feel like it came from the same shop.</p>
<p>Two template files, one build step, zero external dependencies. Here's what we changed.</p>
<h2>The build system (why it mattered)</h2>
<p>The blog is generated by <code>build.js</code> — a zero-dependency Node script that converts markdown to HTML:</p>
<pre><code>posts/*.md + _template.html / _index_template.html → *.html</code></pre>
<p>This means <strong>we never touch a generated <code>.html</code> file by hand</strong>. Every visual change flows through the two templates. <code>build.js</code> then stamps out all 11 posts plus the index in under a second.</p>
<p>The site-wide CSS — navigation, footer, base typography, the brand amber <code>&ndash;c-accent: #c9935a</code> — lives in <code>../style.css</code>, which is served by Git Studio from outside the blog repo. We deliberately <strong>did not</strong> touch that file. Instead, we injected a self-contained <code><style></code> block at the very end of <code><head></code> in both templates, after the <code>../style.css</code> link. Cascade order handles the overrides.</p>
<h2>What we changed</h2>
<h3>Post template (<code>_template.html</code>)</h3>
<p><strong>Gradient title.</strong> The post <code>h1</code> now gets the gradient treatment — amber <code>#c9935a</code> → warm gold <code>#f59e0b</code> → blue <code>#38bdf8</code>. It's the same technique as the team guide: <code>background-clip: text; color: transparent</code>. The underlying text is still there for screen readers and SEO.</p>
<p><strong>Date pill.</strong> The post date is now a monospace chip — blue text, <code>999px</code> border-radius, uppercase, tight letter-spacing. It sits before the title like a kicker.</p>
<p><strong>Code blocks.</strong> This was the biggest functional improvement. The site CSS only styled inline <code><code></code> — fenced code blocks inside <code><pre></code> had no styling at all. We added a dark panel (<code>#0a0e14</code> background, <code>#2a3340</code> border, <code>10px</code> radius, monospace, <code>overflow-x: auto</code>). A reset rule on <code>.post__body pre code</code> prevents the inline-code styles from doubling up inside the panel. Inline code got its own treatment: <code>#1c2230</code> background, green <code>#9fe6c0</code> text.</p>
<p><strong>Section cues.</strong> <code>h2</code> headings now have a <code>4px</code> amber left border as a visual anchor. <code>h3</code> headings get a subtle purple tint — enough to signal a section break without pulling focus.</p>
<p><strong>Links and emphasis.</strong> Body links are blue <code>#38bdf8</code> and shift to purple <code>#a855f7</code> on hover. Bold text picks up amber <code>#f59e0b</code>. Horizontal rules became a single dark <code>#2a3340</code> line.</p>
<p><strong>Blockquote callouts.</strong> We wrote the CSS for amber-tinted callout blockquotes — tinted background, <code>4px</code> amber left border, rounded right corners — but <code>build.js</code> doesn't emit <code><blockquote></code> yet. The rules are there, ready to activate when someone adds blockquote handling or swaps in a full markdown library.</p>
<h3>Index template (<code>_index_template.html</code>)</h3>
<p>The blog listing page got the same treatment, translated to its own selectors:</p>
<ul>
<li><code>.blog-header__title</code> — same amber→gold→blue gradient</li>
<li><code>.section-label</code> — a monospace kicker pill above the title</li>
<li><code>.blog-card__date</code> — date pill matching the post pages</li>
<li><code>.blog-card:hover</code> — border shifts to brand amber on hover</li>
<li><code>.blog-card__read</code> — blue link, purple on card hover</li>
</ul>
<h3>The palette at a glance</h3>
<p>This means we never touch a generated <code>.html</code> file by hand. Every visual change flows through the templates. The site-wide CSS — nav, footer, base typography, brand accent — lives in <code>../style.css</code>, served by Git Studio from outside the repo. We didn't touch it.</p>
<p>Instead, we injected a self-contained <code><style></code> block at the end of <code><head></code> in both templates, after the <code>../style.css</code> link. Cascade order handles the overrides. No <code>!important</code>. No external font loads. No CDN dependencies.</p>
<h2>The palette</h2>
<p>Four accent colours, borrowed from our team guide:</p>
<p>| Role | Colour | Where |</p>
<p>|&mdash;&mdash;|&mdash;&mdash;&ndash;|&mdash;&mdash;-|</p>
<p>| Brand anchor | <code>#c9935a</code> | Gradient start, h2 left bar, card hover |</p>
<p>| Brand amber | <code>#c9935a</code> | Gradient start, h2 left bar, card hover |</p>
<p>| Warm gold | <code>#f59e0b</code> | Gradient midpoint, bold text |</p>
<p>| Blue | <code>#38bdf8</code> | Gradient endpoint, links, date pills |</p>
<p>| Purple | <code>#a855f7</code> | h3 colour, link hover |</p>
<p>| Dark panel | <code>#0a0e14</code> | Code block background |</p>
<p>| Border | <code>#2a3340</code> | Code panels, hr, inline code |</p>
<p>Four accent colours. No rainbow.</p>
<p>Tasteful. Not a rainbow.</p>
<h2>What we styled</h2>
<p><strong>Gradient titles.</strong> Post <code>h1</code> gets <code>linear-gradient(90deg, #c9935a, #f59e0b 40%, #38bdf8)</code> via <code>background-clip: text; color: transparent</code>. Underlying text preserved for screen readers and SEO.</p>
<p><strong>Date pills.</strong> Monospace chip — blue text, <code>999px</code> border-radius, uppercase, tight letter-spacing. Sits before the title like a kicker.</p>
<p><strong>Code blocks.</strong> The site CSS only styled inline <code><code></code>. Fenced blocks got a dark panel (<code>#0a0e14</code>, <code>#2a3340</code> border, <code>10px</code> radius, monospace, <code>overflow-x: auto</code>). A reset rule on <code>pre code</code> prevents inline-code styles from doubling up inside the panel.</p>
<p><strong>Section cues.</strong> h2 gets a 4px amber left border as visual anchor. h3 gets a purple tint — enough to signal a section break without pulling focus.</p>
<p><strong>Links.</strong> Blue <code>#38bdf8</code>, purple <code>#a855f7</code> on hover. Bold text picks up amber.</p>
<p><strong>Blockquote callouts.</strong> Amber-tinted background, 4px amber left border, rounded right corners. The CSS is written and waiting — <code>build.js</code> doesn't emit <code><blockquote></code> yet, but the rules activate the moment we add <code>></code> syntax support.</p>
<p><strong>Index page.</strong> Same gradient on the listing title. Date pills on cards. Amber border on card hover. Blue/purple read links.</p>
<h2>The rebuild</h2>
<pre><code class="language-bash">cd ~/tinqs-ltd/blog &amp;&amp; node build.js</code></pre>
<pre><code>Building blog...
agent-harness.md → agent-harness.html
agentic-workflow.md → agentic-workflow.html
...
studio-cli.md → studio-cli.html
index.html (listing)
Done — 11 posts built.</code></pre>
<p>Zero errors. Every regenerated HTML file now carries the inline <code><style></code> block. We confirmed with <code>grep -l "background-clip" *.html</code> all 12 files (11 posts + index) ship the gradient.</p>
<h2>What we didn't change</h2>
<p>Navigation, footer, and site chrome are untouched. This was a CSS-only change — no markup was altered in either template beyond the <code><style></code> injection. The existing responsive behaviour is preserved. The blog still uses the same <code>IBM Plex Sans</code> font stack, the same SEO metadata, the same <code>build.js</code> pipeline.</p>
<h2>What's next</h2>
<p>The restyle is on a branch (<code>style/team-guide-aesthetic</code>) awaiting review. Once merged, the blog gets its new look with no deployment step beyond a <code>git push</code> — Git Studio picks it up automatically.</p>
<p>Two gaps we might address later:</p>
<p>1. <strong>Blockquote support in <code>build.js</code>.</strong> The callout CSS is written and waiting. Adding <code>></code> syntax to our markdown converter would let post authors drop amber callout boxes anywhere in a post.</p>
<p>2. <strong>Ordered lists.</strong> Same story — the CSS isn't written because <code>build.js</code> doesn't emit <code><ol></code> / <code><li></code> inside ordered lists yet. Both are one-function additions.</p>
<h2>The blog toolkit: a hands-on guide</h2>
<p>If you're going to write for the Tinqs blog — or tweak how it looks — here's everything you need to know, all in one place.</p>
<h3>Adding a new post</h3>
<p>Every post starts as a markdown file in <code>posts/</code>. The filename doesn't matter for routing (that's driven by the <code>slug</code> field), but we name them descriptively: <code>blog-visual-upgrade.md</code>, <code>pre-commit-agent.md</code>, etc.</p>
<p>The file has two parts: <strong>YAML frontmatter</strong> (metadata wrapped in <code>&mdash;</code>) and <strong>markdown body</strong> (everything after the second <code>&mdash;</code>).</p>
<p><strong>Frontmatter fields:</strong></p>
<pre><code class="language-yaml">---
title: "Post Title — with optional subtitle"
slug: url-friendly-slug
date: "2026-06-03"
description: "One-sentence summary for meta tags and SEO."
og_description: "Shorter version for social cards (optional — falls back to description)."
og_image: "https://www.tinqs.com/img/og-cover.jpg"
excerpt: "A teaser line shown on the blog index page."
author: "Ozan Bozkurt"
author_initials: "OB"
author_role: "CTO &amp; Developer, Tinqs"
---</code></pre>
<p>All fields are required except <code>og_description</code> and <code>og_image</code> (they have defaults). The <code>slug</code> becomes the filename on disk — <code>blog-visual-upgrade</code> produces <code>blog-visual-upgrade.html</code>.</p>
<p><strong>Markdown body:</strong> The first paragraph after frontmatter becomes the <strong>lead</strong> (shown above the fold on the post page). Everything after the first blank line is the body. <code>build.js</code> splits them automatically.</p>
<p>Once your <code>.md</code> file is ready:</p>
<pre><code class="language-bash">node build.js</code></pre>
<p>That regenerates the new post's HTML plus a fresh <code>index.html</code> with the updated card listing. No manual HTML editing, ever.</p>
<h3>The template handshake</h3>
<p>The blog uses two Handlebars-style templates (actually plain string replacement — no library needed):</p>
<p>| File | Role | Key placeholders |</p>
<p>|&mdash;&mdash;|&mdash;&mdash;|&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;|</p>
<p>| <code>_template.html</code> | Wraps a single blog post | <code>{{TITLE}}</code>, <code>{{DATE_DISPLAY}}</code>, <code>{{LEAD}}</code>, <code>{{BODY}}</code>, <code>{{AUTHOR_*}}</code> |</p>
<p>| <code>_index_template.html</code> | Wraps the blog listing page | <code>{{CARDS}}</code> — replaced with an <code><a class="blog-card"></code> block per post |</p>
<p><code>build.js</code> reads both templates at startup, then for each post:</p>
<p>1. Parses the frontmatter + splits lead from body</p>
<p>2. Runs the markdown converter on the body (<code>md()</code> function)</p>
<p>3. Does <code>template.replace(/\{\{KEY\}\}/g, value)</code> for every placeholder</p>
<p>4. Writes the result to <code>{slug}.html</code></p>
<p>After all posts are built, it generates <code>index.html</code> by sorting posts newest-first and replacing <code>{{CARDS}}</code> with a block of <code>.blog-card</code> links.</p>
<p><strong>The critical rule:</strong> never edit a generated <code>*.html</code> file. They get overwritten on the next <code>node build.js</code>. Always change the templates or the markdown source.</p>
<h3>The three-layer styling architecture</h3>
<p>Styling has three layers, and they cascade in this order:</p>
<pre><code>1. ../style.css ← external, served by Git Studio (untouchable from this repo)
2. &lt;style&gt; in _template ← post-page overrides (inline, at end of &lt;head&gt;)
3. &lt;style&gt; in _index ← index-page overrides (inline, at end of &lt;head&gt;)</code></pre>
<p>Layer 1 provides the nav, footer, base typography, and the <code>&ndash;c-accent: #c9935a</code> variable. It also defines bare selectors like <code>.post__title</code>, <code>.post__date</code>, <code>.blog-card</code>, etc. — but with minimal styling.</p>
<p>Layers 2 and 3 are our <strong>self-contained inline <code><style></code> blocks</strong>. They sit AFTER the <code>../style.css</code> link in the <code><head></code>, so same-specificity rules win by cascade order. We never use <code>!important</code> the position handles precedence naturally.</p>
<p><strong>Why inline instead of a separate <code>.css</code> file?</strong> The blog repo is standalone — it doesn't control what Git Studio serves. Adding a file like <code>blog-style.css</code> would require coordinating a deploy to the parent site. Inline <code><style></code> blocks ship inside the generated HTML, so the blog is fully self-contained. One <code>git push</code> and it's live.</p>
<h3>The markdown dialect (what <code>build.js</code> understands)</h3>
<p>Our converter is intentionally minimal — zero dependencies, about 100 lines of Node. It handles the subset we actually use:</p>
<p>| Markdown | HTML emitted | Notes |</p>
<p>|&mdash;&mdash;&mdash;-|&mdash;&mdash;&mdash;&mdash;-|&mdash;&mdash;-|</p>
<p>| <code># Heading</code> through <code>######</code> | <code><h1></code><code><h6></code> | |</p>
<p>| <code><strong>bold</strong></code> | <code><strong></code> | |</p>
<p>| <code><em>italic</em></code> | <code><em></code> | |</p>
<p>| `<code> </code>code<code> </code><code> | </code><code>` (inline) | |</p>
<p>| <code> </code>`<code>lang </code>`<code> </code> | <code><pre><code class="language-lang"></code> | Fenced code blocks |</p>
<p>| <code>- list item</code> or <code>* list item</code> | <code><ul><li></code> | Unordered only |</p>
<p>| <code>!<a href="url" style="color: var(&ndash;c-accent-l);">alt</a></code> on its own line | <code><figure><img><figcaption></code> | |</p>
<p>| <code>&mdash;</code> on its own line | <code><hr></code> | |</p>
<p>| <code><a href="url" style="color: var(&ndash;c-accent-l);">text</a></code> | <code><a href></code> | |</p>
<p>| Bare text | <code><p></code> | |</p>
<p><strong>What's NOT supported yet:</strong></p>
<ul>
<li><code>> blockquote</code> — the CSS callout rules are written and waiting</li>
<li><code>1. ordered lists</code> — no <code><ol></code> output</li>
<li>Nested lists, tables, inline HTML, footnotes</li>
</ul>
<p>If you need one of these, the fix lives in the <code>md()</code> function in <code>build.js</code>. Each missing feature is a ~5-line addition.</p>
<h3>Adding a new style rule</h3>
<p>You're writing a post and want a new visual element. The workflow:</p>
<p>1. <strong>Open <code>_template.html</code></strong> (or <code>_index_template.html</code> for listing-only styles)</p>
<p>2. <strong>Find the <code><style></code> block</strong> at the end of <code><head></code> it's clearly marked with a <code>/<em> Team guide aesthetic </em>/</code> comment</p>
<p>3. <strong>Add your rule</strong> inside that block. Follow the existing conventions:</p>
<p> - Use the team guide palette (amber <code>#c9935a</code>, gold <code>#f59e0b</code>, blue <code>#38bdf8</code>, purple <code>#a855f7</code>)</p>
<p> - Prefix body-content rules with <code>.post__body</code> to scope them</p>
<p> - Match the existing code style (2-space indent, comment headers for sections)</p>
<p>4. <strong>Rebuild:</strong> <code>node build.js</code></p>
<p>5. <strong>Verify:</strong> open the page and check; <code>grep</code> your new selector in <code>*.html</code> to confirm it shipped</p>
<p>The golden rules for style additions:</p>
<ul>
<li><strong>Never</strong> edit <code>../style.css</code> — it's outside the repo</li>
<li><strong>Never</strong> hand-edit a <code>*.html</code> file — the build will clobber it</li>
<li><strong>Don't</strong> restyle <code>.nav</code>, <code>.footer</code>, or the mobile menu — those belong to the parent site</li>
<li><strong>Do</strong> use the existing palette; don't introduce new colours unless there's a strong reason</li>
<li><strong>Keep it self-contained</strong> — no external font loads, no CDN dependencies, no <code>@import</code></li>
</ul>
<h3>Extending <code>build.js</code></h3>
<p>Here are the most likely extensions and where to add them:</p>
<p><strong>Blockquote support</strong> (<code>></code> lines in markdown). Add to the <code>md()</code> function after the list handler:</p>
<pre><code class="language-js">// Blockquote
const bqMatch = line.match(/^&gt;\s?(.*)$/);
if (bqMatch) {
closeUl();
html += `&lt;blockquote&gt;&lt;p&gt;${inline(bqMatch[1])}&lt;/p&gt;&lt;/blockquote&gt;\n`;
continue;
}</code></pre>
<p><strong>Ordered lists</strong> (<code>1. item</code>). Add after the unordered list handler, with a separate <code>inOl</code> flag and <code>closeOl()</code> function mirroring <code>closeUl()</code>.</p>
<p><strong>Syntax highlighting.</strong> The current <code>md()</code> function already adds <code>class="language-{lang}"</code> to <code><code></code> inside <code><pre></code>. Swap in a lightweight highlighter like <code>highlight.js</code> or <code>shiki</code> — or write a tokenizer that emits <code><span></code> classes matching the team guide's <code>.c</code>, <code>.g</code>, <code>.p</code>, <code>.y</code>, <code>.r</code> colour convention.</p>
<p><strong>Swap in a full parser.</strong> If the feature gap gets annoying, replace the <code>md()</code> function with <code>marked</code> or <code>markdown-it</code>. The template system and frontmatter parsing stay the same — only the body conversion changes. One <code>require()</code> call and you get tables, blockquotes, ordered lists, and footnotes for free.</p>
<h3>Quick reference cheatsheet</h3>
<pre><code class="language-bash"># Add a new post
nano posts/my-post.md # write frontmatter + markdown
node build.js # regenerate HTML
# Tweak styling
nano _template.html # edit the &lt;style&gt; block
node build.js # rebuild all pages
grep "your-selector" *.html # confirm it shipped
# Verify before deploy
git diff --stat # should only show templates + *.html
node build.js # must exit 0
ls *.html | wc -l # 12 files = 11 posts + index</code></pre>
<p>In the meantime, the blog already looks sharper and more intentional — and it took two template files, one build step, and zero external dependencies to get there. That's the kind of upgrade we like.</p>
<pre><code>Building blog...
11 posts built + index.html
Done.</code></pre>
<p>Zero errors. Every regenerated HTML file now carries the inline <code><style></code>. Confirmed with <code>grep -l "background-clip" *.html</code> all pages ship the gradient.</p>
<h2>What we didn't touch</h2>
<p>Navigation, footer, site chrome, responsive behaviour — all unchanged. This was a CSS-only change. No markup altered beyond the <code><style></code> injection.</p>
<h2>What we learned</h2>
<p><strong>Inline styles beat separate CSS files when you don't control the server.</strong> The blog repo is standalone — it can't modify <code>../style.css</code>. Inline <code><style></code> blocks ship inside the generated HTML, so the blog is fully self-contained. One <code>git push</code> and it's live.</p>
<p><strong>Cascade order is the cleanest specificity hack.</strong> Putting the <code><style></code> block after the external stylesheet link means same-selector rules win by position. No <code>!important</code>, no selector wars, no unexpected regressions.</p>
<p><strong>Build systems make CSS changes safe.</strong> Because we never hand-edit <code>.html</code>, every style change is tested by regenerating all pages and grepping for the new selectors. If a rule doesn't ship, you know immediately.</p>
<p>Two gaps we'll fill later: blockquote support in <code>build.js</code> (the callout CSS is waiting) and ordered lists (same story). In the meantime, the blog already looks intentional — and it took two template files, one build step, and zero dependencies.</p>
<hr>
<p><em>The blog is generated by <a href="https://tinqs.com/tinqs/blog" style="color: var(&ndash;c-accent-l);">build.js</a> and served by <a href="https://tinqs.com" style="color: var(&ndash;c-accent-l);">Tinqs Studio</a>. All styling is self-contained in the templates.</em></p>
</div>
Before
After