Content Guide
This guide covers how to update every page type on the site. Each section maps to a specific page or component. Find the section for what you are updating and follow the steps in order.
For stylesheet conventions, see Architecture. For JavaScript interactions, see JavaScript.
Adding a blog post
A post is one file: blog/posts/<slug>/index.html. It contains front matter and the body HTML only. The shared layout, _includes/layouts/post.html, adds the head, navigation, hero, series navigation, comments, newsletter and footer at build time. Do not paste a page shell into a post.
Publishing a post updates the blog index, the work page's "Latest writing" strip, the neighbouring posts' previous and next links, and the sitemap. You do not edit any of them.
1. Create the folder and file
Folder naming rules:
- Lowercase letters and hyphens only
- No spaces, no uppercase, no special characters
- The folder name is the URL slug, and must match
canonicalUrl
2. Write the front matter
The working example is Part 9. Its source file in the website repository is blog/posts/information-architecture-in-documentation/index.html. Read its front matter alongside the tables below.
Required fields
Every post needs all of these. Without one of the first five, the build fails or the page loses its layout. Without one of the others, the build still succeeds, but part of the page or of the blog listings is empty or broken.
| Field | Read by | Without it |
|---|---|---|
layout |
Eleventy. Always layouts/post.html |
The post is published as its bare body, with no head, navigation or styles |
title |
The hero, the Twitter title, the structured data, and the cards on the blog index and the work page. Also the browser title and og:title, unless pageTitle is set |
The build fails |
description |
The meta description and the structured data. Also the Open Graph and Twitter descriptions, unless ogDescription or twitterDescription is set |
The build fails |
canonicalUrl |
The canonical link, og:url, the structured data and the sitemap |
The build fails |
publishDate |
The hero, the structured data and the sitemap. Must be in the form September 25, 2026 |
The build fails. A date in any other form also fails the build |
part |
The series number. It sets the order on the blog index, the series tag, the title suffix and the previous and next links | The post is left out of the blog index, the work page and the sitemap, and has no series tag |
ogImage |
og:image and twitter:image. An absolute URL |
Both share image tags are empty |
readTime |
The hero and the featured post on the blog index | The read time is blank |
cardImage |
A file name in assets/images/. The image on the blog index and work page cards, and the banner |
The card images are broken |
cardDate |
The card date on the blog index, in the form September 2026 |
The card date is blank |
cardExcerpt |
The card excerpt on the blog index | The excerpt is blank |
pullQuote |
The featured post on the blog index, and the "Continue the series" cards on the neighbouring posts | The quotation marks show with nothing between them |
Structured data cannot contain HTML entities such as ’, because a browser does not decode them inside a script. A title, description, keyword or citation that contains one fails the build. Type the character itself.
Fields with a default
blog/posts/posts.json sets these for every post in blog/posts/. A post may override either one in its own front matter.
| Field | Default in posts.json |
Read by |
|---|---|---|
postSubtitle |
The author line | The hero subtitle. It may contain HTML |
series |
The series name, its total number of parts, and the blog url |
The series tag, for example "Systems Over Sentences · Part 09 of 10", the title suffix, the part navigation label and the structured data. A post that overrides only series.name keeps the default total and url. The Eleventy configuration also reads series from posts.json for every other page, so the blog index and the work page show the same name |
Optional fields
| Field | Effect |
|---|---|
pageTitle |
The title for search results. For the browser title and og:title, the build adds a vertical bar, the series name and the part number after it. Without it, the build adds the same suffix to the post's title instead |
ogTitle |
Replaces the assembled page title in og:title |
ogDescription |
The Open Graph description. Without it, og:description uses description |
twitterDescription |
Replaces description in twitter:description |
banner |
true shows cardImage as a banner above the hero, and hides the visible post title, which stays available to screen readers |
bannerImage |
Shows this file in the banner instead of cardImage |
keywords |
A list of strings, added to the structured data |
citation |
A string, or a list of strings, added to the structured data |
draft |
See Drafts |
newsletterShowLabel |
true shows the "Stay current" label above the newsletter block |
newsletterTitleTag |
The HTML tag for the newsletter heading. Without it, the heading is a p |
newsletterBtnClass |
The class of the newsletter block's booking button. Without it, the class is btn-ghost-light |
newsletterNoTarget |
true opens the booking link in the same tab |
3. Write the body
Below the front matter, write the post body as HTML. Start at the first paragraph or heading of the article. Do not include <html>, <head>, navigation or footer.
4. Add the images
All images live in assets/images/, which the blog shares with the rest of the site.
Name the banner with the post's slug exactly, with no suffix. Give it the extension that matches the file's real format: .png for a PNG, and .jpg or .jpeg for a JPEG. For example, Part 9's slug is information-architecture-in-documentation, and its banner is a PNG, so the file is information-architecture-in-documentation.png. Part 7's banner is a JPEG, so the file is the-discipline-of-research.jpg.
Place the file named in cardImage in assets/images/. If ogImage points to a different file, add that too. A missing image does not fail the build.
5. Preview
Check the post, /blog/, /work/ and /sitemap.xml.
6. Publish
Work through Publishing Checklist: Publish a blog post, then commit on a branch and merge:
git switch -c content/part-NN
git add blog/posts/your-post-slug assets/images
git commit -m "feat: publish Part NN, Your Post Title"
git push origin content/part-NN
Drafts
draft: true removes a post from the posts collections. The post then does not appear on the blog index, the work page, the series navigation or the sitemap.
Draft posts are excluded from production builds entirely. They render only under npm start. Remove draft: true to publish. Eleventy does not clear _site/ between builds, so a page that was drafted, deleted or renamed after being published can survive in your local _site/ and look live when it is not.
Updating the work page
The work page lives at work/index.html.
Adding a portfolio card
Each card follows this structure:
<a href="/your-portfolio-piece/" class="work-card reveal reveal-d1">
<div class="work-banner">
<img class="work-banner-img"
src="../assets/images/your-card-image.png"
alt="Alt text" />
<span class="work-banner-tag">Category</span>
</div>
<div class="work-body">
<div class="work-num">0?</div>
<div class="work-tags">
<span class="work-tag">Tag One</span>
<span class="work-tag">Tag Two</span>
</div>
<p class="work-ctitle">Portfolio Piece Title</p>
<div class="work-ba">
<div class="work-ba-block before">
<p class="work-ba-label">The problem</p>
<p class="work-ba-text">One sentence describing the problem.</p>
</div>
<div class="work-ba-block">
<p class="work-ba-label after">What I built</p>
<p class="work-ba-text">One sentence describing what you built.</p>
</div>
</div>
<p class="work-outcome"><strong>Result:</strong> One sentence outcome.</p>
<div class="work-foot">
<span class="work-link">Read the documentation</span>
<span class="work-arrow">→</span>
</div>
</div>
</a>
Rules:
- Increment work-num sequentially
- Use reveal-d1 and reveal-d2 alternately for staggered animation
- Add the card image to assets/images/ before pushing
- If the piece has its own URL, follow Publishing Checklist: Add or remove a portfolio piece
Removing a portfolio card
Removing a card requires four steps beyond deleting the block:
- Resequence the work numbers: update every
work-numvalue so the sequence is unbroken. If you remove card 03, cards 04, 05, and 06 become 03, 04, and 05. - Rebalance the reveal delay classes: check that
reveal-d1andreveal-d2still alternate correctly across the remaining cards. - Check the homepage: if the removed piece appeared in the selected
work grid on
index.html, replace it with another piece or remove the card from that grid too. - Update the sitemap and counts: follow Publishing Checklist: Add or remove a portfolio piece.
Updating the services page
The services page lives at services/index.html.
The page documents the Documentation Audit service. If the pricing,
deliverables, or FAQ change, edit the relevant section directly in
services/index.html. All written content is hardcoded HTML. The one
dynamic component is the Calendly booking embed. It loads from an
external script and renders the booking widget at runtime.
If the Calendly booking link changes, follow Publishing Checklist: Change the booking link.
Updating the homepage
The homepage lives at index.html in the repository root. All sections
are inline. Page-specific styles are in the <style> block in <head>.
JavaScript interactions are in the <script> block before </body>.
This section covers four homepage elements that require content updates: the selected work grid, the social proof carousel, the typewriter snippets, and the availability status. For all other homepage JavaScript behaviour, see JavaScript.
Updating the selected work grid
The homepage displays four selected work cards. To swap a card, find the
relevant .work-card block in index.html and replace its content.
Follow the same card structure as the work page above.
Updating the social proof carousel
The carousel in the hero side panel has three items. To update an item,
find the .side-carousel-item block and edit the text directly. To add
a fourth item:
- Add a new
.side-carousel-itemdiv inside#sideCarouselTrack - Add a new
<button class="side-carousel-dot">inside#sideCarouselDots - The carousel JavaScript handles the rest automatically
Updating the typewriter snippets
The typewriter cycles through three code snippets in the hero panel.
Find the twSnippets array in the <script> block and edit the strings
directly. Each string supports \n for line breaks.
Updating availability status
The availability indicator appears in two places on the homepage:
- Hero badge:
<div class="hero-badge"> - Footer:
<div class="f-avail">
Update both when availability changes.
Updating the footer
Blog post footers come from the shared layout, _includes/layouts/post.html. A change there reaches every post at the next build, so posts are not in the list below.
The footer on every other page is hard-coded in that page. It is not a shared component. If the footer content changes (navigation links, contact details, copyright year or tagline), apply the change to each of these files by hand:
index.htmlwork/index.htmlservices/index.htmlaudit/index.htmlblog/index.html_includes/layouts/post.html, for blog posts
404.html has no footer.
Use VS Code's global find and replace (⌘⇧H on Mac, Ctrl+Shift+H on
Windows) to apply footer changes across all files simultaneously.