# How Hallmark's Frontend Architecture Works: A Zero-Dependency SPA Built on Template Swapping

> Discover Hallmark's innovative zero-dependency SPA frontend architecture. Learn how vanilla JavaScript template swapping creates dynamic pages without frameworks or build steps.

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

---

**Hallmark uses a pure vanilla‑JavaScript **single‑page application** that rebuilds the visible page on the fly by swapping archetype templates and injecting theme‑specific copy—no build step, no framework, and no external dependencies.**

This architecture powers 20 distinct visual themes in the Hallmark project, a code analysis tool from Nutlope. The entire frontend lives in `site/` and relies on native browser APIs: Web Components aren't used, yet the system achieves component‑like modularity through a **slot‑based template system**. Understanding this design reveals how far modern vanilla JS can take you without React, Vue, or Svelte.

---

## The Core Abstraction: Three Maps Drive Everything

Hallmark's frontend architecture centers on three plain JavaScript objects that together define what users see. These reside in [`site/js/main.js`](https://github.com/Nutlope/hallmark/blob/main/site/js/main.js) and are consulted on every theme change.

| Map | Purpose | Lines in [`main.js`](https://github.com/Nutlope/hallmark/blob/main/main.js) |
|---|---|---|
| **`THEMES`** | Theme key → human‑readable label for the picker UI | 42‑63 |
| **`ARCHETYPES`** | Theme → `{hero, footer}` component tuple (structural "bones") | 70‑91 |
| **`COPY`** | Theme → text fixtures (eyebrow, title, lede, CTA, proof points) | 98‑200 |

The **archetype map** is the distinctive design choice. Rather than applying CSS variables to recolor a fixed layout, switching themes in Hallmark **rebuilds the DOM structure** by selecting different hero and footer templates. This guarantees visual variety—two themes can share no markup whatsoever.

---

## Slot System and Template Swapping

Templates live as standard `<template>` elements in [`site/index.html`](https://github.com/Nutlope/hallmark/blob/main/site/index.html), organized by archetype. The JavaScript manipulates them through two DOM regions marked with `data-slot` attributes.

### Slot Placement in HTML

```html
<!-- site/index.html excerpt -->
<main>
  <div data-slot="hero"></div>
  <!-- ...page content... -->
  <div data-slot="footer"></div>
</main>

<template id="hero-marquee">...</template>
<template id="hero-quote-led">...</template>
<template id="footer-colophon">...</template>
<template id="footer-minimal">...</template>

```

### The `swapArchetypes()` Function

This is the engine of Hallmark's frontend architecture. Located at lines 74‑96 in [`site/js/main.js`](https://github.com/Nutlope/hallmark/blob/main/site/js/main.js), it:

1. Looks up the archetype tuple from `ARCHETYPES[theme]`
2. Finds the corresponding `<template>` elements by ID
3. Clones their content
4. Interpolates copy from `COPY[theme]` into marked elements
5. Replaces the slot contents with the new fragment

```javascript
// Conceptual flow from site/js/main.js lines 74-96
function swapArchetypes(theme) {
  const { hero, footer } = ARCHETYPES[theme];
  const copy = COPY[theme];
  
  // Hero slot
  const heroTemplate = document.getElementById(`hero-${hero}`);
  const heroClone = heroTemplate.content.cloneNode(true);
  interpolateCopy(heroClone, copy);  // fills [data-copy] elements
  document.querySelector('[data-slot="hero"]').replaceChildren(heroClone);
  
  // Footer slot (mirrors hero pattern)
  // ...
  
  // One-shot fade-in animation
  document.documentElement.style.viewTransitionName = 'theme-swap';
}

```

The **copy interpolation** matches elements with `data-copy` attributes (e.g., `<span data-copy="eyebrow">`) against keys in the theme's copy fixture.

---

## Theme Application and State Management

The `applyTheme()` function (lines 53‑66) orchestrates the complete user‑visible transition:

```javascript
// site/js/main.js lines 53-66
async function applyTheme(theme) {
  // 1. Persist choice
  localStorage.setItem('hallmark-theme', theme);
  document.documentElement.dataset.theme = theme;
  
  // 2. Update picker UI
  setPressed(theme);
  
  // 3. Visual transition (View Transition API where supported)
  if (document.startViewTransition) {
    await document.startViewTransition(() => swapArchetypes(theme));
  } else {
    swapArchetypes(theme);
  }
}

```

**Progressive enhancement** is built in: browsers with `document.startViewTransition` get a smooth morphing animation; others fall back to instant replacement without polyfills.

---

## Critical Rendering Path: Beating First Paint

Hallmark's architecture includes a **blocking script in `<head>`** that eliminates flash-of-unstyled-content for themes. From [`site/index.html`](https://github.com/Nutlope/hallmark/blob/main/site/index.html) lines 65‑71:

```javascript
<script>
  // Runs before first paint
  const params = new URLSearchParams(location.search);
  const stored = localStorage.getItem('hallmark-theme');
  const initial = params.get('theme') || stored || 'default';
  document.documentElement.dataset.theme = initial;
</script>

```

This guarantees the correct CSS theme selectors apply immediately. A second optimization waits for fonts before revealing content:

```javascript
// site/js/main.js lines 73-88
document.fonts.ready.then(() => {
  document.documentElement.dataset.fontsReady = 'true';
});

```

---

## CSS Architecture: Tokens to Sections

The stylesheet organization mirrors the component boundaries. All files live in `site/css/`:

| File | Responsibility |
|---|---|
| [`tokens.css`](https://github.com/Nutlope/hallmark/blob/main/tokens.css) | **Design tokens**: OKLCH color palettes, typographic scales, motion curves, spacing units |
| [`base.css`](https://github.com/Nutlope/hallmark/blob/main/base.css) | CSS reset, global layout foundations, `data-theme` attribute selectors |
| [`components.css`](https://github.com/Nutlope/hallmark/blob/main/components.css) | Reusable UI primitives: buttons, tooltips, copy-buttons, theme picker dots |
| [`sections.css`](https://github.com/Nutlope/hallmark/blob/main/sections.css) | Hero and footer layouts specific to each archetype |

Theme-specific styling uses attribute selectors generated from the `data-theme` value set during the critical rendering path:

```css
/* site/css/base.css excerpt */
[data-theme="cobalt"] {
  --surface-1: oklch(20% 0.05 250);
  --text-1: oklch(95% 0.01 250);
  --accent: oklch(70% 0.15 250);
}

```

---

## Interaction Layer: Zero-Dependency Utilities

The remaining ~200 lines of [`site/js/main.js`](https://github.com/Nutlope/hallmark/blob/main/site/js/main.js) implement ergonomic features without libraries:

- **`attachCopyButtons()`** (lines 101‑110): Clipboard API with fallback for `[data-copy-source]` elements
- **Keyboard shortcuts**: `T` cycles themes, `R` picks random
- **Hover preview**: Tooltip on picker dots showing theme name
- **Easter egg**: Hidden activation pattern for a surprise theme

These attach during DOMContentLoaded and use event delegation where possible.

---

## Extending Hallmark: Adding a New Theme

The frontend architecture is intentionally **flat and data-driven**. Adding a theme requires no HTML changes if reusing existing archetypes, or new `<template>` blocks if creating novel layouts.

```javascript
// site/js/main.js — extend three maps

const THEMES = {
  // ...existing themes...
  aurora: "Aurora",
};

const ARCHETYPES = {
  // ...existing themes...
  aurora: { hero: "gradient-text", footer: "colophon" },
};

const COPY = {
  // ...existing themes...
  aurora: {
    eyebrow: "Gradient typography",
    title: "HALLOWS' EVE",
    lede: "A theme that lives in the space between colors.",
    ctaLabel: "Explore themes",
    proofLabel: "From the Hallmark community",
  },
};

```

Then add to [`site/index.html`](https://github.com/Nutlope/hallmark/blob/main/site/index.html):

```html
<template id="hero-gradient-text">
  <h1 style="background: linear-gradient(...)">...</h1>
</template>

```

Reload—the picker automatically includes "Aurora" and handles all state transitions.

---

## Summary

- **Hallmark's frontend architecture** is a template‑swapping SPA with zero dependencies, using native `<template>` elements and the View Transition API.

- **Three maps** (`THEMES`, `ARCHETYPES`, `COPY`) in [`site/js/main.js`](https://github.com/Nutlope/hallmark/blob/main/site/js/main.js) completely define available themes and their content.

- **Structural variety** comes from archetype selection—each theme picks hero and footer templates, not just color schemes.

- **Performance** is optimized through a blocking head script for theme selection and `document.fonts.ready` to prevent FOUT.

- **Extensibility** is data-driven: new themes require only map entries and optional template definitions, with no build process.

---

## Frequently Asked Questions

### What frontend framework does Hallmark use?

Hallmark uses **no frontend framework**. It is built with vanilla JavaScript, HTML templates, and CSS. All interactivity—including theme switching, copy-to-clipboard, and keyboard shortcuts—is implemented with native browser APIs in [`site/js/main.js`](https://github.com/Nutlope/hallmark/blob/main/site/js/main.js). The only modern API feature used optionally is the View Transition API for smooth theme changes.

### How does Hallmark switch themes without reloading the page?

The `applyTheme()` function in [`site/js/main.js`](https://github.com/Nutlope/hallmark/blob/main/site/js/main.js) persists the choice to `localStorage`, updates the `data-theme` attribute on `<html>`, and calls `swapArchetypes()` to **clone new template content** and replace the `data-slot` regions. This happens entirely client-side; no server request occurs. Browsers supporting `document.startViewTransition` animate the swap natively.

### Where are the theme templates defined in Hallmark?

Templates are inline `<template>` elements in [`site/index.html`](https://github.com/Nutlope/hallmark/blob/main/site/index.html), located around line 885 in the "HERO ARCHETYPES" section. Each has an `id` following the pattern `hero-{archetype}` or `footer-{archetype}`. The JavaScript references these by ID when `swapArchetypes()` clones them for the active theme.

### Can I add custom themes to Hallmark without modifying the core code?

Yes, but you must edit the source files. Extend `THEMES`, `ARCHETYPES`, and `COPY` in [`site/js/main.js`](https://github.com/Nutlope/hallmark/blob/main/site/js/main.js), then add corresponding `<template>` blocks in [`site/index.html`](https://github.com/Nutlope/hallmark/blob/main/site/index.html) if using new archetypes. Because Hallmark has no build step, saving these files and reloading the browser immediately surfaces the new theme in the picker UI.