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

> Optimize Nutlope/hallmark landing pages for 60fps using GPU-composited properties, IntersectionObserver, and View Transitions API. Achieve smoother animations and eliminate layout thrash.

- Repository: [Hassan El Mghari/hallmark](https://github.com/Nutlope/hallmark)
- Tags: performance
- Published: 2026-07-15

---

**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`](https://github.com/Nutlope/hallmark/blob/main/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`](https://github.com/Nutlope/hallmark/blob/main/skills/hallmark/references/motion.md), ensuring animations feel responsive while completing quickly enough to avoid jank on low-end devices.

```css
.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`](https://github.com/Nutlope/hallmark/blob/main/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.

```javascript
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`](https://github.com/Nutlope/hallmark/blob/main/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.

```javascript
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`](https://github.com/Nutlope/hallmark/blob/main/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.

```javascript
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`](https://github.com/Nutlope/hallmark/blob/main/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`](https://github.com/Nutlope/hallmark/blob/main/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`](https://github.com/Nutlope/hallmark/blob/main/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`](https://github.com/Nutlope/hallmark/blob/main/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.