Performance Considerations for Nutlope/hallmark: Optimizing Landing Pages for 60fps

Nutlope/hallmark achieves smooth 60fps animations by restricting motion to GPU-composited properties like transform and opacity, eliminating layout thrash through IntersectionObserver, and batching DOM updates using the View Transitions API.

Nutlope/hallmark is a pure HTML and CSS landing-page generator designed to run entirely in the browser without heavy JavaScript frameworks. Understanding the performance considerations for Nutlope/hallmark is essential for maintaining fluid user experiences, as the project follows strict guidelines to minimize paint cycles, avoid layout recalculations, and respect user accessibility preferences.

GPU-Friendly Animation Strategy

The foundation of Hallmark’s performance strategy rests on GPU-composited properties only. According to skills/hallmark/references/motion.md, the codebase explicitly restricts animations to transform and opacity because these properties never trigger layout or paint operations, allowing the browser to offload work directly to the graphics processor.

Banned Properties That Trigger Layout Thrash

Animating size-related properties forces the browser to recalculate layout on every frame, causing layout thrash. Hallmark explicitly bans animations that touch width, height, top, left, margin, and padding as documented in the motion reference bans section. These restrictions prevent the main thread from bogging down during scroll or resize events.

Standardized Duration Buckets

To keep motion predictable and performant, Hallmark buckets all animation durations into three standardized values:

  • Micro: ~120ms for subtle feedback
  • Short: ~220ms for standard transitions
  • Long: ~420ms for emphasis animations

All durations use an exponential ease-out curve defined in skills/hallmark/references/motion.md, ensuring animations feel responsive while completing quickly enough to avoid jank on low-end devices.

.reveal {
  opacity: 0;
  transform: translateY(8px);
  animation: reveal var(--dur-long) var(--ease-out) forwards;
  animation-delay: calc(var(--i, 0) * 60ms);
}

@keyframes reveal {
  to { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .reveal { animation: none; opacity: 1; transform: none; }
}

Reduced Motion Accessibility

Every animation in Hallmark is wrapped in a @media (prefers-reduced-motion: reduce) block as mandated by the motion reference guidelines. This media query forces fast, linear transitions or removes animations entirely for users who have disabled motion in their system preferences. This approach guarantees accessibility compliance while eliminating unnecessary GPU work for motion-sensitive users.

JavaScript Performance Optimizations

Beyond CSS constraints, Hallmark employs three specific JavaScript strategies in site/js/main.js to avoid main-thread blocking.

Scroll-Linked Effects with IntersectionObserver

Instead of listening to scroll events—which fire synchronously on the main thread and can fire hundreds of times per second—Hallmark uses IntersectionObserver to trigger animations only when elements actually enter the viewport. This API runs off-thread and significantly reduces CPU usage during scrolling.

const observer = new IntersectionObserver((entries) => {
  entries.forEach(e => {
    if (e.isIntersecting) {
      e.target.classList.add('is-in');
      observer.unobserve(e.target);
    }
  });
});

document.querySelectorAll('.reveal').forEach(el => observer.observe(el));

View Transitions for Theme Switching

Theme switching in Hallmark leverages the document.startViewTransition API (lines 61-66 in site/js/main.js) when available. This browser feature batches DOM changes into a single compositor-only repaint, preventing white flashes or layout jumps. When the API is unavailable, the code falls back to a regular synchronous apply.

function applyTheme(theme) {
  if (!THEMES[theme]) return;
  const apply = () => {
    root.dataset.theme = theme;
    swapArchetypes(theme);
    setPressed(theme);
    localStorage.setItem(STORAGE_KEY, theme);
  };
  if (!reduced && document.startViewTransition) {
    document.startViewTransition(apply);
  } else {
    apply();
  }
}

Network Request Caching

Expensive network calls, such as fetching the GitHub star count, are cached in localStorage for one hour (3,600,000ms) as implemented in lines 80-86 of site/js/main.js. Subsequent page loads read the cached value instantly and only re-validate the data in the background, eliminating network latency from the critical rendering path.

const CACHE_KEY = 'hallmark-star-count:nutlope/hallmark';
const TTL = 3600000; // 1 hour
let cached = JSON.parse(localStorage.getItem(CACHE_KEY) || '{}');

if (cached.n && Date.now() - cached.t < TTL) {
  starEl.textContent = format(cached.n);
} else {
  fetch(`https://api.github.com/repos/nutlope/hallmark`)
    .then(r => r.json())
    .then(d => {
      const n = d.stargazers_count;
      starEl.textContent = format(n);
      localStorage.setItem(CACHE_KEY, JSON.stringify({n, t: Date.now()}));
    });
}

Anti-Patterns and Performance Pitfalls

The skills/hallmark/references/anti-pattern.md file documents common CSS pitfalls that degrade performance, including the indiscriminate use of will-change and animating layout properties. Additionally, skills/hallmark/references/custom-craft.md warns against clip-path recalculations during animations, recommending transform alternatives instead. Following these guidelines ensures that most Hallmark examples ship with zero-byte JavaScript and minimal paint complexity.

Summary

  • Animate only transform and opacity to ensure GPU compositing and avoid layout recalculations.
  • Respect prefers-reduced-motion to improve accessibility and eliminate unnecessary animation work.
  • Use IntersectionObserver instead of scroll listeners to keep scroll-linked effects off the main thread.
  • Batch DOM changes with document.startViewTransition for theme switches to prevent layout thrash.
  • Cache network requests in localStorage with TTL logic to speed up subsequent page loads.

Frequently Asked Questions

What CSS properties does Hallmark ban to maintain 60fps performance?

Hallmark explicitly bans animating width, height, top, left, margin, and padding as defined in skills/hallmark/references/motion.md. These properties trigger layout recalculations on every frame, whereas transform and opacity are GPU-composited and never affect layout.

How does Hallmark respect user motion preferences while maintaining functionality?

Every animation is wrapped in a @media (prefers-reduced-motion: reduce) block that overrides transitions with instant state changes or removes them entirely. This ensures the interface remains functional and accessible without forcing motion on users who have disabled it in their system settings.

Why does Hallmark use IntersectionObserver instead of scroll event listeners?

IntersectionObserver runs off the main thread and only triggers callbacks when observed elements enter the viewport, whereas scroll listeners fire synchronously and can execute hundreds of times per second. This change eliminates scroll jank and reduces CPU usage on low-end devices.

How does the GitHub star count caching mechanism work?

As implemented in site/js/main.js, the star count is stored in localStorage with a timestamp. If the cached data is less than one hour old (3,600,000ms), the UI displays the cached value immediately. The system only re-fetches from the GitHub API after the TTL expires or on first visit, preventing network latency from blocking the initial render.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →