# How the Hallmark Theme Diversification Rule Ensures Consecutive Themes Differ on Axes

> Learn how the Hallmark theme diversification rule prevents consecutive themes from repeating on any axes, ensuring unique combinations for every build and enhancing variety.

- Repository: [Hassan El Mghari/hallmark](https://github.com/Nutlope/hallmark)
- Tags: deep-dive
- Published: 2026-08-13

---

**The Hallmark theme-diversification rule guarantees that no two consecutive builds reuse the exact same combination of paper-band, display-style, and accent-hue by rejecting candidates that match all three axes of the most recent entry in [`.hallmark/log.json`](https://github.com/Nutlope/hallmark/blob/main/.hallmark/log.json).**

The Nutlope/hallmark skill enforces the **theme diversification rule** every time it generates a page or component to keep consecutive outputs visually distinct. By tracking three independent design axes across builds, the system prevents repetitive styling while still allowing macrostructures and other visual elements to recur. This rule is formally specified in [`skills/hallmark/SKILL.md`](https://github.com/Nutlope/hallmark/blob/main/skills/hallmark/SKILL.md) and operates against the persistent [`.hallmark/log.json`](https://github.com/Nutlope/hallmark/blob/main/.hallmark/log.json) file.

## What Is the Theme Diversification Rule?

The theme diversification rule is a deterministic guard that runs before Hallmark finalizes a theme selection. It compares the candidate theme’s axis values against recent build history to ensure at least one attribute changes. If the candidate matches the previous entry on all three axes, the skill throws a diversification failure and selects a different theme.

### The Three Diversification Axes

Every catalog theme and custom theme in Hallmark declares three independent attributes under an `## Axes (diversification)` heading:

- **paper-band** — the background paper colour family, such as `cream`, `night`, or `daylight`.
- **display-style** — the typographic treatment of headings, such as `classical-serif-lowercase`, `italic-serif`, or `mono-display`.
- **accent-hue** — the primary accent colour strategy, such as `single-hue`, `multi-hue`, or `duo-tone`.

These axes are recorded in theme reference files like [`skills/hallmark/references/themes/hum.md`](https://github.com/Nutlope/hallmark/blob/main/skills/hallmark/references/themes/hum.md) and [`skills/hallmark/references/themes/carnival.md`](https://github.com/Nutlope/hallmark/blob/main/skills/hallmark/references/themes/carnival.md).

### Where the Axes Are Defined

Catalog themes expose their axes directly in their reference markdown files. Custom themes declare them explicitly during generation so the rotation logic remains blind to whether a theme is built-in or user-created. This ensures the diversification rule applies uniformly across all theme types.

## How the Rotation Check Works

Before committing to a candidate theme, Hallmark reads the last three to five entries from [`.hallmark/log.json`](https://github.com/Nutlope/hallmark/blob/main/.hallmark/log.json) as defined in [`skills/hallmark/SKILL.md`](https://github.com/Nutlope/hallmark/blob/main/skills/hallmark/SKILL.md) § 2.5. It then compares the candidate’s axis tuple against the most recent log entry. If `paper-band`, `display-style`, and `accent-hue` all match, the candidate is rejected.

The logic is implemented roughly as follows:

```javascript
// 1️⃣ Load the log (if it exists)
const logPath = '.hallmark/log.json';
let recent = [];
if (fs.existsSync(logPath)) {
  const entries = JSON.parse(fs.readFileSync(logPath, 'utf8'));
  recent = entries.slice(0, 5);      // look at the last 3‑5 runs
}

// 2️⃣ Resolve candidate theme's axes
// (catalog themes read from site/css/tokens.css, custom themes specify them directly)
const candidate = {
  theme: 'hum',
  axes: { paperBand: 'cream', displayStyle: 'rounded‑sans', accentHue: 'multi' }
};

// 3️⃣ Compare with the most recent entry
const last = recent[0];
if (last && candidate.axes.paperBand === last.axes.paperBand &&
    candidate.axes.displayStyle === last.axes.displayStyle &&
    candidate.axes.accentHue === last.axes.accentHue) {
  // ❌ All three axes match – reject this theme
  throw new Error('Diversification failure: pick a theme that differs on at least one axis.');
}

// 4️⃣ If it passes, record the new entry
const newEntry = {
  date: new Date().toISOString().split('T')[0],
  macrostructure: chosenMacro,
  theme: candidate.theme,
  theme_axes: `${candidate.axes.paperBand} / ${candidate.axes.displayStyle} / ${candidate.axes.accentHue}`,
  brief: briefOneLiner
};
fs.writeFileSync(logPath, JSON.stringify([newEntry, ...recent].slice(0, 20), null, 2));

```

Because the check only rejects when **all three** axes align, the rule allows two consecutive builds to share two attributes as long as at least one axis differs. This fine-grained control prevents "variety drift" without forcing total visual discontinuity.

## Logging Prior Runs in [`.hallmark/log.json`](https://github.com/Nutlope/hallmark/blob/main/.hallmark/log.json)

The [`.hallmark/log.json`](https://github.com/Nutlope/hallmark/blob/main/.hallmark/log.json) file serves as the project-level memory for the skill. After each successful build, Hallmark appends a JSON entry containing the theme name, the three axis values, the chosen macrostructure, and a brief description. The file retains roughly the last 20 entries, giving the rotation check enough history to enforce variety.

A typical catalog build is logged like this:

```json
{
  "date": "2026-08-13",
  "macrostructure": "long-document",
  "theme": "hum",
  "theme_axes": "cream / rounded‑sans / multi",
  "brief": "Kids‑friendly habit tracker"
}

```

Custom themes follow the same shape but record explicitly provided axes:

```json
{
  "date": "2026-08-12",
  "macrostructure": "gallery",
  "theme": "custom",
  "theme_axes": "day‑paper / mono‑display / duo‑tone",
  "vibe": "playful tech startup",
  "brief": "Landing page for a new SDK"
}

```

By persisting this data to disk, Hallmark maintains continuity across sessions and ensures the diversification rule is enforced even after the process restarts.

## Special Cases and Inversions

The diversification rule has two important modifications depending on project context: full-app redesigns and custom themes.

### App-Wide Redesigns with [`design.md`](https://github.com/Nutlope/hallmark/blob/main/design.md)

When a project contains a [`design.md`](https://github.com/Nutlope/hallmark/blob/main/design.md) file, Hallmark treats the work as a multi-page application redesign rather than a standalone page. In this mode, the rule is inverted: consecutive pages **must share** the same axes to preserve a coherent visual system. This behavior is documented in [`skills/hallmark/references/verbs/redesign.md`](https://github.com/Nutlope/hallmark/blob/main/skills/hallmark/references/verbs/redesign.md) § 2, where the skill rejects candidates that deviate from the established app-wide theme instead of ones that match it.

### Custom Themes and the Same Rotation Logic

Custom-generated themes are not exempt from diversification. According to [`skills/hallmark/references/custom-theme.md`](https://github.com/Nutlope/hallmark/blob/main/skills/hallmark/references/custom-theme.md) § 4–5, custom themes declare their `paper-band`, `display-style`, and `accent-hue` values explicitly. The skill processes them through the same `theme-route-blind` rotation check, meaning a custom theme can still be rejected if its axes match the previous build. The log records the custom axes exactly like catalog entries.

## Summary

- The **theme diversification rule** is specified in [`skills/hallmark/SKILL.md`](https://github.com/Nutlope/hallmark/blob/main/skills/hallmark/SKILL.md) and prevents visual repetition across consecutive builds.
- It operates on three axes: **paper-band**, **display-style**, and **accent-hue**.
- The skill reads the last 3–5 entries from [`.hallmark/log.json`](https://github.com/Nutlope/hallmark/blob/main/.hallmark/log.json) and rejects any candidate whose axes exactly match the most recent entry.
- **Custom themes** declare their own axes and are subject to the same rotation logic.
- In **app-wide redesigns** with a [`design.md`](https://github.com/Nutlope/hallmark/blob/main/design.md) file, the rule inverts so consecutive pages share the same axes for consistency.

## Frequently Asked Questions

### What are the three diversification axes in Hallmark?

The three axes are **paper-band** (background colour family), **display-style** (heading typography), and **accent-hue** (primary colour strategy). Every theme declares these attributes under an `## Axes (diversification)` heading in its reference file. The skill uses this tuple to decide whether a candidate theme is different enough from the previous build.

### How does Hallmark prevent the same theme from appearing twice in a row?

Hallmark loads [`.hallmark/log.json`](https://github.com/Nutlope/hallmark/blob/main/.hallmark/log.json) and compares the candidate theme’s three axes against the most recent log entry. If all three axes match, the skill raises a diversification error and picks a different theme. This guarantees at least one axis differs between any two consecutive Hallmark outputs.

### Where does Hallmark store the history of previously used themes?

The skill persists build history in [`.hallmark/log.json`](https://github.com/Nutlope/hallmark/blob/main/.hallmark/log.json) at the project root. Each entry records the theme name, macrostructure, the three axis values as a slash-delimited string, and a brief description of the build. This file allows the rotation check to enforce variety across multiple sessions.

### Does the theme diversification rule apply to custom themes?

Yes. According to [`skills/hallmark/references/custom-theme.md`](https://github.com/Nutlope/hallmark/blob/main/skills/hallmark/references/custom-theme.md), custom themes explicitly declare their three axes. The skill applies the same `theme-route-blind` rotation check to custom themes as it does to catalog themes, ensuring they are also compared against the log and rejected if they duplicate the most recent axes.