<!-- demo:embed example="basic/3-custom-theme" -->

Now you'll open every file in `themes/default/` and name what it does. By the end of this page you should be able to walk a stranger through the theme directory like it's your own.

## The directory shape, again — but with reasons

```
themes/default/
├── index.json
├── *.template.html                 ← every element-renderer template
├── master/sample/email/*.json      ← 50+ buyer-surface email samples
├── master/sample/page/*.json       ← 20+ buyer-surface page samples
├── master/assets/image/email/*     ← content images for email samples
└── master/assets/image/page/*      ← content images for page samples
```

Three top-level concepts:

1. **Templates** (the rendering contract) live next to `index.json`.
2. **Samples** (the buyer pages) live under `master/sample/`.
3. **Assets** (images) live under `master/assets/`.

That's the entire theme surface.

## index.json — the manifest

Open `themes/default/index.json`:

```json
{
    "name": "default",
    "title": "Default Theme",
    "description": "ACM Default Theme",
    "pages": ["Page", "Form"],
    "templates": ["Block", "Grid", "Cell", "P", "Heading", "Image", "Button", "..."],
    "configData": { "..." },
    "formats": { "..." }
}
```

Five fields the engine reads:

- `name` — the dir name (must match the folder).
- `title` — display label for the gallery + theme picker. Buyers see this string.
- `pages` — which top-level page-template families this theme provides. Most themes ship `Page` (the layout for emails / landing pages) and optionally `Form` (the layout for checkout / signup forms).
- `templates` — an array listing every `<Name>.template.html` file the theme ships. Used by the engine to validate what samples can use.
- `formats` + `configData` — baseline values applied to every page that uses this theme (default text colour, default button colour, default container width, etc.). Per-page overrides win.

You can ignore the rest at first. The five above are enough to register a theme.

## Templates — the visual contract

Every element type the engine surfaces has a corresponding `<Name>.template.html`. The shape:

```ejs
<!-- Image.template.html — render an ImageElement -->
<%
const align = formats.align || 'left';
const width = formats.width || 600;
const radius = formats.border_radius || 0;
%>
<div style="text-align: <%= align %>;">
    <a <% if (url) { %>href="<%= url %>" target="<%= target %>" rel="<%= rel %>"<% } %>>
        <img src="<%= MEDIA_URL %><%= src %>"
             alt="<%= alt %>"
             width="<%= width %>"
             style="border-radius: <%= radius %>px; max-width: 100%; height: auto;">
    </a>
</div>
```

EJS is the templating language — `<%= var %>` interpolates a JS expression, `<% if (x) { %>` runs JS for control flow. The engine evaluates each template against:

- The element's own JSON node (`src`, `alt`, `url`, etc. become local vars)
- The element's `formats` object (under the local `formats` var)
- A small set of theme-wide globals (`MEDIA_URL`, `THEME_CONFIG_DATA`, etc.)

Two template categories you'll see:

- **Element templates** — `P`, `Heading`, `Image`, `Button`, `Divider`, `List`, … one per leaf element class. These are the bulk of the templates dir.
- **Container templates** — `Page`, `Block`, `Grid`, `Cell`, `Form`. These define how their children are stacked. The shipped Page template is wrapped for email-client compatibility (table-based layout, MSO conditionals).

Every template is < 200 LOC; most are 30-80 LOC. You can read all 30 in an afternoon.

## Samples — the buyer pages

`master/sample/email/` holds the email samples. `master/sample/page/` holds the landing-page + checkout samples. Each sample is one `.json` file plus a thumbnail (`.png` for full-bleed previews, `.svg` for placeholder layouts) and an optional `.html` rendering for offline preview.

Pick any sample and open its JSON — you'll recognise the shape from [JSON structure](/docs/03-json-structure/). The `Minimal.json` sample is the smallest meaningful example (≈ 250 lines); `WelcomeCustomers.json` and `SitewideSale.json` are richer (~1000 lines). Pick by your appetite.

## Assets — content images

`master/assets/image/email/` and `master/assets/image/page/` hold every content image used by samples — hero photos, product shots, social-icon glyphs, brand glyphs. The shipped images are AI-generated for a clean royalty-free shipping story (RULE-H1).

When a sample's `ImageElement` references `"src": "master/assets/image/email/logo.png"`, the engine resolves it as `MEDIA_URL + 'master/assets/image/email/logo.png'`. `MEDIA_URL` is the fourth global in the bundle — for the shipped demo it's `/themes/default/`, so the final URL is `/themes/default/master/assets/image/email/logo.png`.

Replace any image by dropping a file with the same path + name. The JSON pointing at it doesn't change.

## The 8 hard rules every theme must follow

Distilled from `THEME_BEST_PRACTICE.md` (which is 2800 LOC of rules + rationale; you don't need to read it cover-to-cover, but you will hit it eventually):

1. **No copyrighted images, ever** (RULE-H1). Use the shipped AI library, generate your own via the included `scripts/core/gen-images.mjs`, or buy royalty-free.
2. **Hierarchy is sacred** (RULE-H2). Page → Block → Element. Page → Block → Grid → Cell → Block → Element. Don't skip layers.
3. **No hardcoded image sizes except logos + icons** (RULE-H3). Content imagery is responsive; only fixed assets like logos / glyphs declare pixel widths.
4. **Aspect-lock on by default** (RULE-H4). Image elements preserve aspect ratio unless the buyer explicitly overrides.
5. **4-colour palette cap** (RULE-H8). One sample uses at most 4 distinct colours (background, foreground, accent, muted). More than 4 reads as Geocities.
6. **5-typography-tier cap** (RULE-H10). H1 / H2 / H3 / Body / Caption. Anything beyond is rejected at the audit.
7. **No margin on `BlockElement`** (RULE-H11). Vertical rhythm comes from Block's `padding_top` + `padding_bottom`. Margin collapses unpredictably in email clients.
8. **Explicit units in dimensions** (RULE-H12). `"width": 600` is pixels; never `"width": "100%"` in a `formats` field. Percentages live elsewhere (cell widths, container queries) — but a leaf element's width is always typed.

The rules feel pedantic until you've shipped a sample that breaks one and watched it render differently in Gmail vs Outlook. Then they read as protection.

## The 5 templates you'll touch most

If you fork the default theme, these are the files most buyers customise:

- `Page.template.html` — the outer page wrapper. Where you put your brand's outermost background, container width, fixed header.
- `Heading.template.html` — your brand's headline typography (font, weight, line-height).
- `P.template.html` — your brand's body typography.
- `Button.template.html` — primary CTA shape (border radius, padding, hover state).
- `Image.template.html` — usually fine as-is; touch only if you need brand-specific image treatments (rounded corners, drop shadows, polaroid frames).

Almost every other template inherits visual decisions from these five via `formats.<key>` defaults in the buyer's brand-customised `index.json` block.

## Did you make it?

You should now be able to:

- ✅ Open `themes/default/`, name every top-level dir + `index.json`'s purpose without re-reading.
- ✅ Read an EJS template and predict the rendered HTML for a given JSON node.
- ✅ State the 8 hard rules and explain why each one exists.
- ✅ Identify the 5 templates a brand fork would touch first.

What's next?

- → [Create a sample](/docs/06-create-sample/) ⭐ — the headline lock-step copy-along; you'll author a new email and see it in the gallery in 30 minutes.
- 🔗 file-tree of `demo/themes/default/`.
- 🔗 `THEME_BEST_PRACTICE.md` for the long-form authoring rules + audit matrix.

Stuck?

- 🐛 grep `themes/default/<TemplateName>.template.html` for the exact EJS contract.
- 🐛 the `example-mini` theme dir is the smallest possible theme — open it for a 5-file reference of what's required vs nice-to-have.
