How Hallmark's Frontend Architecture Works: A Zero-Dependency SPA Built on Template Swapping
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 and are consulted on every theme change.
| Map | Purpose | Lines in 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, organized by archetype. The JavaScript manipulates them through two DOM regions marked with data-slot attributes.
Slot Placement in 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, it:
- Looks up the archetype tuple from
ARCHETYPES[theme] - Finds the corresponding
<template>elements by ID - Clones their content
- Interpolates copy from
COPY[theme]into marked elements - Replaces the slot contents with the new fragment
// 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:
// 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 lines 65‑71:
<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:
// 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 |
Design tokens: OKLCH color palettes, typographic scales, motion curves, spacing units |
base.css |
CSS reset, global layout foundations, data-theme attribute selectors |
components.css |
Reusable UI primitives: buttons, tooltips, copy-buttons, theme picker dots |
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:
/* 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 implement ergonomic features without libraries:
attachCopyButtons()(lines 101‑110): Clipboard API with fallback for[data-copy-source]elements- Keyboard shortcuts:
Tcycles themes,Rpicks 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.
// 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:
<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) insite/js/main.jscompletely 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.readyto 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. 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 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, 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, then add corresponding <template> blocks in 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →