# Pattern Background

> A hero framed by its own pattern: the grid lines up on your content, four hairlines run along its edges with crop marks on the corners, the rest fades out. Grid, dots, crosses or diagonals. No images.

Backgrounds & textures · background, pattern, grid, dots, frame, hero · HTML, CSS, vanilla JS · no dependencies · updated 2026-10-20
Demo: https://kodza.dev/components/pattern-background/

## Files

The code below, served as files: byte for byte what the Kodza site copies (the CSS includes its theme guard).

- [`kodza-pattern-background.css`](https://kodza.dev/registry/pattern-background/kodza-pattern-background.css) · 23.9 KB
- [`kodza-pattern-background.js`](https://kodza.dev/registry/pattern-background/kodza-pattern-background.js) · 8.0 KB
- [`kodza-pattern-background.html`](https://kodza.dev/registry/pattern-background/kodza-pattern-background.html) · 1.0 KB · the markup, to adapt and paste into your page

**Can you run shell commands? Download the CSS and the JS, don't retype them.** Retyping costs thousands of tokens and can drop or change lines of the CSS. Put the files where your stack expects them (Use it says where: change the `-o` paths):

```bash
curl -fsSL --create-dirs -o kodza/kodza-pattern-background.css https://kodza.dev/registry/pattern-background/kodza-pattern-background.css
curl -fsSL --create-dirs -o kodza/kodza-pattern-background.js https://kodza.dev/registry/pattern-background/kodza-pattern-background.js
```

Then load the files from your page as Use it shows, instead of pasting the content. The HTML is the part you adapt (text, links, modifiers): paste it from the Code section. Copy the CSS and the JS from the Code section only if you can't run commands, exactly as they are. (Windows PowerShell: `curl.exe`.)

## API

- Root: `[data-kodza-pattern-background]`
- Variables (set them on the root element itself: it declares its own defaults):
  - `--kz-pattern-background-bg: #f2f2f0` — the paper
  - `--kz-pattern-background-ink: #0a0a0a` — the pattern, the text, the button
  - `--kz-pattern-background-muted: rgba(10, 10, 10, 0.62)`
  - `--kz-pattern-background-size: 40px` — one cell, in px (dots sit at half of it)
  - `--kz-pattern-background-line: 1px` — stroke, and dot radius
  - `--kz-pattern-background-strength: 1` — 0.5 is fainter, 2 is twice as present
  - `--kz-pattern-background-air: 24px` — the least room between the content and its frame
  - `--kz-pattern-background-mark: 11px` — the crosses on the corners of the frame
  - `--kz-pattern-background-fade-x: 14%` — how far the left and right edges fade
  - `--kz-pattern-background-fade-y: 24%` — same, top and bottom
  - `--kz-pattern-background-draw: 1.6s` — --draw: how long the whole trace takes
  - `--kz-pattern-background-ease: cubic-bezier(0.33, 1, 0.68, 1)`
  - `--kz-pattern-background-height: 600px`
  - `--kz-pattern-background-width: 1152px` — the text column
  - `--kz-pattern-background-side: clamp(20px, 5vw, 64px)`
- Modifiers: `.kodza-pattern-background--cross`, `.kodza-pattern-background--diagonal`, `.kodza-pattern-background--dots`, `.kodza-pattern-background--draw`
- Attributes:
  - `data-kodza-pattern-background`
- Lifecycle: `kodza:init` / `kodza:destroy` on `document`, with `{ detail: { root } }` to target a container.

## Dark mode

The values Kodza uses on a dark background (`#0a0a0a`). For a page that follows the system theme, add them after the component's CSS:

```css
@media (prefers-color-scheme: dark) {
  .kodza-pattern-background {
    --kz-pattern-background-bg: #0a0a0a;
    --kz-pattern-background-ink: #f2f2f0;
    --kz-pattern-background-muted: rgba(242, 242, 240, 0.62);
  }
}
```

A site with its own theme switch puts the same rules under its dark selector instead (`[data-theme="dark"] .kodza-pattern-background { … }`); a single dark section, under that section's class.

## Use it

### Plain HTML page

- CSS: `kodza/kodza-pattern-background.css` next to the page (the Files commands as they are), linked in `<head>`: `<link rel="stylesheet" href="kodza/kodza-pattern-background.css">`. Without a shell, paste it in a `<style>` in `<head>` instead. It only touches `kodza-` classes.
- HTML: where the component goes. Paste it as many times as you need: instances never interfere.
- JS: `kodza/kodza-pattern-background.js`, loaded with `<script src="kodza/kodza-pattern-background.js" defer></script>` in `<head>`. Without a shell, paste it in a `<script>` before `</body>` (or in `<head>`: it waits for the page). One copy per page, whatever the number of instances.
- Restyle with the `--kz-…` variables, set on the component's root element itself (the root declares its own defaults, so a value on an ancestor is ignored), and with the modifiers / attributes listed under API.
- Font: it takes the page's own. The Kodza look is Geist: `<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600;700&family=Geist+Mono:wght@400;500&display=swap">`.
- Content added later (AJAX navigation, a SPA route, a builder preview): start it with `document.dispatchEvent(new CustomEvent('kodza:init', { detail: { root: container } }))`; before removing it, the same call with `'kodza:destroy'` stops it and puts the original markup back.

### Claude artifact (claude.ai)

- Paste the CSS in a `<style>` and the JS in a `<script>` at the end, in the artifact itself: it loads stylesheets only from Google Fonts and scripts only from a few CDNs, so no `<link>` or `<script src>` to Kodza.
- Writing the artifact file with a shell? Insert the downloaded files (Files) with a command instead of retyping them: a line `KODZA-CSS` alone inside the `<style>`, `KODZA-JS` inside the `<script>`, then `sed -e '/^KODZA-CSS$/{r kodza/kodza-pattern-background.css' -e 'd' -e '}' -e '/^KODZA-JS$/{r kodza/kodza-pattern-background.js' -e 'd' -e '}' page.html > artifact.html`.
- Font: load Geist from Google Fonts (allowed): `<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600;700&family=Geist+Mono:wght@400;500&display=swap">`.
- Dark mode: the viewer sets `data-theme="dark"` or `"light"` on `<html>`, or nothing when it follows the system. Write the Dark mode rules twice: inside `@media (prefers-color-scheme: dark)` with `:root:not([data-theme="light"])` in front of each selector, and with `:root[data-theme="dark"]` in front of each selector.

### WordPress

- HTML: a **Custom HTML** block (or a pattern / template part). WordPress keeps this markup as it is only for users allowed to post unfiltered HTML (Administrators; Super Admins on multisite): for anyone else it strips tags such as `<svg>`, `<input>` or `<dialog>`, so add it from the theme (a pattern or template part) instead.
- This one spans the page: a block theme keeps a Custom HTML block to the width of the text column, so put it inside a **Group** block set to **Full width**, with **Inner blocks use content width** turned off.
- CSS and JS: the downloaded files go in the theme, under `wp-content/themes/<your-theme>/kodza/` (the child theme if there is one), enqueued from its functions.php (or a code-snippets plugin):
  ```php
  add_action('wp_enqueue_scripts', function () {
    $dir = get_stylesheet_directory() . '/kodza/';
    $uri = get_stylesheet_directory_uri() . '/kodza/';
    wp_enqueue_style('kodza-pattern-background', $uri . 'kodza-pattern-background.css', [], filemtime($dir . 'kodza-pattern-background.css'));
    wp_enqueue_script('kodza-pattern-background', $uri . 'kodza-pattern-background.js', [], filemtime($dir . 'kodza-pattern-background.js'), true);
  });
  ```
  Scoped to `kodza-` classes, the CSS never touches the theme. One copy of the JS, whatever the number of blocks.
- No access to the theme files? The CSS can also go, as it is, in **Appearance → Customize → Additional CSS** (classic theme) or **Styles → Additional CSS** in the Site Editor (block theme).
- Theme with AJAX page transitions: fire `kodza:init` on the new content after each swap (and `kodza:destroy` on the old one).
- Performance plugin (WP Rocket, LiteSpeed Cache, Perfmatters…) with **Delay JavaScript execution** turned on: add `kodza` to the scripts it must not delay. Otherwise the component waits for the visitor's first scroll, tap or mouse move, and that first click can be lost.
- Colours: it takes the colour of the text around it (or keeps its own monochrome): nothing to point at the theme.
- WordPress turns straight quotes and `--` into typographic ones in the text of a Custom HTML block (not inside `<code>` or `<pre>`).
- Put it in a full-width Group. Your own text goes inside __content, and the frame fits whatever you put there; pick the pattern with --dots, --cross or --diagonal, and set --kz-pattern-background-bg and --kz-pattern-background-ink to your theme's colours.

#### With Elementor

- HTML: an **HTML** widget (Elementor shows it to administrators only). It keeps the code exactly as pasted, straight quotes included.
- This one spans the page: give the widget its own container, **Content Width: Full Width**, padding 0.
- CSS and JS: as above. Elementor's Site Settings → Theme Style styles every button, field, link and image of the page, components included; the theme guard at the end of the CSS keeps the component's buttons, fields and link colours as they are.
- The Elementor editor redraws each widget after the page has loaded: without the script below, the component stays still in the editor preview (the published page works). Add it once per site, in the footer like the component's script (`kodza/elementor-editor.js`, or a code-snippets plugin):

```js
// Kodza in the Elementor editor: the editor redraws each HTML widget after the page has loaded.
// Start Kodza in what it draws, stop it in what it replaces or removes. Once per site.
window.addEventListener('elementor/frontend/init', () => {
  if (!elementorFrontend.isEditMode()) return;
  class KodzaWidget extends elementorModules.frontend.handlers.Base {
    onInit() {
      super.onInit();
      this.kodza = [...this.$element[0].children];
      document.dispatchEvent(new CustomEvent('kodza:init', { detail: { root: this.$element[0] } }));
    }
    onDestroy() {
      super.onDestroy();
      this.kodza.forEach((root) => document.dispatchEvent(new CustomEvent('kodza:destroy', { detail: { root } })));
    }
  }
  elementorFrontend.elementsHandler.attachHandler('html', KodzaWidget);
});
```

- No child theme: everything can go in the HTML widget instead, `<style>` with the CSS, the HTML, then `<script>` with the JS. It works on the page and in the editor without the script above.

### Shopify

- Files: `assets/kodza-pattern-background.css` and `assets/kodza-pattern-background.js` (the Files commands with `-o assets/…`).
- In the section: `{{ 'kodza-pattern-background.css' | asset_url | stylesheet_tag }}` and `<script src="{{ 'kodza-pattern-background.js' | asset_url }}" defer></script>`, then the HTML.
- The theme editor re-renders sections: restart them with
  `document.addEventListener('shopify:section:load', (e) => document.dispatchEvent(new CustomEvent('kodza:init', { detail: { root: e.target } })));`
  and the same with `shopify:section:unload` → `kodza:destroy`.

### React / Next.js

- Files, side by side (e.g. in `components/kodza/`): `PatternBackground.tsx` (below), the CSS block under Code saved as `pattern-background.css` and the JS block as `pattern-background.js`, both as they are. The HTML block is already inside the component: skip it.
- Use it: `import { PatternBackground } from '@/components/kodza/PatternBackground';` then `<PatternBackground />`. Props: `variant` ("cross", "diagonal", "dots", "draw", or several in an array), `vars` (the `--kz-…` variables listed under API, e.g. `vars={{ '--kz-pattern-background-bg': … }}`), `className`, `style` and the other props of its `<section>` (`onClick`, `aria-…`).
- Next.js App Router: it is a Client Component already (`'use client'`). Pages Router: Next.js takes global CSS only in `pages/_app`, so move the `import './pattern-background.css'` line there.
- Safe under StrictMode, client-side navigation and prop changes: when a prop changes, the `key` gives React a fresh element, `kodza:destroy` stops the script on the old one and `kodza:init` starts it on the new one.

```tsx
'use client';
// Kodza — Pattern Background, for React. From https://kodza.dev/components/pattern-background/
// Kodza License: free to use, client work included. Not to resell or redistribute, ported or not: https://kodza.dev/docs/#print-license
// Next to this file: pattern-background.css and pattern-background.js, the component's CSS and JS from Kodza, as they are.
// Yours to edit: the markup is plain JSX below.
// The script changes this markup once React has rendered it (it adds layers, splits text, moves
// nodes), so React must not patch it afterwards. When a prop changes, the key gives React a fresh
// element: kodza:destroy stops the script on the old one, kodza:init starts it on the new one.
import { useRef, useEffect, type ComponentPropsWithoutRef, type ComponentRef, type CSSProperties } from 'react';
import './pattern-background.css';

type Variant = "cross" | "diagonal" | "dots" | "draw";

export type PatternBackgroundProps = Omit<ComponentPropsWithoutRef<'section'>, "children" | "variant" | "vars"> & {
  /** Modifier class(es): .kodza-pattern-background--cross, .kodza-pattern-background--diagonal, .kodza-pattern-background--dots, .kodza-pattern-background--draw */
  variant?: Variant | Variant[];
  /** Settings, as CSS variables on the root */
  vars?: {
    /** the paper — default: #f2f2f0 */
    "--kz-pattern-background-bg"?: string | number;
    /** the pattern, the text, the button — default: #0a0a0a */
    "--kz-pattern-background-ink"?: string | number;
    /** default: rgba(10, 10, 10, 0.62) */
    "--kz-pattern-background-muted"?: string | number;
    /** one cell, in px (dots sit at half of it) — default: 40px */
    "--kz-pattern-background-size"?: string | number;
    /** stroke, and dot radius — default: 1px */
    "--kz-pattern-background-line"?: string | number;
    /** 0.5 is fainter, 2 is twice as present — default: 1 */
    "--kz-pattern-background-strength"?: string | number;
    /** the least room between the content and its frame — default: 24px */
    "--kz-pattern-background-air"?: string | number;
    /** the crosses on the corners of the frame — default: 11px */
    "--kz-pattern-background-mark"?: string | number;
    /** how far the left and right edges fade — default: 14% */
    "--kz-pattern-background-fade-x"?: string | number;
    /** same, top and bottom — default: 24% */
    "--kz-pattern-background-fade-y"?: string | number;
    /** --draw: how long the whole trace takes — default: 1.6s */
    "--kz-pattern-background-draw"?: string | number;
    /** default: cubic-bezier(0.33, 1, 0.68, 1) */
    "--kz-pattern-background-ease"?: string | number;
    /** default: 600px */
    "--kz-pattern-background-height"?: string | number;
    /** the text column — default: 1152px */
    "--kz-pattern-background-width"?: string | number;
    /** default: clamp(20px, 5vw, 64px) */
    "--kz-pattern-background-side"?: string | number;
  };
  className?: string;
};

export function PatternBackground({ variant, vars, className, style, ...rest }: PatternBackgroundProps) {
  const ref = useRef<ComponentRef<'section'>>(null);
  const classes = ["kodza-pattern-background", "kodza-pattern-background--draw", ...([] as string[]).concat(variant ?? []).map((v) => `kodza-pattern-background--${v}`), className].filter(Boolean).join(' ');
  // Everything the markup is made of: when it changes, React gets a fresh element (see the top)
  const key = JSON.stringify([classes, vars, style, rest]);

  // Starts the script on this element once React has rendered it, stops it when it goes
  useEffect(() => {
    const root = ref.current;
    let live = true;
    // @ts-ignore: a plain script, no types to find
    import('./pattern-background.js').then(() => {
      if (live) document.dispatchEvent(new CustomEvent('kodza:init', { detail: { root } }));
    });
    return () => {
      live = false;
      document.dispatchEvent(new CustomEvent('kodza:destroy', { detail: { root } }));
    };
  }, [key]);

  return (
    <section data-kodza-pattern-background="" key={key} {...rest} className={classes} style={{ ...vars, ...style } as CSSProperties} ref={ref}>
      <div className="kodza-pattern-background__content">
        <p className="kodza-pattern-background__eyebrow">Four patterns · zero images</p>
        <div className="kodza-pattern-background__grid">
          <h1 className="kodza-pattern-background__title">
            <span className="kodza-pattern-background__line">Measure twice.</span>
            {' '}
            <span className="kodza-pattern-background__line">Ship once.</span>
          </h1>
          <div className="kodza-pattern-background__side">
            <p className="kodza-pattern-background__lead">Hairlines that line up on your words and fade at the edges, so they never argue with your headline. It looks planned. It was measured.</p>
            <p className="kodza-pattern-background__actions">
              <a className="kodza-pattern-background__button" href="#">Copy the CSS</a>
              {' '}
              <a className="kodza-pattern-background__button kodza-pattern-background__button--ghost" href="#">Read the docs</a>
            </p>
          </div>
        </div>
      </div>
    </section>
  );
}
```

## Code

```html
<section class="kodza-pattern-background kodza-pattern-background--draw" data-kodza-pattern-background>
  <div class="kodza-pattern-background__content">
    <p class="kodza-pattern-background__eyebrow">Four patterns · zero images</p>
    <div class="kodza-pattern-background__grid">
      <h1 class="kodza-pattern-background__title">
        <span class="kodza-pattern-background__line">Measure twice.</span>
        <span class="kodza-pattern-background__line">Ship once.</span>
      </h1>
      <div class="kodza-pattern-background__side">
        <p class="kodza-pattern-background__lead">Hairlines that line up on your words and fade at the edges, so they never argue with your headline. It looks planned. It was measured.</p>
        <p class="kodza-pattern-background__actions">
          <a class="kodza-pattern-background__button" href="#">Copy the CSS</a>
          <a class="kodza-pattern-background__button kodza-pattern-background__button--ghost" href="#">Read the docs</a>
        </p>
      </div>
    </div>
  </div>
</section>
```

```css
/* Kodza License: free to use, client work included. Not to resell or redistribute, ported or not: https://kodza.dev/docs/#print-license */
/* Kodza — Pattern Background
   A hero framed by its own pattern. The script measures the content and lines the pattern up on
   it: four hairlines run along its edges right across the section, a small cross marks each
   corner like a crop mark, and the content sits on whole cells. The rest of the pattern stays
   faint and fades out at the edges. Grid by default; --dots, --cross and --diagonal swap the
   pattern, and --draw traces it all out of the content when the section arrives. No images.
   Everything is tuned with the --kz-pattern-background-* variables. */
.kodza-pattern-background {
  --kz-pattern-background-bg: #f2f2f0;            /* the paper */
  --kz-pattern-background-ink: #0a0a0a;           /* the pattern, the text, the button */
  --kz-pattern-background-muted: rgba(10, 10, 10, 0.62);
  --kz-pattern-background-size: 40px;             /* one cell, in px (dots sit at half of it) */
  --kz-pattern-background-line: 1px;              /* stroke, and dot radius */
  --kz-pattern-background-strength: 1;            /* 0.5 is fainter, 2 is twice as present */
  --kz-pattern-background-air: 24px;              /* the least room between the content and its frame */
  --kz-pattern-background-mark: 11px;             /* the crosses on the corners of the frame */
  --kz-pattern-background-fade-x: 14%;            /* how far the left and right edges fade */
  --kz-pattern-background-fade-y: 24%;            /* same, top and bottom */
  --kz-pattern-background-draw: 1.6s;             /* --draw: how long the whole trace takes */
  --kz-pattern-background-ease: cubic-bezier(0.33, 1, 0.68, 1);
  --kz-pattern-background-height: 600px;
  --kz-pattern-background-width: 1152px;          /* the text column */
  --kz-pattern-background-side: clamp(20px, 5vw, 64px);

  position: relative;
  isolation: isolate;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  min-height: var(--kz-pattern-background-height);
  padding: 120px var(--kz-pattern-background-side) 72px;
  overflow: hidden;
  background: var(--kz-pattern-background-bg);
  color: var(--kz-pattern-background-ink);
}

/* The pattern: two layers behind the frame, faded on all four edges. Centred on the section until
   the script has measured the content. The grid puts its rows in ::before and its columns in
   ::after; the other patterns use ::before alone. */
.kodza-pattern-background::before,
.kodza-pattern-background::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -2;
  background-position: center;
  background-position: round(50%, 1px) round(50%, 1px);
  pointer-events: none;
  -webkit-mask-image:
    linear-gradient(to right, transparent, #000 var(--kz-pattern-background-fade-x), #000 calc(100% - var(--kz-pattern-background-fade-x)), transparent),
    linear-gradient(to bottom, transparent, #000 var(--kz-pattern-background-fade-y), #000 calc(100% - var(--kz-pattern-background-fade-y)), transparent);
  mask-image:
    linear-gradient(to right, transparent, #000 var(--kz-pattern-background-fade-x), #000 calc(100% - var(--kz-pattern-background-fade-x)), transparent),
    linear-gradient(to bottom, transparent, #000 var(--kz-pattern-background-fade-y), #000 calc(100% - var(--kz-pattern-background-fade-y)), transparent);
  -webkit-mask-composite: source-in;
  mask-composite: intersect;
}

/* Grid: rows */
.kodza-pattern-background::before {
  background-image: linear-gradient(to bottom, color-mix(in srgb, var(--kz-pattern-background-ink) calc(var(--kz-pattern-background-strength) * 9%), transparent) 0 var(--kz-pattern-background-line), transparent 0);
  background-size: 100% var(--kz-pattern-background-size);
}

/* Grid: columns */
.kodza-pattern-background::after {
  background-image: linear-gradient(to right, color-mix(in srgb, var(--kz-pattern-background-ink) calc(var(--kz-pattern-background-strength) * 9%), transparent) 0 var(--kz-pattern-background-line), transparent 0);
  background-size: var(--kz-pattern-background-size) 100%;
}

/* --dots: a dot every half cell */
.kodza-pattern-background--dots::before {
  background-image: radial-gradient(circle, color-mix(in srgb, var(--kz-pattern-background-ink) calc(var(--kz-pattern-background-strength) * 16%), transparent) var(--kz-pattern-background-line), transparent calc(var(--kz-pattern-background-line) + 0.6px));
  background-size: calc(var(--kz-pattern-background-size) / 2) calc(var(--kz-pattern-background-size) / 2);
}

/* --cross: the grid kept only around its crossings, so each one becomes a small plus */
.kodza-pattern-background--cross::before {
  background-image:
    linear-gradient(to bottom, transparent 50%, color-mix(in srgb, var(--kz-pattern-background-ink) calc(var(--kz-pattern-background-strength) * 16%), transparent) 0 calc(50% + var(--kz-pattern-background-line)), transparent 0),
    linear-gradient(to right, transparent 50%, color-mix(in srgb, var(--kz-pattern-background-ink) calc(var(--kz-pattern-background-strength) * 16%), transparent) 0 calc(50% + var(--kz-pattern-background-line)), transparent 0);
  background-size: calc(var(--kz-pattern-background-size) * 2) calc(var(--kz-pattern-background-size) * 2);
  -webkit-mask-image:
    radial-gradient(circle at calc(50% + var(--kz-pattern-background-line) / 2) calc(50% + var(--kz-pattern-background-line) / 2), #000 5px, transparent 5.5px),
    linear-gradient(to right, transparent, #000 var(--kz-pattern-background-fade-x), #000 calc(100% - var(--kz-pattern-background-fade-x)), transparent),
    linear-gradient(to bottom, transparent, #000 var(--kz-pattern-background-fade-y), #000 calc(100% - var(--kz-pattern-background-fade-y)), transparent);
  mask-image:
    radial-gradient(circle at calc(50% + var(--kz-pattern-background-line) / 2) calc(50% + var(--kz-pattern-background-line) / 2), #000 5px, transparent 5.5px),
    linear-gradient(to right, transparent, #000 var(--kz-pattern-background-fade-x), #000 calc(100% - var(--kz-pattern-background-fade-x)), transparent),
    linear-gradient(to bottom, transparent, #000 var(--kz-pattern-background-fade-y), #000 calc(100% - var(--kz-pattern-background-fade-y)), transparent);
  -webkit-mask-size: calc(var(--kz-pattern-background-size) * 2) calc(var(--kz-pattern-background-size) * 2), auto, auto;
  mask-size: calc(var(--kz-pattern-background-size) * 2) calc(var(--kz-pattern-background-size) * 2), auto, auto;
  -webkit-mask-position: center;
  -webkit-mask-position: round(50%, 1px) round(50%, 1px);
  mask-position: center;
  mask-position: round(50%, 1px) round(50%, 1px);
  -webkit-mask-repeat: repeat, no-repeat, no-repeat;
  mask-repeat: repeat, no-repeat, no-repeat;
}

/* --diagonal: hatching at 45 degrees, about half a cell apart. The step is set across the row
   (0.7 of a cell, 28px) rather than along the slope, so it is a whole number of pixels and every
   line lands on the same pixels instead of one crisp, one blurred. */
.kodza-pattern-background--diagonal::before {
  background-image: repeating-linear-gradient(-45deg, color-mix(in srgb, var(--kz-pattern-background-ink) calc(var(--kz-pattern-background-strength) * 10%), transparent) 0 var(--kz-pattern-background-line), transparent calc(var(--kz-pattern-background-line) + 0.5px) calc(var(--kz-pattern-background-size) * 0.7 / 1.41421356));
  background-image: repeating-linear-gradient(-45deg, color-mix(in srgb, var(--kz-pattern-background-ink) calc(var(--kz-pattern-background-strength) * 10%), transparent) 0 var(--kz-pattern-background-line), transparent calc(var(--kz-pattern-background-line) + 0.5px) calc(round(var(--kz-pattern-background-size) * 0.7, 1px) / 1.41421356));
  background-size: auto;
}

/* The other patterns live in ::before alone */
.kodza-pattern-background--dots::after,
.kodza-pattern-background--cross::after,
.kodza-pattern-background--diagonal::after {
  content: none;
}

/* Lined up: once the script has measured the content, the pattern starts on the corner of the
   frame, so its lines run along the frame (dots and crosses sit on its crossings). The diagonals
   keep their own rhythm: only the frame counts for them. */
.kodza-pattern-background.is-framed {
  --kz-pattern-background-cx: calc(var(--kz-pattern-background-frame-x) + var(--kz-pattern-background-frame-w) / 2);
  --kz-pattern-background-cy: calc(var(--kz-pattern-background-frame-y) + var(--kz-pattern-background-frame-h) / 2);
}

.kodza-pattern-background.is-framed::before,
.kodza-pattern-background.is-framed::after {
  background-position: var(--kz-pattern-background-frame-x) var(--kz-pattern-background-frame-y);
}

.kodza-pattern-background--dots.is-framed::before {
  background-position: calc(var(--kz-pattern-background-frame-x) - var(--kz-pattern-background-size) / 4 + var(--kz-pattern-background-line) / 2) calc(var(--kz-pattern-background-frame-y) - var(--kz-pattern-background-size) / 4 + var(--kz-pattern-background-line) / 2);
}

.kodza-pattern-background--cross.is-framed::before {
  background-position: calc(var(--kz-pattern-background-frame-x) - var(--kz-pattern-background-size)) calc(var(--kz-pattern-background-frame-y) - var(--kz-pattern-background-size));
  /* Only the crossings move with the frame: the two fades stay on the section */
  -webkit-mask-position: calc(var(--kz-pattern-background-frame-x) - var(--kz-pattern-background-size)) calc(var(--kz-pattern-background-frame-y) - var(--kz-pattern-background-size)), 0 0, 0 0;
  mask-position: calc(var(--kz-pattern-background-frame-x) - var(--kz-pattern-background-size)) calc(var(--kz-pattern-background-frame-y) - var(--kz-pattern-background-size)), 0 0, 0 0;
}

.kodza-pattern-background--diagonal.is-framed::before {
  background-position: center;
  background-position: round(50%, 1px) round(50%, 1px);
}

/* The frame, added by the script: four hairlines a touch stronger than the pattern, along the
   edges of the frame and right across the section, and a cross on each corner. Solid colours
   mixed with the paper, so a line never darkens where it crosses another. */
.kodza-pattern-background__frame {
  --kz-pattern-background-rule: color-mix(in srgb, var(--kz-pattern-background-ink) calc(var(--kz-pattern-background-strength) * 18%), var(--kz-pattern-background-bg));
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
}

.kodza-pattern-background__frame::before,
.kodza-pattern-background__frame::after {
  content: "";
  position: absolute;
  box-sizing: border-box;
}

/* The two rows, from edge to edge: the top and bottom borders of a band as tall as the frame */
.kodza-pattern-background__frame::before {
  top: var(--kz-pattern-background-frame-y);
  right: 0;
  left: 0;
  height: calc(var(--kz-pattern-background-frame-h) + var(--kz-pattern-background-line));
  border-block: var(--kz-pattern-background-line) solid var(--kz-pattern-background-rule);
  transform-origin: var(--kz-pattern-background-cx) 0;
}

/* The two columns, from top to bottom */
.kodza-pattern-background__frame::after {
  top: 0;
  bottom: 0;
  left: var(--kz-pattern-background-frame-x);
  width: calc(var(--kz-pattern-background-frame-w) + var(--kz-pattern-background-line));
  border-inline: var(--kz-pattern-background-line) solid var(--kz-pattern-background-rule);
  transform-origin: 0 var(--kz-pattern-background-cy);
}

/* The crosses, centred on the corners: top left, top right, bottom left, bottom right. Over the
   lines they sit on. */
.kodza-pattern-background__mark {
  --kz-pattern-background-arm: calc((var(--kz-pattern-background-mark) - var(--kz-pattern-background-line)) / 2);
  position: absolute;
  z-index: 1;
  top: calc(var(--kz-pattern-background-frame-y) - var(--kz-pattern-background-arm));
  left: calc(var(--kz-pattern-background-frame-x) - var(--kz-pattern-background-arm));
  width: var(--kz-pattern-background-mark);
  height: var(--kz-pattern-background-mark);
}

.kodza-pattern-background__mark:nth-child(even) {
  left: calc(var(--kz-pattern-background-frame-x) + var(--kz-pattern-background-frame-w) - var(--kz-pattern-background-arm));
}

.kodza-pattern-background__mark:nth-child(n + 3) {
  top: calc(var(--kz-pattern-background-frame-y) + var(--kz-pattern-background-frame-h) - var(--kz-pattern-background-arm));
}

.kodza-pattern-background__mark::before,
.kodza-pattern-background__mark::after {
  content: "";
  position: absolute;
  background: color-mix(in srgb, var(--kz-pattern-background-ink) calc(var(--kz-pattern-background-strength) * 50%), var(--kz-pattern-background-bg));
}

.kodza-pattern-background__mark::before {
  top: var(--kz-pattern-background-arm);
  left: 0;
  width: 100%;
  height: var(--kz-pattern-background-line);
}

.kodza-pattern-background__mark::after {
  top: 0;
  left: var(--kz-pattern-background-arm);
  width: var(--kz-pattern-background-line);
  height: 100%;
}

/* --draw: the trace starts from the content. The four lines grow out of the frame to the edges of
   the section, the crosses mark the corners as they land, then the pattern spreads out from
   behind the words. Played by CSS on page load; the script holds a section that starts below the
   fold until it arrives. */
.kodza-pattern-background--draw .kodza-pattern-background__frame::before {
  animation: kodza-pattern-background-across calc(var(--kz-pattern-background-draw) * 0.56) var(--kz-pattern-background-ease) both;
}

.kodza-pattern-background--draw .kodza-pattern-background__frame::after {
  animation: kodza-pattern-background-down calc(var(--kz-pattern-background-draw) * 0.56) var(--kz-pattern-background-ease) both;
}

.kodza-pattern-background--draw .kodza-pattern-background__mark::before {
  animation: kodza-pattern-background-across calc(var(--kz-pattern-background-draw) * 0.2) var(--kz-pattern-background-ease) calc(var(--kz-pattern-background-draw) * 0.36) both;
}

.kodza-pattern-background--draw .kodza-pattern-background__mark::after {
  animation: kodza-pattern-background-down calc(var(--kz-pattern-background-draw) * 0.2) var(--kz-pattern-background-ease) calc(var(--kz-pattern-background-draw) * 0.36) both;
}

.kodza-pattern-background--draw::before,
.kodza-pattern-background--draw::after {
  animation: kodza-pattern-background-spread calc(var(--kz-pattern-background-draw) * 0.56) var(--kz-pattern-background-ease) calc(var(--kz-pattern-background-draw) * 0.44) both;
}

.kodza-pattern-background--draw.is-waiting::before,
.kodza-pattern-background--draw.is-waiting::after,
.kodza-pattern-background--draw.is-waiting .kodza-pattern-background__frame::before,
.kodza-pattern-background--draw.is-waiting .kodza-pattern-background__frame::after,
.kodza-pattern-background--draw.is-waiting .kodza-pattern-background__mark::before,
.kodza-pattern-background--draw.is-waiting .kodza-pattern-background__mark::after {
  visibility: hidden;
  animation: none;
}

@keyframes kodza-pattern-background-across {
  from {
    transform: scaleX(0);
  }
}

@keyframes kodza-pattern-background-down {
  from {
    transform: scaleY(0);
  }
}

@keyframes kodza-pattern-background-spread {
  from {
    clip-path: inset(var(--kz-pattern-background-cy, 50%) calc(100% - var(--kz-pattern-background-cx, 50%)) calc(100% - var(--kz-pattern-background-cy, 50%)) var(--kz-pattern-background-cx, 50%));
    opacity: 0;
  }
  to {
    clip-path: inset(0);
  }
}

/* On a phone the frame cannot always keep its air in whole cells: the script then pads the content
   inwards (--kz-pattern-background-squeeze), so the words keep their distance from the lines. */
.kodza-pattern-background__content {
  box-sizing: border-box;
  width: 100%;
  max-width: var(--kz-pattern-background-width);
  margin: 0 auto;
  padding-inline: var(--kz-pattern-background-squeeze, 0px);
}

.kodza-pattern-background__eyebrow {
  margin: 0 0 20px;
  color: var(--kz-pattern-background-muted);
  font-family: "Geist Mono", ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 12px;
  line-height: 1.4;
}

.kodza-pattern-background__grid {
  display: grid;
  grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
  align-items: end;
  gap: 28px 64px;
}

.kodza-pattern-background__title {
  margin: 0;
  font-size: clamp(44px, 6.6vw, 88px);
  font-weight: 600;
  line-height: 0.94;
  letter-spacing: -0.055em;
}

.kodza-pattern-background__line {
  display: block;
}

.kodza-pattern-background__lead {
  max-width: 30em;
  margin: 0 0 24px;
  color: var(--kz-pattern-background-muted);
  font-size: clamp(16px, 1.4vw, 18px);
  line-height: 1.5;
  text-wrap: pretty;
}

.kodza-pattern-background__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin: 0;
}

.kodza-pattern-background__button {
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  height: 48px;
  padding: 0 22px;
  border-radius: 999px;
  background: var(--kz-pattern-background-ink);
  color: var(--kz-pattern-background-bg);
  font-size: 15px;
  font-weight: 500;
  letter-spacing: -0.01em;
  text-decoration: none;
  white-space: nowrap;
  transition: opacity 0.18s ease;
}

.kodza-pattern-background__button:hover {
  opacity: 0.86;
}

/* Opaque, so the pattern does not show through the button */
.kodza-pattern-background__button--ghost {
  background: var(--kz-pattern-background-bg);
  color: inherit;
  box-shadow: inset 0 0 0 1px color-mix(in srgb, currentColor 22%, transparent);
}

.kodza-pattern-background__button:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 3px;
}

@media (max-width: 720px) {
  .kodza-pattern-background {
    padding: 96px var(--kz-pattern-background-side) 56px;
  }

  .kodza-pattern-background__grid {
    grid-template-columns: minmax(0, 1fr);
    gap: 24px;
  }
}

/* Reduced motion: the frame and the pattern are simply there */
@media (prefers-reduced-motion: reduce) {
  .kodza-pattern-background::before,
  .kodza-pattern-background::after,
  .kodza-pattern-background__frame::before,
  .kodza-pattern-background__frame::after,
  .kodza-pattern-background__mark::before,
  .kodza-pattern-background__mark::after {
    animation: none !important;
  }

  .kodza-pattern-background__button {
    transition: none;
  }
}

/* kodza-guard 41205ef5bfbd — generated by Kodza, do not edit.
   Theme guard: keeps a site's own CSS (WordPress themes, Bootstrap, CSS resets…) off this
   component. :not(#_) is always true and weighs as much as an id, so these lines win over
   theme rules made of classes and tags. Customise the component through its --kz-* variables. */

/* What the browser gives, where the component sets it only in some states: at the lowest weight, so its own rules below put it back */

:where(.kodza-pattern-background__button):not(#_) {
  outline: revert;
  box-shadow: revert;
  outline-offset: revert;
}

/* What the browser gives, on everything the component leaves to it */

.kodza-pattern-background__frame:not(#_),
.kodza-pattern-background__mark:not(#_),
.kodza-pattern-background__grid:not(#_),
.kodza-pattern-background__line:not(#_),
.kodza-pattern-background__side:not(#_) {
  box-sizing: revert;
}

.kodza-pattern-background__eyebrow:not(#_) {
  border: revert;
  padding: revert;
  border-radius: revert;
  outline: revert;
  text-decoration: revert;
  transition: revert;
  list-style: revert;
  box-sizing: revert;
  float: revert;
  position: revert;
  background-color: revert;
  background-image: revert;
  box-shadow: revert;
  outline-offset: revert;
  appearance: revert;
  max-width: revert;
  max-height: revert;
  font-weight: revert;
  font-style: revert;
  letter-spacing: revert;
  text-transform: revert;
  text-underline-offset: revert;
  text-align: revert;
  text-indent: revert;
  text-shadow: revert;
  vertical-align: revert;
}

.kodza-pattern-background__title:not(#_) {
  border: revert;
  padding: revert;
  border-radius: revert;
  outline: revert;
  text-decoration: revert;
  transition: revert;
  list-style: revert;
  box-sizing: revert;
  float: revert;
  position: revert;
  background-color: revert;
  background-image: revert;
  box-shadow: revert;
  outline-offset: revert;
  appearance: revert;
  max-width: revert;
  max-height: revert;
  color: revert;
  font-family: revert;
  font-style: revert;
  text-transform: revert;
  text-underline-offset: revert;
  text-align: revert;
  text-indent: revert;
  text-shadow: revert;
  vertical-align: revert;
}

.kodza-pattern-background__lead:not(#_) {
  border: revert;
  padding: revert;
  border-radius: revert;
  outline: revert;
  text-decoration: revert;
  transition: revert;
  list-style: revert;
  box-sizing: revert;
  float: revert;
  position: revert;
  background-color: revert;
  background-image: revert;
  box-shadow: revert;
  outline-offset: revert;
  appearance: revert;
  max-height: revert;
  font-family: revert;
  font-weight: revert;
  font-style: revert;
  letter-spacing: revert;
  text-transform: revert;
  text-underline-offset: revert;
  text-align: revert;
  text-indent: revert;
  text-shadow: revert;
  vertical-align: revert;
}

.kodza-pattern-background__actions:not(#_) {
  border: revert;
  padding: revert;
  border-radius: revert;
  outline: revert;
  text-decoration: revert;
  transition: revert;
  list-style: revert;
  box-sizing: revert;
  float: revert;
  position: revert;
  background-color: revert;
  background-image: revert;
  box-shadow: revert;
  outline-offset: revert;
  appearance: revert;
  max-width: revert;
  max-height: revert;
  color: revert;
  font-family: revert;
  font-size: revert;
  font-weight: revert;
  font-style: revert;
  line-height: revert;
  letter-spacing: revert;
  text-transform: revert;
  text-underline-offset: revert;
  text-align: revert;
  text-indent: revert;
  text-shadow: revert;
  vertical-align: revert;
}

.kodza-pattern-background__button:not(#_) {
  border: revert;
  margin: revert;
  list-style: revert;
  float: revert;
  position: revert;
  appearance: revert;
  max-width: revert;
  max-height: revert;
  font-family: revert;
  font-style: revert;
  line-height: revert;
  text-transform: revert;
  text-underline-offset: revert;
  text-align: revert;
  text-indent: revert;
  text-shadow: revert;
  vertical-align: revert;
}

.kodza-pattern-background__eyebrow:not(#_):where(:not([hidden])),
.kodza-pattern-background__title:not(#_):where(:not([hidden])),
.kodza-pattern-background__lead:not(#_):where(:not([hidden])) {
  display: revert;
}

.kodza-pattern-background__eyebrow:not(#_):where([hidden]),
.kodza-pattern-background__title:not(#_):where([hidden]),
.kodza-pattern-background__lead:not(#_):where([hidden]) {
  display: none;
}

.kodza-pattern-background:not(#_)::before,
.kodza-pattern-background:not(#_)::after,
.kodza-pattern-background__mark:not(#_)::before,
.kodza-pattern-background__mark:not(#_)::after {
  box-sizing: revert;
}

/* The component's own rules, one step stronger: real buttons and fields, what it sets on tags themes style (links, lists, headings, images…), and the root width (a theme may narrow it, never widen it) */

.kodza-pattern-background__eyebrow:not(#_) {
  margin: 0 0 20px;
  color: var(--kz-pattern-background-muted);
  font-family: "Geist Mono", ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 12px;
  line-height: 1.4;
}

.kodza-pattern-background__title:not(#_) {
  margin: 0;
  font-size: clamp(44px, 6.6vw, 88px);
  font-weight: 600;
  line-height: 0.94;
  letter-spacing: -0.055em;
}

.kodza-pattern-background__lead:not(#_) {
  max-width: 30em;
  margin: 0 0 24px;
  color: var(--kz-pattern-background-muted);
  font-size: clamp(16px, 1.4vw, 18px);
  line-height: 1.5;
}

.kodza-pattern-background__actions:not(#_) {
  display: flex;
  margin: 0;
}

.kodza-pattern-background__button:not(#_) {
  box-sizing: border-box;
  display: inline-flex;
  padding: 0 22px;
  border-radius: 999px;
  background: var(--kz-pattern-background-ink);
  color: var(--kz-pattern-background-bg);
  font-size: 15px;
  font-weight: 500;
  letter-spacing: -0.01em;
  text-decoration: none;
  transition: opacity 0.18s ease;
}

.kodza-pattern-background__button--ghost:not(#_) {
  background: var(--kz-pattern-background-bg);
  color: inherit;
  box-shadow: inset 0 0 0 1px color-mix(in srgb, currentColor 22%, transparent);
}

.kodza-pattern-background__button:focus-visible:not(#_) {
  outline: 2px solid currentColor;
  outline-offset: 3px;
}

@media (prefers-reduced-motion: reduce) {
  .kodza-pattern-background__button:not(#_) {
    transition: none;
  }
}
```

```js
// Kodza License: free to use, client work included. Not to resell or redistribute, ported or not: https://kodza.dev/docs/#print-license
// Kodza — Pattern Background
// The pattern itself is CSS. The script lines it up on the content: it measures the content, fits
// a frame of whole cells around it, adds the four hairlines and the corner crosses, and moves the
// pattern so its lines run along the frame. It measures again whenever the section or its content
// changes size. With --draw, a section that starts below the fold waits there until it scrolls
// in, so the trace plays in front of the reader, not off-screen.
(() => {
  const SELECTOR = '[data-kodza-pattern-background]';
  const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
  const EDGE = 8; // a frame line never closer than this to the edge of the section
  const FOLD = 2.5; // kodza:replay folds the trace away this many times faster than it draws

  // One axis of the frame: whole cells, centred on the content, about `air` away from it. Fewer
  // cells when the section has no room for more (on a phone), never fewer than the content needs.
  const fit = (start, length, extent, cell, air) => {
    const least = Math.max(1, Math.ceil(length / cell));
    let cells = Math.max(least, Math.round((length + air * 2) / cell));
    if ((cells * cell - length) / 2 < air / 2) cells++;
    for (; cells > least; cells--) {
      const from = start - (cells * cell - length) / 2;
      if (from >= EDGE && from + cells * cell <= extent - EDGE) break;
    }
    return [Math.round(start - (cells * cell - length) / 2), cells * cell];
  };

  // One instance, with its own state: two copies on a page never interfere. Everything it
  // starts is tied to `signal` (listeners get { signal }, observers and timers stop on its
  // 'abort' event), so kodza:destroy stops it all.
  function mount(root, signal) {
    const content = root.querySelector('.kodza-pattern-background__content');
    if (!content) return;

    const frame = document.createElement('div');
    frame.className = 'kodza-pattern-background__frame';
    frame.setAttribute('aria-hidden', 'true');
    frame.innerHTML = '<span class="kodza-pattern-background__mark"></span>'.repeat(4);
    root.prepend(frame);
    signal.addEventListener('abort', () => frame.remove());

    // Measured in the section's own pixels: a parent that scales it (a preview, a theme) is
    // divided out. --cross repeats every two cells, so its frame takes pairs of cells when the
    // section has room for them.
    const place = () => {
      const style = getComputedStyle(root);
      const size = parseFloat(style.getPropertyValue('--kz-pattern-background-size')) || 40;
      const air = parseFloat(style.getPropertyValue('--kz-pattern-background-air')) || 0;
      const unit = root.classList.contains('kodza-pattern-background--cross') ? size * 2 : size;
      const outer = root.getBoundingClientRect();
      const inner = content.getBoundingClientRect();
      const scale = outer.width / root.offsetWidth || 1;
      const axis = (start, length, extent) => {
        const box = fit(start, length, extent, unit, air);
        return box[0] >= 0 && box[0] + box[1] <= extent ? box : fit(start, length, extent, size, air);
      };
      const left = (inner.left - outer.left) / scale - root.clientLeft;
      const [x, w] = axis(left, inner.width / scale, root.clientWidth);
      const [y, h] = axis((inner.top - outer.top) / scale - root.clientTop, inner.height / scale, root.clientHeight);
      // Too close to the lines (a phone): the words step in, the box and the frame stay put
      const room = Math.min(left - x, x + w - left - inner.width / scale);
      root.style.setProperty('--kz-pattern-background-squeeze', `${Math.max(0, Math.ceil(air / 2 - room))}px`);
      root.style.setProperty('--kz-pattern-background-frame-x', `${x}px`);
      root.style.setProperty('--kz-pattern-background-frame-y', `${y}px`);
      root.style.setProperty('--kz-pattern-background-frame-w', `${w}px`);
      root.style.setProperty('--kz-pattern-background-frame-h', `${h}px`);
      root.classList.add('is-framed');
    };
    place();
    const resize = new ResizeObserver(place);
    resize.observe(root);
    resize.observe(content, { box: 'border-box' });
    signal.addEventListener('abort', () => resize.disconnect());

    const drawing = root.classList.contains('kodza-pattern-background--draw') && !reduceMotion;

    // kodza:replay folds the trace back into the content, measures again and draws it once more
    // (a demo that swaps the pattern, a slide that comes back): nothing vanishes at once. A
    // function passed as event.detail runs in between, while nothing is drawn.
    const then = [];
    let folding = false;
    const draw = () => {
      folding = false;
      if (signal.aborted) return;
      then.splice(0).forEach((fn) => fn());
      place();
      if (!drawing) return;
      root.classList.add('is-waiting');
      void root.offsetWidth;
      root.classList.remove('is-waiting');
    };
    root.addEventListener('kodza:replay', (event) => {
      if (typeof event.detail === 'function') then.push(event.detail);
      if (folding) return;
      const trace = drawing ? root.getAnimations({ subtree: true }).filter((a) => a.animationName?.startsWith('kodza-pattern-background-')) : [];
      if (!trace.length) return draw();
      // Played backwards on one shared clock, from wherever the trace is: the pattern folds first,
      // then the crosses, then the lines run back into the content
      const at = Math.max(...trace.map((a) => a.currentTime));
      folding = true;
      trace.forEach((a) => {
        const { endTime } = a.effect.getComputedTiming();
        if (at > endTime) a.effect.updateTiming({ endDelay: at - endTime });
        a.currentTime = at;
        a.playbackRate = -FOLD;
        a.play();
      });
      Promise.all(trace.map((a) => a.finished)).then(draw, draw);
    }, { signal });

    if (!drawing) return;

    // Already in view: the trace that CSS started on load plays on
    const box = root.getBoundingClientRect();
    if (box.top < innerHeight * 0.75 && box.bottom > 0) return;

    root.classList.add('is-waiting');
    const observer = new IntersectionObserver((entries) => {
      if (!entries[0].isIntersecting) return;
      observer.disconnect();
      root.classList.remove('is-waiting');
    }, { rootMargin: '0px 0px -25% 0px' });
    observer.observe(root);
    signal.addEventListener('abort', () => observer.disconnect());
  }

  // Kodza lifecycle, the same in every component: `kodza:init` starts the instances inside
  // event.detail.root (the whole page by default), `kodza:destroy` stops them and puts their
  // markup back. For themes and apps that swap content without a page load.
  const instances = new WeakMap();
  const find = (scope = document) => [scope, ...scope.querySelectorAll(SELECTOR)].filter((el) => el.matches?.(SELECTOR));
  const init = (scope) => find(scope).forEach((root) => {
    if (root.classList.contains('is-ready')) return; // already running, maybe from another copy of this script
    const controller = new AbortController();
    instances.set(root, { controller, html: root.innerHTML, attrs: [...root.attributes].map((a) => [a.name, a.value]) });
    mount(root, controller.signal);
    root.classList.add('is-ready');
  });
  const destroy = (scope) => find(scope).forEach((root) => {
    const instance = instances.get(root);
    if (!instance) return;
    instances.delete(root);
    instance.controller.abort();
    [...root.attributes].forEach((a) => root.removeAttribute(a.name));
    instance.attrs.forEach(([name, value]) => root.setAttribute(name, value));
    root.innerHTML = instance.html;
  });
  document.addEventListener('kodza:init', (event) => init(event.detail?.root));
  document.addEventListener('kodza:destroy', (event) => destroy(event.detail?.root));
  init();
  // Loaded in <head>, the script runs before the markup exists: once more when it is parsed
  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', () => init(), { once: true });
})();
```

Free to use, from Kodza (https://kodza.dev).
