# Header Mega

> A full site header whose mega menus share one panel: from a link to the next it changes shape and slides instead of closing, a soft pill follows the pointer, and below 900 px the links become a drawer with levels.

Header · header, mega menu, nav, dropdown, navbar, mobile menu, wp_nav_menu · HTML, CSS, vanilla JS · no dependencies · updated 2026-10-28
Demo: https://kodza.dev/components/header-mega/
Source on GitHub: https://github.com/kodza-dev/kodza/tree/main/components/header-mega

Takes the whole screen (or reacts to the page scroll): give it a page, not a small box.

## Files

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

- [`kodza-header-mega.css`](https://kodza.dev/registry/header-mega/kodza-header-mega.css) · 48.3 KB
- [`kodza-header-mega.js`](https://kodza.dev/registry/header-mega/kodza-header-mega.js) · 55.1 KB
- [`kodza-header-mega.html`](https://kodza.dev/registry/header-mega/kodza-header-mega.html) · 7.8 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-header-mega.css https://kodza.dev/registry/header-mega/kodza-header-mega.css
curl -fsSL --create-dirs -o kodza/kodza-header-mega.js https://kodza.dev/registry/header-mega/kodza-header-mega.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: `.kodza-header-mega`
- Variables (set them on the root element itself: it declares its own defaults):
  - `--kz-header-mega-bg: transparent`
  - `--kz-header-mega-fg: #0a0a0a`
  - `--kz-header-mega-muted: rgba(10, 10, 10, 0.62)`
  - `--kz-header-mega-soft: rgba(10, 10, 10, 0.06)` — the pill under the pointer, the card
  - `--kz-header-mega-surface: #ffffff` — the panel
  - `--kz-header-mega-border: rgba(10, 10, 10, 0.1)`
  - `--kz-header-mega-shadow: 0 18px 40px -16px rgba(10, 10, 10, 0.3)`
  - `--kz-header-mega-ink: #0a0a0a` — the call to action
  - `--kz-header-mega-on-ink: #ffffff`
  - `--kz-header-mega-height: 72px`
  - `--kz-header-mega-width: 1200px` — the row, centred
  - `--kz-header-mega-pad: 24px`
  - `--kz-header-mega-gap: 8px` — between the bar and the panel
  - `--kz-header-mega-radius: 14px`
  - `--kz-header-mega-duration: 0.45s` — the panel changing shape, the pill gliding
  - `--kz-header-mega-ease: cubic-bezier(0.22, 1, 0.36, 1)`
  - `--kz-header-mega-menu-bg: #0a0a0a` — the drawer, below 900 px
  - `--kz-header-mega-menu-fg: #f2f2f0`
  - `--kz-header-mega-menu-muted: rgba(242, 242, 240, 0.62)`
  - `--kz-header-mega-menu-rule: rgba(242, 242, 240, 0.14)`
  - `--kz-header-mega-menu-ink: rgba(10, 10, 10, 0.06)` — the Menu button
  - `--kz-header-mega-menu-on-ink: #0a0a0a`
  - `--kz-header-mega-menu-gap: 10px` — between the drawer and the edges of the screen
  - `--kz-header-mega-menu-radius: 20px`
  - `--kz-header-mega-menu-pad: clamp(20px, 6cqw, 28px)`
  - `--kz-header-mega-menu-width: min(440px, calc(100cqw - 2 * var(--kz-header-mega-menu-gap)))`
  - `--kz-header-mega-menu-size: clamp(24px, calc(2.6cqh + 14px), 34px)`
  - `--kz-header-mega-menu-shade: 0.5` — how dark the page gets under the drawer
  - `--kz-header-mega-menu-shadow: 0 24px 64px -16px rgba(0, 0, 0, 0.5)`
  - `--kz-header-mega-menu-duration: 0.8s`
  - `--kz-header-mega-menu-ease: cubic-bezier(0.32, 0.72, 0, 1)`
- Attributes:
  - `data-all`
  - `data-page`
  - `data-side`
- Lifecycle: `kodza:init` / `kodza:destroy` on `document`, with `{ detail: { root } }` to target a container.

## Keyboard

What each key does, by where the focus is. Keep it working when you adapt the HTML: same elements, same classes.

- In an open menu (focus in `.kodza-header-mega__pane`):
  - `ArrowDown` / `ArrowUp`: Next or previous row, up from the first back to its link
  - `Home` / `End`: First or last row
  - `Escape`: Closes it, focus back on its link
- On a link with a menu (focus in `.kodza-header-mega__trigger`):
  - `Enter` / `Space`: Opens or closes its menu
  - `ArrowDown`: Opens its menu, focus on the first row
- Along the bar (focus in `.kodza-header-mega__links`):
  - `ArrowRight` / `ArrowLeft`: Next or previous link, an open menu follows
- Anywhere, while a menu is open (while `.kodza-header-mega.is-open` is on the page):
  - `Escape`: Closes the menu
- On the Menu button, below 900 px (focus in `.kodza-header-mega__toggle`):
  - `Enter` / `Space`: Opens the drawer on the current branch
- On a row with a level (focus in `.kodza-header-mega__branch`):
  - `ArrowRight` / `Enter` / `Space`: Opens its level, focus on its first row
- Inside a level (focus in `.kodza-header-mega__panel`, while `.kodza-header-mega__back.is-deep` is on the page):
  - `ArrowLeft` / `Escape`: Back up one level, focus on the row you came from
- In the drawer (focus in `.kodza-header-mega__panel`):
  - `ArrowDown` / `ArrowUp`: Next or previous row, round and round
  - `Home` / `End`: First or last row
  - `Tab` / `Shift+Tab`: Stays inside the drawer, round and round
  - `PageUp` / `PageDown`: Held back, the page behind stays put
  - `Escape`: Closes it from the first level, focus back on Menu

## 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-header-mega {
    --kz-header-mega-fg: #f2f2f0;
    --kz-header-mega-muted: rgba(242, 242, 240, 0.62);
    --kz-header-mega-soft: rgba(255, 255, 255, 0.08);
    --kz-header-mega-surface: #161616;
    --kz-header-mega-border: rgba(255, 255, 255, 0.12);
    --kz-header-mega-shadow: 0 18px 40px -16px rgba(0, 0, 0, 0.8);
    --kz-header-mega-ink: #f2f2f0;
    --kz-header-mega-on-ink: #0a0a0a;
    --kz-header-mega-menu-bg: #f2f2f0;
    --kz-header-mega-menu-fg: #0a0a0a;
    --kz-header-mega-menu-muted: rgba(10, 10, 10, 0.62);
    --kz-header-mega-menu-rule: rgba(10, 10, 10, 0.12);
    --kz-header-mega-menu-ink: rgba(255, 255, 255, 0.08);
    --kz-header-mega-menu-on-ink: #f2f2f0;
  }
}
```

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

Every colour is a variable on the header: set --kz-header-mega-fg, -muted, -soft, -surface, -border, -ink and -on-ink for a dark bar and panel (the demo's dark band has the recipe). The phone drawer has its own --kz-header-mega-menu-* set, so it can stay dark or turn to paper.

Its images have dark versions next to them (`kz-float-1400.webp` → `kz-float-dark-1400.webp`). To swap them on a dark page: `<picture><source media="(prefers-color-scheme: dark)" srcset="…-dark-….webp"><img …></picture>`.

## Use it

### Plain HTML page

- CSS: `kodza/kodza-header-mega.css` next to the page (the Files commands as they are), linked in `<head>`: `<link rel="stylesheet" href="kodza/kodza-header-mega.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.
- Where it goes, step by step. (1) At the top of the page, as its only header: it fills the width it is given and centres its row at --kz-header-mega-width. (2) Put the logo in the first link, your main pages in the nav: a link of its own (kodza-header-mega__top), or a button followed by a pane for a big menu. (3) In a pane: columns of entries (a name and a line each) under a small label, and an optional card with a picture on the right. Mark the current page with aria-current="page": its link gets a dot, and so does its section in the bar. (4) Sign in and the call-to-action go in the end block; below 520 px only the Menu button stays. (5) Nothing to write for phones: the drawer lists the same links, a big menu becoming a level, unless you fill its ul yourself.
- JS: `kodza/kodza-header-mega.js`, loaded with `<script src="kodza/kodza-header-mega.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-header-mega.css' -e 'd' -e '}' -e '/^KODZA-JS$/{r kodza/kodza-header-mega.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">`.
- Images: the artifact blocks every other host, `kodza.dev` included, without an error. Embed each one as a `data:` URI, with a command if you can run one (`curl -s <url> | base64`; the 1400 px files weigh 10 to 30 KB), never by typing base64 out. If you can't run commands, keep the `<img>` with its `width` and `height`, and tell the user the picture stays blank inside the artifact.
- 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.
- Where it goes, step by step. (1) At the top of the page, as its only header: it fills the width it is given and centres its row at --kz-header-mega-width. (2) Put the logo in the first link, your main pages in the nav: a link of its own (kodza-header-mega__top), or a button followed by a pane for a big menu. (3) In a pane: columns of entries (a name and a line each) under a small label, and an optional card with a picture on the right. Mark the current page with aria-current="page": its link gets a dot, and so does its section in the bar. (4) Sign in and the call-to-action go in the end block; below 520 px only the Menu button stays. (5) Nothing to write for phones: the drawer lists the same links, a big menu becoming a level, unless you fill its ul yourself.

### 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-header-mega', $uri . 'kodza-header-mega.css', [], filemtime($dir . 'kodza-header-mega.css'));
    wp_enqueue_script('kodza-header-mega', $uri . 'kodza-header-mega.js', [], filemtime($dir . 'kodza-header-mega.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 the site header (Site Editor → Patterns → Header, or the theme's header.php) in place of the theme's own header. A big menu is a button followed by its pane: write its columns by hand, or print a wp_nav_menu() sub-menu inside a column with 'items_wrap' => '%3$s' and the entry class added by a walker. The phone drawer is written from the bar's links; to use the theme's menu there instead, fill the drawer's empty ul with wp_nav_menu(['theme_location' => 'primary', 'container' => false]) and it reads its sub-menus as levels. Logged in, the drawer stays below the admin bar.

#### 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. This one reads the page around it, and a script in the widget runs before the rest of the page exists: put its `<script>` in an HTML widget at the bottom of the page instead.

### Shopify

- Files: `assets/kodza-header-mega.css` and `assets/kodza-header-mega.js` (the Files commands with `-o assets/…`).
- In the section: `{{ 'kodza-header-mega.css' | asset_url | stylesheet_tag }}` and `<script src="{{ 'kodza-header-mega.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`.
- Where it goes, step by step. (1) At the top of the page, as its only header: it fills the width it is given and centres its row at --kz-header-mega-width. (2) Put the logo in the first link, your main pages in the nav: a link of its own (kodza-header-mega__top), or a button followed by a pane for a big menu. (3) In a pane: columns of entries (a name and a line each) under a small label, and an optional card with a picture on the right. Mark the current page with aria-current="page": its link gets a dot, and so does its section in the bar. (4) Sign in and the call-to-action go in the end block; below 520 px only the Menu button stays. (5) Nothing to write for phones: the drawer lists the same links, a big menu becoming a level, unless you fill its ul yourself.

### React / Next.js

- Files, side by side (e.g. in `components/kodza/`): `HeaderMega.tsx` (below), the CSS block under Code saved as `header-mega.css` and the JS block as `header-mega.js`, both as they are. The HTML block is already inside the component: skip it.
- Use it: `import { HeaderMega } from '@/components/kodza/HeaderMega';` then `<HeaderMega />`. Props: `vars` (the `--kz-…` variables listed under API, e.g. `vars={{ '--kz-header-mega-bg': … }}`), `className`, `style` and the other props of its `<header>` (`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 './header-mega.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.
- Built from your data (tabs, rows, options)? Add what the markup is made of to the `key`, as plain values (ids, text, numbers). What the visitor changes (the open tab, the sort, what they type) is the script's: follow it with `onClick` or `onInput` on the root, and change it from your code as a visitor would (click the tab; set the field's value, then dispatch a bubbling `input` event). Fields stay uncontrolled (`defaultValue`, `name`).

```tsx
'use client';
// Kodza — Header Mega, for React. From https://kodza.dev/components/header-mega/
// 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: header-mega.css and header-mega.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.
// Built from your data (tabs, rows, options)? Put what the markup is made of in the key, as plain
// values (ids, text, numbers): a change starts the script again on fresh markup. What the visitor
// changes (the open tab, the sort, what they type) is the script's: keep it out of the key, follow
// it with onClick or onInput on the root, and change it from your code as a visitor would (click
// the tab; set the field's value, then dispatch a bubbling input event).
import { useRef, useEffect, type ComponentPropsWithoutRef, type ComponentRef, type CSSProperties } from 'react';
import './header-mega.css';

export type HeaderMegaProps = Omit<ComponentPropsWithoutRef<'header'>, "children" | "vars"> & {
  /** Settings, as CSS variables on the root */
  vars?: {
    /** default: transparent */
    "--kz-header-mega-bg"?: string | number;
    /** default: #0a0a0a */
    "--kz-header-mega-fg"?: string | number;
    /** default: rgba(10, 10, 10, 0.62) */
    "--kz-header-mega-muted"?: string | number;
    /** the pill under the pointer, the card — default: rgba(10, 10, 10, 0.06) */
    "--kz-header-mega-soft"?: string | number;
    /** the panel — default: #ffffff */
    "--kz-header-mega-surface"?: string | number;
    /** default: rgba(10, 10, 10, 0.1) */
    "--kz-header-mega-border"?: string | number;
    /** default: 0 18px 40px -16px rgba(10, 10, 10, 0.3) */
    "--kz-header-mega-shadow"?: string | number;
    /** the call to action — default: #0a0a0a */
    "--kz-header-mega-ink"?: string | number;
    /** default: #ffffff */
    "--kz-header-mega-on-ink"?: string | number;
    /** default: 72px */
    "--kz-header-mega-height"?: string | number;
    /** the row, centred — default: 1200px */
    "--kz-header-mega-width"?: string | number;
    /** default: 24px */
    "--kz-header-mega-pad"?: string | number;
    /** between the bar and the panel — default: 8px */
    "--kz-header-mega-gap"?: string | number;
    /** default: 14px */
    "--kz-header-mega-radius"?: string | number;
    /** the panel changing shape, the pill gliding — default: 0.45s */
    "--kz-header-mega-duration"?: string | number;
    /** default: cubic-bezier(0.22, 1, 0.36, 1) */
    "--kz-header-mega-ease"?: string | number;
    /** the drawer, below 900 px — default: #0a0a0a */
    "--kz-header-mega-menu-bg"?: string | number;
    /** default: #f2f2f0 */
    "--kz-header-mega-menu-fg"?: string | number;
    /** default: rgba(242, 242, 240, 0.62) */
    "--kz-header-mega-menu-muted"?: string | number;
    /** default: rgba(242, 242, 240, 0.14) */
    "--kz-header-mega-menu-rule"?: string | number;
    /** the Menu button — default: rgba(10, 10, 10, 0.06) */
    "--kz-header-mega-menu-ink"?: string | number;
    /** default: #0a0a0a */
    "--kz-header-mega-menu-on-ink"?: string | number;
    /** between the drawer and the edges of the screen — default: 10px */
    "--kz-header-mega-menu-gap"?: string | number;
    /** default: 20px */
    "--kz-header-mega-menu-radius"?: string | number;
    /** default: clamp(20px, 6cqw, 28px) */
    "--kz-header-mega-menu-pad"?: string | number;
    /** default: min(440px, calc(100cqw - 2 * var(--kz-header-mega-menu-gap))) */
    "--kz-header-mega-menu-width"?: string | number;
    /** default: clamp(24px, calc(2.6cqh + 14px), 34px) */
    "--kz-header-mega-menu-size"?: string | number;
    /** how dark the page gets under the drawer — default: 0.5 */
    "--kz-header-mega-menu-shade"?: string | number;
    /** default: 0 24px 64px -16px rgba(0, 0, 0, 0.5) */
    "--kz-header-mega-menu-shadow"?: string | number;
    /** default: 0.8s */
    "--kz-header-mega-menu-duration"?: string | number;
    /** default: cubic-bezier(0.32, 0.72, 0, 1) */
    "--kz-header-mega-menu-ease"?: string | number;
  };
  className?: string;
};

export function HeaderMega({ vars, className, style, ...rest }: HeaderMegaProps) {
  const ref = useRef<ComponentRef<'header'>>(null);
  const classes = ["kodza-header-mega", 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('./header-mega.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 (
    <header key={key} {...rest} className={classes} style={{ ...vars, ...style } as CSSProperties} ref={ref}>
      <div className="kodza-header-mega__row">
        <a className="kodza-header-mega__logo" href="#" aria-label="Harbor, home">
          <svg viewBox="0 0 24 22" width="22" height="20" aria-hidden="true">
            <path d="M2 11a10 10 0 0 1 20 0z" />
            <path d="M2.46 14h19.08a10 10 0 0 1-19.08 0z" />
          </svg>
          <span>harbor</span>
        </a>
        <nav className="kodza-header-mega__menu" aria-label="Main">
          <ul className="kodza-header-mega__links">
            <li className="kodza-header-mega__spot">
              <button className="kodza-header-mega__trigger" type="button" aria-expanded="false">Product<span className="kodza-header-mega__caret" aria-hidden="true" /></button>
              <div className="kodza-header-mega__pane" hidden>
                <div className="kodza-header-mega__inner">
                  <div className="kodza-header-mega__col">
                    <p className="kodza-header-mega__label">Ship</p>
                    <a className="kodza-header-mega__entry" href="#">
                      <span className="kodza-header-mega__name">Deploys</span>
                      <span className="kodza-header-mega__line">Push to main. That's the whole ritual.</span>
                    </a>
                    {' '}
                    <a className="kodza-header-mega__entry" href="#" aria-current="page">
                      <span className="kodza-header-mega__name">Previews</span>
                      <span className="kodza-header-mega__line">A live URL for every branch.</span>
                    </a>
                    {' '}
                    <a className="kodza-header-mega__entry" href="#">
                      <span className="kodza-header-mega__name">Rollbacks</span>
                      <span className="kodza-header-mega__line">One click back to the last good build.</span>
                    </a>
                  </div>
                  <div className="kodza-header-mega__col">
                    <p className="kodza-header-mega__label">Watch</p>
                    <a className="kodza-header-mega__entry" href="#">
                      <span className="kodza-header-mega__name">Logs</span>
                      <span className="kodza-header-mega__line">Every request, searchable for 30 days.</span>
                    </a>
                    {' '}
                    <a className="kodza-header-mega__entry" href="#">
                      <span className="kodza-header-mega__name">Alerts</span>
                      <span className="kodza-header-mega__line">We ping. You sleep.</span>
                    </a>
                    {' '}
                    <a className="kodza-header-mega__entry" href="#">
                      <span className="kodza-header-mega__name">Uptime</span>
                      <span className="kodza-header-mega__line">A status page clients can read.</span>
                    </a>
                  </div>
                  <a className="kodza-header-mega__card" href="#">
                    <span className="kodza-header-mega__media">
                      <img src="https://kodza.dev/media/kz-float-1400.webp" alt="" width="1400" height="933" loading="lazy" />
                    </span>
                    {' '}
                    <span className="kodza-header-mega__tag">New in v4</span>
                    {' '}
                    <span className="kodza-header-mega__headline">Previews boot in nine seconds</span>
                  </a>
                </div>
              </div>
            </li>
            <li className="kodza-header-mega__spot">
              <button className="kodza-header-mega__trigger" type="button" aria-expanded="false">Solutions<span className="kodza-header-mega__caret" aria-hidden="true" /></button>
              <div className="kodza-header-mega__pane" hidden>
                <div className="kodza-header-mega__inner">
                  <div className="kodza-header-mega__col">
                    <p className="kodza-header-mega__label">By team</p>
                    <a className="kodza-header-mega__entry" href="#">
                      <span className="kodza-header-mega__name">Agencies</span>
                      <span className="kodza-header-mega__line">Forty client sites, one login.</span>
                    </a>
                    {' '}
                    <a className="kodza-header-mega__entry" href="#">
                      <span className="kodza-header-mega__name">Freelancers</span>
                      <span className="kodza-header-mega__line">Bill the work, not the build minutes.</span>
                    </a>
                    {' '}
                    <a className="kodza-header-mega__entry" href="#">
                      <span className="kodza-header-mega__name">Startups</span>
                      <span className="kodza-header-mega__line">From prototype to prod, no rewrite.</span>
                    </a>
                    {' '}
                    <a className="kodza-header-mega__entry" href="#">
                      <span className="kodza-header-mega__name">Enterprise</span>
                      <span className="kodza-header-mega__line">SSO, audit logs and a real SLA.</span>
                    </a>
                  </div>
                  <a className="kodza-header-mega__card" href="#">
                    <span className="kodza-header-mega__media">
                      <img src="https://kodza.dev/media/kz-steps-1400.webp" alt="" width="1400" height="933" loading="lazy" />
                    </span>
                    {' '}
                    <span className="kodza-header-mega__tag">Case study</span>
                    {' '}
                    <span className="kodza-header-mega__headline">A ferry app that ships 30 times a day</span>
                  </a>
                </div>
              </div>
            </li>
            <li className="kodza-header-mega__spot">
              <button className="kodza-header-mega__trigger" type="button" aria-expanded="false">Resources<span className="kodza-header-mega__caret" aria-hidden="true" /></button>
              <div className="kodza-header-mega__pane" hidden>
                <div className="kodza-header-mega__inner">
                  <div className="kodza-header-mega__col">
                    <p className="kodza-header-mega__label">Learn</p>
                    <a className="kodza-header-mega__entry" href="#">
                      <span className="kodza-header-mega__name">Docs</span>
                      <span className="kodza-header-mega__line">Written by the people who built it.</span>
                    </a>
                    {' '}
                    <a className="kodza-header-mega__entry" href="#">
                      <span className="kodza-header-mega__name">Guides</span>
                      <span className="kodza-header-mega__line">From zero to prod before lunch.</span>
                    </a>
                    {' '}
                    <a className="kodza-header-mega__entry" href="#">
                      <span className="kodza-header-mega__name">Changelog</span>
                      <span className="kodza-header-mega__line">What shipped. Even on Fridays.</span>
                    </a>
                  </div>
                  <div className="kodza-header-mega__col">
                    <p className="kodza-header-mega__label">Talk</p>
                    <a className="kodza-header-mega__entry" href="#">
                      <span className="kodza-header-mega__name">Community</span>
                      <span className="kodza-header-mega__line">Questions answered by humans.</span>
                    </a>
                    {' '}
                    <a className="kodza-header-mega__entry" href="#">
                      <span className="kodza-header-mega__name">Support</span>
                      <span className="kodza-header-mega__line">A real person reads every ticket.</span>
                    </a>
                    {' '}
                    <a className="kodza-header-mega__entry" href="#">
                      <span className="kodza-header-mega__name">Status</span>
                      <span className="kodza-header-mega__line">All green, and we'd say otherwise.</span>
                    </a>
                  </div>
                </div>
              </div>
            </li>
            <li className="kodza-header-mega__spot">
              <a className="kodza-header-mega__top" href="#">Pricing</a>
            </li>
            <li className="kodza-header-mega__spot">
              <a className="kodza-header-mega__top" href="#">Blog</a>
            </li>
          </ul>
        </nav>
        <div className="kodza-header-mega__end">
          <a className="kodza-header-mega__sign" href="#">Sign in</a>
          {' '}
          <a className="kodza-header-mega__cta" href="#">Start free</a>
          {' '}
          <button className="kodza-header-mega__toggle" type="button" aria-haspopup="dialog" aria-expanded="false"><span className="kodza-header-mega__lines" aria-hidden="true" />Menu</button>
        </div>
      </div>
      <div className="kodza-header-mega__panel" role="dialog" aria-modal="true" aria-label="Menu" tabIndex={-1} hidden>
        <span className="kodza-header-mega__shade" aria-hidden="true" />
        <div className="kodza-header-mega__sheet">
          <div className="kodza-header-mega__bar">
            <button className="kodza-header-mega__close" type="button">
              <span className="kodza-header-mega__cross" aria-hidden="true" />
              <span className="kodza-header-mega__say">
                <span>Close</span>
              </span>
            </button>
          </div>
          <button className="kodza-header-mega__back" type="button" disabled aria-hidden="true">
            <span className="kodza-header-mega__arrow" aria-hidden="true" />
            <span className="kodza-header-mega__title">
              <span className="kodza-header-mega__heading">
                <span>Menu</span>
              </span>
            </span>
          </button>
          <nav className="kodza-header-mega__nav" aria-label="Menu">
            <ul />
          </nav>
          <div className="kodza-header-mega__foot">
            <p className="kodza-header-mega__more">
              <a className="kodza-header-mega__start" href="#">Start free</a>
              <a href="#">Sign in</a>
            </p>
            <p className="kodza-header-mega__small">Free for one project. No card.</p>
          </div>
        </div>
      </div>
    </header>
  );
}
```

- Where it goes, step by step. (1) At the top of the page, as its only header: it fills the width it is given and centres its row at --kz-header-mega-width. (2) Put the logo in the first link, your main pages in the nav: a link of its own (kodza-header-mega__top), or a button followed by a pane for a big menu. (3) In a pane: columns of entries (a name and a line each) under a small label, and an optional card with a picture on the right. Mark the current page with aria-current="page": its link gets a dot, and so does its section in the bar. (4) Sign in and the call-to-action go in the end block; below 520 px only the Menu button stays. (5) Nothing to write for phones: the drawer lists the same links, a big menu becoming a level, unless you fill its ul yourself.

## Code

```html
<header class="kodza-header-mega">
  <div class="kodza-header-mega__row">
    <a class="kodza-header-mega__logo" href="#" aria-label="Harbor, home"><svg viewBox="0 0 24 22" width="22" height="20" aria-hidden="true"><path d="M2 11a10 10 0 0 1 20 0z"/><path d="M2.46 14h19.08a10 10 0 0 1-19.08 0z"/></svg><span>harbor</span></a>
    <nav class="kodza-header-mega__menu" aria-label="Main">
      <ul class="kodza-header-mega__links">
        <li class="kodza-header-mega__spot">
          <button class="kodza-header-mega__trigger" type="button" aria-expanded="false">Product<span class="kodza-header-mega__caret" aria-hidden="true"></span></button>
          <div class="kodza-header-mega__pane" hidden>
            <div class="kodza-header-mega__inner">
              <div class="kodza-header-mega__col">
                <p class="kodza-header-mega__label">Ship</p>
                <a class="kodza-header-mega__entry" href="#"><span class="kodza-header-mega__name">Deploys</span><span class="kodza-header-mega__line">Push to main. That's the whole ritual.</span></a>
                <a class="kodza-header-mega__entry" href="#" aria-current="page"><span class="kodza-header-mega__name">Previews</span><span class="kodza-header-mega__line">A live URL for every branch.</span></a>
                <a class="kodza-header-mega__entry" href="#"><span class="kodza-header-mega__name">Rollbacks</span><span class="kodza-header-mega__line">One click back to the last good build.</span></a>
              </div>
              <div class="kodza-header-mega__col">
                <p class="kodza-header-mega__label">Watch</p>
                <a class="kodza-header-mega__entry" href="#"><span class="kodza-header-mega__name">Logs</span><span class="kodza-header-mega__line">Every request, searchable for 30 days.</span></a>
                <a class="kodza-header-mega__entry" href="#"><span class="kodza-header-mega__name">Alerts</span><span class="kodza-header-mega__line">We ping. You sleep.</span></a>
                <a class="kodza-header-mega__entry" href="#"><span class="kodza-header-mega__name">Uptime</span><span class="kodza-header-mega__line">A status page clients can read.</span></a>
              </div>
              <a class="kodza-header-mega__card" href="#">
                <span class="kodza-header-mega__media"><img src="https://kodza.dev/media/kz-float-1400.webp" alt="" width="1400" height="933" loading="lazy"></span>
                <span class="kodza-header-mega__tag">New in v4</span>
                <span class="kodza-header-mega__headline">Previews boot in nine seconds</span>
              </a>
            </div>
          </div>
        </li>
        <li class="kodza-header-mega__spot">
          <button class="kodza-header-mega__trigger" type="button" aria-expanded="false">Solutions<span class="kodza-header-mega__caret" aria-hidden="true"></span></button>
          <div class="kodza-header-mega__pane" hidden>
            <div class="kodza-header-mega__inner">
              <div class="kodza-header-mega__col">
                <p class="kodza-header-mega__label">By team</p>
                <a class="kodza-header-mega__entry" href="#"><span class="kodza-header-mega__name">Agencies</span><span class="kodza-header-mega__line">Forty client sites, one login.</span></a>
                <a class="kodza-header-mega__entry" href="#"><span class="kodza-header-mega__name">Freelancers</span><span class="kodza-header-mega__line">Bill the work, not the build minutes.</span></a>
                <a class="kodza-header-mega__entry" href="#"><span class="kodza-header-mega__name">Startups</span><span class="kodza-header-mega__line">From prototype to prod, no rewrite.</span></a>
                <a class="kodza-header-mega__entry" href="#"><span class="kodza-header-mega__name">Enterprise</span><span class="kodza-header-mega__line">SSO, audit logs and a real SLA.</span></a>
              </div>
              <a class="kodza-header-mega__card" href="#">
                <span class="kodza-header-mega__media"><img src="https://kodza.dev/media/kz-steps-1400.webp" alt="" width="1400" height="933" loading="lazy"></span>
                <span class="kodza-header-mega__tag">Case study</span>
                <span class="kodza-header-mega__headline">A ferry app that ships 30 times a day</span>
              </a>
            </div>
          </div>
        </li>
        <li class="kodza-header-mega__spot">
          <button class="kodza-header-mega__trigger" type="button" aria-expanded="false">Resources<span class="kodza-header-mega__caret" aria-hidden="true"></span></button>
          <div class="kodza-header-mega__pane" hidden>
            <div class="kodza-header-mega__inner">
              <div class="kodza-header-mega__col">
                <p class="kodza-header-mega__label">Learn</p>
                <a class="kodza-header-mega__entry" href="#"><span class="kodza-header-mega__name">Docs</span><span class="kodza-header-mega__line">Written by the people who built it.</span></a>
                <a class="kodza-header-mega__entry" href="#"><span class="kodza-header-mega__name">Guides</span><span class="kodza-header-mega__line">From zero to prod before lunch.</span></a>
                <a class="kodza-header-mega__entry" href="#"><span class="kodza-header-mega__name">Changelog</span><span class="kodza-header-mega__line">What shipped. Even on Fridays.</span></a>
              </div>
              <div class="kodza-header-mega__col">
                <p class="kodza-header-mega__label">Talk</p>
                <a class="kodza-header-mega__entry" href="#"><span class="kodza-header-mega__name">Community</span><span class="kodza-header-mega__line">Questions answered by humans.</span></a>
                <a class="kodza-header-mega__entry" href="#"><span class="kodza-header-mega__name">Support</span><span class="kodza-header-mega__line">A real person reads every ticket.</span></a>
                <a class="kodza-header-mega__entry" href="#"><span class="kodza-header-mega__name">Status</span><span class="kodza-header-mega__line">All green, and we'd say otherwise.</span></a>
              </div>
            </div>
          </div>
        </li>
        <li class="kodza-header-mega__spot"><a class="kodza-header-mega__top" href="#">Pricing</a></li>
        <li class="kodza-header-mega__spot"><a class="kodza-header-mega__top" href="#">Blog</a></li>
      </ul>
    </nav>
    <div class="kodza-header-mega__end">
      <a class="kodza-header-mega__sign" href="#">Sign in</a>
      <a class="kodza-header-mega__cta" href="#">Start free</a>
      <button class="kodza-header-mega__toggle" type="button" aria-haspopup="dialog" aria-expanded="false"><span class="kodza-header-mega__lines" aria-hidden="true"></span>Menu</button>
    </div>
  </div>
  <div class="kodza-header-mega__panel" role="dialog" aria-modal="true" aria-label="Menu" tabindex="-1" hidden>
    <span class="kodza-header-mega__shade" aria-hidden="true"></span>
    <div class="kodza-header-mega__sheet">
      <div class="kodza-header-mega__bar">
        <button class="kodza-header-mega__close" type="button"><span class="kodza-header-mega__cross" aria-hidden="true"></span><span class="kodza-header-mega__say"><span>Close</span></span></button>
      </div>
      <button class="kodza-header-mega__back" type="button" disabled aria-hidden="true"><span class="kodza-header-mega__arrow" aria-hidden="true"></span><span class="kodza-header-mega__title"><span class="kodza-header-mega__heading"><span>Menu</span></span></span></button>
      <nav class="kodza-header-mega__nav" aria-label="Menu">
        <ul></ul>
      </nav>
      <div class="kodza-header-mega__foot">
        <p class="kodza-header-mega__more"><a class="kodza-header-mega__start" href="#">Start free</a><a href="#">Sign in</a></p>
        <p class="kodza-header-mega__small">Free for one project. No card.</p>
      </div>
    </div>
  </div>
</header>
```

```css
/* Kodza License: free to use, client work included. Not to resell or redistribute, ported or not: https://kodza.dev/docs/#print-license */
/* Kodza — Header Mega
   A whole site header: logo, links, a call to action, and one panel under the bar for the big
   menus. From one link to the next the panel doesn't close: it changes shape and slides, the
   old columns leave on the side you come from as the new ones come in from the other, and a
   soft pill glides along the bar under the pointer. Below 900 px wide the links give way to a
   Menu button and a drawer with levels, built from the same links (or from your own list).
   Everything is tuned with the --kz-header-mega-* variables (--kz-header-mega-menu-* for the
   drawer). */
.kodza-header-mega {
  --kz-header-mega-bg: transparent;
  --kz-header-mega-fg: #0a0a0a;
  --kz-header-mega-muted: rgba(10, 10, 10, 0.62);
  --kz-header-mega-soft: rgba(10, 10, 10, 0.06); /* the pill under the pointer, the card */
  --kz-header-mega-surface: #ffffff; /* the panel */
  --kz-header-mega-border: rgba(10, 10, 10, 0.1);
  --kz-header-mega-shadow: 0 18px 40px -16px rgba(10, 10, 10, 0.3);
  --kz-header-mega-ink: #0a0a0a; /* the call to action */
  --kz-header-mega-on-ink: #ffffff;
  --kz-header-mega-height: 72px;
  --kz-header-mega-width: 1200px; /* the row, centred */
  --kz-header-mega-pad: 24px;
  --kz-header-mega-gap: 8px; /* between the bar and the panel */
  --kz-header-mega-radius: 14px;
  --kz-header-mega-duration: 0.45s; /* the panel changing shape, the pill gliding */
  --kz-header-mega-ease: cubic-bezier(0.22, 1, 0.36, 1);
  --kz-header-mega-menu-bg: #0a0a0a; /* the drawer, below 900 px */
  --kz-header-mega-menu-fg: #f2f2f0;
  --kz-header-mega-menu-muted: rgba(242, 242, 240, 0.62);
  --kz-header-mega-menu-rule: rgba(242, 242, 240, 0.14);
  --kz-header-mega-menu-ink: rgba(10, 10, 10, 0.06); /* the Menu button */
  --kz-header-mega-menu-on-ink: #0a0a0a;
  --kz-header-mega-menu-gap: 10px; /* between the drawer and the edges of the screen */
  --kz-header-mega-menu-radius: 20px;
  --kz-header-mega-menu-pad: clamp(20px, 6cqw, 28px);
  --kz-header-mega-menu-width: min(440px, calc(100cqw - 2 * var(--kz-header-mega-menu-gap)));
  --kz-header-mega-menu-size: clamp(24px, calc(2.6cqh + 14px), 34px);
  --kz-header-mega-menu-shade: 0.5; /* how dark the page gets under the drawer */
  --kz-header-mega-menu-shadow: 0 24px 64px -16px rgba(0, 0, 0, 0.5);
  --kz-header-mega-menu-duration: 0.8s;
  --kz-header-mega-menu-ease: cubic-bezier(0.32, 0.72, 0, 1);

  position: relative;
  z-index: 60;
  display: block;
  box-sizing: border-box;
  width: 100%;
  margin: 0;
  padding: 0;
  background: var(--kz-header-mega-bg);
  color: var(--kz-header-mega-fg);
  font-size: 15px;
  line-height: 1.4;
  text-align: left;
  container-type: inline-size;
}

.kodza-header-mega__row {
  display: flex;
  align-items: center;
  gap: 8px;
  box-sizing: border-box;
  max-width: var(--kz-header-mega-width);
  height: var(--kz-header-mega-height);
  margin: 0 auto;
  padding: 0 var(--kz-header-mega-pad);
}

.kodza-header-mega__logo {
  display: inline-flex;
  flex: none;
  align-items: center;
  gap: 8px;
  height: 40px;
  margin: 0 20px 0 0;
  border-radius: 6px;
  color: inherit;
  font-size: 18px;
  font-weight: 600;
  line-height: 1;
  letter-spacing: -0.04em;
  text-decoration: none;
}

.kodza-header-mega__logo svg {
  display: block;
  flex: none;
  fill: currentColor;
}

/* The bar's links. The list and its items stay static: the panel and the pill are placed
   against the header itself */
.kodza-header-mega__menu,
.kodza-header-mega__spot {
  position: static;
  display: block;
  margin: 0;
  padding: 0;
}

.kodza-header-mega__links {
  position: static;
  display: flex;
  align-items: center;
  gap: 2px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.kodza-header-mega__spot {
  list-style: none;
}

.kodza-header-mega__trigger,
.kodza-header-mega__top,
.kodza-header-mega__sign {
  position: relative;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  box-sizing: border-box;
  height: 40px;
  margin: 0;
  padding: 0 14px;
  border: 0;
  border-radius: 999px;
  background: none;
  color: var(--kz-header-mega-muted);
  font: inherit;
  font-size: 15px;
  font-weight: 500;
  line-height: 1;
  letter-spacing: -0.01em;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: color 0.2s;
  -webkit-tap-highlight-color: transparent;
}

.kodza-header-mega__trigger:hover,
.kodza-header-mega__top:hover,
.kodza-header-mega__sign:hover,
.kodza-header-mega__trigger[aria-expanded="true"],
.kodza-header-mega__trigger:focus-visible,
.kodza-header-mega__top:focus-visible,
.kodza-header-mega__sign:focus-visible,
.kodza-header-mega__trigger.is-current,
.kodza-header-mega__top[aria-current="page"] {
  color: var(--kz-header-mega-fg);
}

.kodza-header-mega__trigger:focus-visible,
.kodza-header-mega__top:focus-visible,
.kodza-header-mega__sign:focus-visible,
.kodza-header-mega__logo:focus-visible {
  outline: 2px solid var(--kz-header-mega-fg);
  outline-offset: 2px;
}

/* A small chevron that turns over while its panel is open */
.kodza-header-mega__caret {
  flex: none;
  box-sizing: border-box;
  width: 7px;
  height: 7px;
  margin: -4px 0 0 1px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  rotate: 45deg;
  transition: rotate 0.45s cubic-bezier(0.22, 1, 0.36, 1), translate 0.45s cubic-bezier(0.22, 1, 0.36, 1);
}

.kodza-header-mega__trigger[aria-expanded="true"] .kodza-header-mega__caret {
  rotate: 225deg;
  translate: 0 4px;
}

/* The section you are in: a dot under its name */
.kodza-header-mega__trigger.is-current::after,
.kodza-header-mega__top[aria-current="page"]::after {
  content: "";
  position: absolute;
  bottom: 3px;
  left: 50%;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: currentColor;
  translate: -50% 0;
}

.kodza-header-mega__trigger.is-current::after {
  left: calc(50% - 6.5px); /* under the word, not the chevron */
}

.kodza-header-mega__end {
  display: flex;
  flex: none;
  align-items: center;
  gap: 6px;
  margin: 0 0 0 auto;
}

.kodza-header-mega__cta {
  display: inline-flex;
  align-items: center;
  box-sizing: border-box;
  height: 40px;
  margin: 0;
  padding: 0 18px;
  border-radius: 999px;
  background: var(--kz-header-mega-ink);
  color: var(--kz-header-mega-on-ink);
  font-size: 15px;
  font-weight: 500;
  line-height: 1;
  letter-spacing: -0.01em;
  text-decoration: none;
  white-space: nowrap;
  transition: background-color 0.2s;
}

@media (hover: hover) {
  .kodza-header-mega__cta:hover {
    background: color-mix(in srgb, var(--kz-header-mega-ink) 84%, transparent);
  }
}

.kodza-header-mega__cta:focus-visible {
  outline: 2px solid var(--kz-header-mega-ink);
  outline-offset: 3px;
}

/* The pill that glides along the bar, from the link under the pointer to the next, and back to
   the one whose panel is open */
.kodza-header-mega__glide {
  position: absolute;
  inset: 0;
  z-index: 0;
  background: var(--kz-header-mega-soft);
  clip-path: inset(0 100% 100% 0 round 999px);
  opacity: 0;
  pointer-events: none;
}

.kodza-header-mega.is-ready .kodza-header-mega__glide {
  transition: clip-path var(--kz-header-mega-duration) var(--kz-header-mega-ease), opacity 0.25s;
}

.kodza-header-mega__glide.is-on {
  opacity: 1;
}

/* The one panel: its box changes shape from a menu to the next, and the menu on show is cut to
   that box */
.kodza-header-mega__drop {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 2;
  box-sizing: border-box;
  width: 0;
  height: 0;
  border-radius: var(--kz-header-mega-radius);
  background: var(--kz-header-mega-surface);
  box-shadow: 0 0 0 1px var(--kz-header-mega-border), var(--kz-header-mega-shadow);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}

/* The way down from the bar: the pointer crosses the gap without leaving the menu */
.kodza-header-mega__drop::before {
  content: "";
  position: absolute;
  right: 0;
  bottom: 100%;
  left: 0;
  height: var(--kz-header-mega-gap);
  pointer-events: auto;
}

.kodza-header-mega__pane {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 3;
  display: block;
  box-sizing: border-box;
  max-width: calc(100% - 32px);
  margin: 0;
  padding: 8px;
  color: var(--kz-header-mega-fg);
}

.kodza-header-mega__pane[hidden] {
  display: none;
}

.kodza-header-mega__inner {
  display: flex;
  gap: 4px;
}

.kodza-header-mega__col {
  display: grid;
  flex: 0 1 auto;
  align-content: start;
  width: 256px;
  min-width: 0;
}

.kodza-header-mega__label {
  margin: 0;
  padding: 10px 12px 6px;
  color: var(--kz-header-mega-muted);
  font-family: "Geist Mono", ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 12px;
  font-weight: 400;
  line-height: 1.4;
  letter-spacing: 0;
}

.kodza-header-mega__entry {
  display: grid;
  gap: 2px;
  margin: 0;
  padding: 10px 12px;
  border-radius: 10px;
  color: inherit;
  text-decoration: none;
  transition: background-color 0.2s;
}

@media (hover: hover) {
  .kodza-header-mega__entry:hover {
    background: var(--kz-header-mega-soft);
  }
}

.kodza-header-mega__entry:focus-visible,
.kodza-header-mega__card:focus-visible {
  outline: 2px solid var(--kz-header-mega-fg);
  outline-offset: -2px;
}

.kodza-header-mega__name {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 14px;
  font-weight: 500;
  line-height: 1.3;
  letter-spacing: -0.01em;
}

/* The page you are on */
.kodza-header-mega__entry[aria-current="page"] .kodza-header-mega__name::after {
  content: "";
  flex: none;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: currentColor;
}

.kodza-header-mega__line {
  display: block;
  color: var(--kz-header-mega-muted);
  font-size: 13px;
  line-height: 1.4;
  text-wrap: pretty;
}

.kodza-header-mega__card {
  display: grid;
  flex: none;
  align-content: start;
  gap: 10px;
  box-sizing: border-box;
  width: 248px;
  margin: 0 0 0 4px;
  padding: 8px 8px 14px;
  border-radius: 10px;
  background: var(--kz-header-mega-soft);
  color: inherit;
  text-decoration: none;
}

.kodza-header-mega__media {
  display: block;
  overflow: hidden;
  aspect-ratio: 3 / 2;
  border-radius: 6px;
}

.kodza-header-mega__media img {
  display: block;
  width: 100%;
  max-width: none;
  height: 100%;
  margin: 0;
  object-fit: cover;
  transition: scale 0.6s cubic-bezier(0.22, 1, 0.36, 1);
}

@media (hover: hover) {
  .kodza-header-mega__card:hover .kodza-header-mega__media img {
    scale: 1.04;
  }
}

.kodza-header-mega__tag,
.kodza-header-mega__headline {
  display: block;
  padding: 0 4px;
}

.kodza-header-mega__tag {
  color: var(--kz-header-mega-muted);
  font-family: "Geist Mono", ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 12px;
  line-height: 1.2;
}

.kodza-header-mega__headline {
  font-size: 14px;
  font-weight: 500;
  line-height: 1.35;
  letter-spacing: -0.01em;
  text-wrap: balance;
}

/* ---------- The drawer, below 900 px (Multi-Level Menu) ---------- */
.kodza-header-mega__toggle,
.kodza-header-mega__close {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  height: 40px;
  margin: 0;
  padding: 0 18px 0 16px;
  border: 0;
  border-radius: 999px;
  font: inherit;
  font-size: 15px;
  font-weight: 500;
  line-height: 1;
  letter-spacing: -0.01em;
  white-space: nowrap;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.kodza-header-mega__toggle {
  background: var(--kz-header-mega-menu-ink);
  color: var(--kz-header-mega-menu-on-ink);
}

.kodza-header-mega__close {
  background: var(--kz-header-mega-menu-fg);
  color: var(--kz-header-mega-menu-bg);
}

.kodza-header-mega__toggle:focus-visible {
  outline: 2px solid var(--kz-header-mega-fg);
  outline-offset: 3px;
}

.kodza-header-mega__close:focus-visible,
.kodza-header-mega__more a:focus-visible {
  outline: 2px solid var(--kz-header-mega-menu-fg);
  outline-offset: 4px;
}

/* Two lines on the button: they cross as the drawer comes in, and Close arrives with them
   crossed, in the same place */
.kodza-header-mega__lines,
.kodza-header-mega__cross {
  position: relative;
  flex: none;
  width: 14px;
  height: 8px;
}

.kodza-header-mega__lines::before,
.kodza-header-mega__lines::after,
.kodza-header-mega__cross::before,
.kodza-header-mega__cross::after {
  content: "";
  position: absolute;
  left: 0;
  width: 100%;
  height: 1.5px;
  border-radius: 1px;
  background: currentColor;
  /* On its own layer: drawn the same way at rest and while turning, so no snap at the end */
  will-change: transform;
}

.kodza-header-mega__lines::before,
.kodza-header-mega__cross::before {
  top: 0;
}

.kodza-header-mega__lines::after,
.kodza-header-mega__cross::after {
  top: 6px; /* whole pixels: both lines sit on the pixel grid the same way */
}

.kodza-header-mega__lines::before,
.kodza-header-mega__lines::after {
  transition: clip-path 0.3s cubic-bezier(0.22, 1, 0.36, 1);
}

/* Whole at rest, as an inset: the pointer slides it to a shorter one (from none it would jump) */
.kodza-header-mega__lines::after {
  clip-path: inset(0 0 0 0);
}

.kodza-header-mega__toggle:hover .kodza-header-mega__lines::after {
  clip-path: inset(0 45% 0 0);
}

.kodza-header-mega__toggle[aria-expanded="true"] .kodza-header-mega__lines::after {
  clip-path: inset(0 0 0 0);
}

.kodza-header-mega__cross::before {
  translate: 0 3px;
  rotate: 45deg;
}

.kodza-header-mega__cross::after {
  translate: 0 -3px;
  rotate: -45deg;
}

/* The word rolls in from below its mask */
.kodza-header-mega__say {
  display: grid;
  margin: -3px 0;
  padding: 3px 0;
  overflow: hidden;
}

.kodza-header-mega__say > span {
  display: block;
}

/* The whole screen while it is open: the dimmed page, and the drawer over it */
.kodza-header-mega__panel {
  position: fixed;
  inset: 0;
  z-index: 99990;
  display: block;
  box-sizing: border-box;
  width: 100%;
  height: 100%;
  max-width: none;
  max-height: none;
  margin: 0;
  padding: 0;
  border: 0;
  overflow: hidden;
  background: transparent;
  color: var(--kz-header-mega-menu-fg);
  font: inherit;
  font-size: 15px;
  line-height: 1.4;
  text-align: left;
  container-type: size;
  -webkit-font-smoothing: antialiased;
}

.kodza-header-mega__panel[hidden] {
  display: none;
}

.kodza-header-mega__panel:focus {
  outline: none;
}

/* A click on the dimmed page closes it */
.kodza-header-mega__shade {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, var(--kz-header-mega-menu-shade));
  cursor: pointer;
}

/* The drawer, off the edges of the screen (and below the WordPress admin bar) */
.kodza-header-mega__sheet {
  position: absolute;
  top: calc(var(--wp-admin--admin-bar--height, 0px) + var(--kz-header-mega-menu-gap));
  right: var(--kz-header-mega-menu-gap);
  bottom: var(--kz-header-mega-menu-gap);
  display: grid;
  grid-template-rows: auto auto minmax(0, 1fr) auto;
  box-sizing: border-box;
  width: var(--kz-header-mega-menu-width);
  overflow: hidden;
  border-radius: var(--kz-header-mega-menu-radius);
  background: var(--kz-header-mega-menu-bg);
  color: var(--kz-header-mega-menu-fg);
  box-shadow: var(--kz-header-mega-menu-shadow);
}

.kodza-header-mega__panel.is-left .kodza-header-mega__sheet {
  right: auto;
  left: var(--kz-header-mega-menu-gap);
}

.kodza-header-mega__bar {
  display: flex;
  justify-content: flex-end;
  padding: 14px 14px 0;
}

.kodza-header-mega__panel.is-left .kodza-header-mega__bar {
  justify-content: flex-start;
}

/* The row on top: the name of the level you are in, and from the second level on the way back */
.kodza-header-mega__back {
  position: relative;
  display: flex;
  align-items: center;
  box-sizing: border-box;
  width: 100%;
  height: 48px;
  margin: 8px 0 0;
  padding: 0 var(--kz-header-mega-menu-pad);
  border: 0;
  border-radius: 0;
  background: none;
  color: var(--kz-header-mega-menu-fg);
  font: inherit;
  font-size: 15px;
  font-weight: 500;
  line-height: 1;
  letter-spacing: -0.01em;
  text-align: left;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.kodza-header-mega__back:disabled {
  color: var(--kz-header-mega-menu-muted);
  opacity: 1;
  cursor: default;
}

.kodza-header-mega__back:focus-visible {
  outline: none;
}

.kodza-header-mega__arrow {
  position: absolute;
  top: 50%;
  left: var(--kz-header-mega-menu-pad);
  width: 16px;
  height: 12px;
  opacity: 0;
  translate: -8px -50%;
  transition: opacity 0.15s, translate 0.5s cubic-bezier(0.22, 1, 0.36, 1);
}

.kodza-header-mega__arrow::before,
.kodza-header-mega__arrow::after {
  content: "";
  position: absolute;
  top: 50%;
}

.kodza-header-mega__arrow::before {
  left: 0;
  width: 100%;
  height: 1.5px;
  margin-top: -0.75px;
  border-radius: 1px;
  background: currentColor;
}

.kodza-header-mega__arrow::after {
  left: 1px;
  box-sizing: border-box;
  width: 7px;
  height: 7px;
  border-bottom: 1.5px solid currentColor;
  border-left: 1.5px solid currentColor;
  translate: 0 -50%;
  rotate: 45deg;
}

.kodza-header-mega__title {
  display: block;
  margin: -3px 0 -6px;
  padding: 3px 0 6px;
  overflow: hidden;
  transition: translate 0.5s cubic-bezier(0.22, 1, 0.36, 1);
}

/* The level names are stacked in one cell: the old one rolls up as the new one comes in */
.kodza-header-mega__heading {
  display: grid;
}

.kodza-header-mega__heading > span {
  grid-area: 1 / 1;
  white-space: nowrap;
}

/* The arrow comes in from the left once the name has started to make room for it */
.kodza-header-mega__back.is-deep .kodza-header-mega__arrow {
  opacity: 1;
  translate: 0 -50%;
  transition: opacity 0.3s 0.1s, translate 0.5s cubic-bezier(0.22, 1, 0.36, 1);
}

.kodza-header-mega__back.is-deep .kodza-header-mega__title {
  translate: 26px 0;
}

@media (hover: hover) {
  .kodza-header-mega__back.is-deep:hover .kodza-header-mega__arrow {
    translate: -3px -50%;
  }
}

.kodza-header-mega__back:focus-visible .kodza-header-mega__heading {
  text-decoration: underline;
  text-decoration-thickness: 1.5px;
  text-underline-offset: 3px;
}

/* The levels, one over the other in the same cell: the drawer keeps its size */
.kodza-header-mega__nav {
  display: grid;
  align-content: start;
  min-height: 0;
  padding: 4px var(--kz-header-mega-menu-pad) 8px;
  overflow: hidden auto;
  overscroll-behavior: contain;
}

.kodza-header-mega__list {
  grid-area: 1 / 1;
  display: block;
  margin: 0;
  padding: 0;
  list-style: none;
}

.kodza-header-mega__list[hidden] {
  display: none;
}

.kodza-header-mega__item {
  position: relative;
  display: block;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* A hairline under each row, drawn from the left as the rows come in */
.kodza-header-mega__item::after {
  content: "";
  position: absolute;
  right: 0;
  bottom: 0;
  left: 0;
  height: 1px;
  background: var(--kz-header-mega-menu-rule);
  transform-origin: left;
}

.kodza-header-mega__link {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  box-sizing: border-box;
  width: 100%;
  margin: 0;
  padding: 14px 0 13px;
  border: 0;
  border-radius: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  outline: none;
  -webkit-tap-highlight-color: transparent;
}

/* The mask has the size of its word, so its room for descenders (g, j, p, q, y) and its step
   aside are measured on the word */
.kodza-header-mega__mask {
  display: block;
  min-width: 0;
  margin: -0.06em 0 -0.22em;
  padding: 0.06em 0 0.22em;
  overflow: hidden;
  font-size: var(--kz-header-mega-menu-size);
  transition: translate 0.45s cubic-bezier(0.22, 1, 0.36, 1);
}

.kodza-header-mega__word {
  display: block;
  font-size: var(--kz-header-mega-menu-size);
  font-weight: 500;
  line-height: 0.98;
  letter-spacing: -0.045em;
  white-space: nowrap;
}

/* On the right of a row: how many links a level holds and its chevron, or a dot on the way to
   the current page */
.kodza-header-mega__meta {
  display: flex;
  flex: none;
  align-items: center;
  gap: 10px;
  color: var(--kz-header-mega-menu-muted);
  font-family: "Geist Mono", ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 12px;
  font-weight: 400;
  line-height: 1;
  letter-spacing: 0;
  transition: color 0.25s;
}

.kodza-header-mega__link.is-current .kodza-header-mega__meta::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--kz-header-mega-menu-fg);
}

.kodza-header-mega__branch .kodza-header-mega__meta::after {
  content: "";
  box-sizing: border-box;
  width: 7px;
  height: 7px;
  margin-right: 3px;
  border-top: 1.5px solid currentColor;
  border-right: 1.5px solid currentColor;
  rotate: 45deg;
  transition: translate 0.45s cubic-bezier(0.22, 1, 0.36, 1);
}

/* Pointed at (by a mouse: a tap must not leave it stuck): the word steps aside, the count
   lights up and the chevron leans in. A row's focus does the same, and is drawn on its word */
@media (hover: hover) {
  .kodza-header-mega__link:hover .kodza-header-mega__mask {
    translate: 0.14em 0;
  }

  .kodza-header-mega__link:hover .kodza-header-mega__meta {
    color: var(--kz-header-mega-menu-fg);
  }

  .kodza-header-mega__branch:hover .kodza-header-mega__meta::after {
    translate: 3px 0;
  }
}

.kodza-header-mega__link:focus-visible .kodza-header-mega__mask {
  translate: 0.14em 0;
}

.kodza-header-mega__link:focus-visible .kodza-header-mega__meta {
  color: var(--kz-header-mega-menu-fg);
}

.kodza-header-mega__branch:focus-visible .kodza-header-mega__meta::after {
  translate: 3px 0;
}

.kodza-header-mega__link:focus-visible .kodza-header-mega__word {
  text-decoration: underline;
  text-decoration-thickness: 2px;
  text-underline-offset: 0.12em;
}

.kodza-header-mega__foot {
  display: grid;
  gap: 12px;
  padding: 20px var(--kz-header-mega-menu-pad) 24px;
}

.kodza-header-mega__foot p {
  margin: 0;
}

.kodza-header-mega__more {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 20px;
  font-size: 15px;
  font-weight: 500;
}

.kodza-header-mega__more a {
  border-radius: 2px;
  color: var(--kz-header-mega-menu-fg);
  text-decoration: none;
}

.kodza-header-mega__more a:hover {
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}

/* The call to action, a pill again in the drawer */
.kodza-header-mega__more .kodza-header-mega__start {
  display: inline-flex;
  align-items: center;
  box-sizing: border-box;
  height: 40px;
  padding: 0 18px;
  border-radius: 999px;
  background: var(--kz-header-mega-menu-fg);
  color: var(--kz-header-mega-menu-bg);
  line-height: 1;
  white-space: nowrap;
}

.kodza-header-mega__more .kodza-header-mega__start:hover {
  text-decoration: none;
}

.kodza-header-mega__small {
  color: var(--kz-header-mega-menu-muted);
  font-family: "Geist Mono", ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 12px;
  font-weight: 400;
  line-height: 1.4;
  letter-spacing: 0;
}

/* Below 900 px the links make way for the Menu button and its drawer; below 520 px the call to
   action goes into the drawer too. Last in the file, so they win over each part's display */
@container (max-width: 899px) {
  .kodza-header-mega__menu,
  .kodza-header-mega__sign {
    display: none;
  }
}

@container (min-width: 900px) {
  .kodza-header-mega__toggle {
    display: none;
  }
}

@container (max-width: 519px) {
  .kodza-header-mega__row {
    padding: 0 16px;
  }

  .kodza-header-mega__cta {
    display: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .kodza-header-mega__toggle *,
  .kodza-header-mega__toggle *::before,
  .kodza-header-mega__toggle *::after,
  .kodza-header-mega__caret,
  .kodza-header-mega__glide,
  .kodza-header-mega__media img,
  .kodza-header-mega__arrow,
  .kodza-header-mega__title,
  .kodza-header-mega__mask,
  .kodza-header-mega__meta,
  .kodza-header-mega__meta::after {
    transition-duration: 0.01ms !important;
    transition-delay: 0s !important;
  }
}

/* Dark site: light words, a dark panel, and the drawer is paper
.kodza-header-mega {
  --kz-header-mega-fg: #f2f2f0;
  --kz-header-mega-muted: rgba(242, 242, 240, 0.62);
  --kz-header-mega-soft: rgba(255, 255, 255, 0.08);
  --kz-header-mega-surface: #161616;
  --kz-header-mega-border: rgba(255, 255, 255, 0.12);
  --kz-header-mega-shadow: 0 18px 40px -16px rgba(0, 0, 0, 0.8);
  --kz-header-mega-ink: #f2f2f0;
  --kz-header-mega-on-ink: #0a0a0a;
  --kz-header-mega-menu-bg: #f2f2f0;
  --kz-header-mega-menu-fg: #0a0a0a;
  --kz-header-mega-menu-muted: rgba(10, 10, 10, 0.62);
  --kz-header-mega-menu-rule: rgba(10, 10, 10, 0.12);
  --kz-header-mega-menu-ink: rgba(255, 255, 255, 0.08);
  --kz-header-mega-menu-on-ink: #f2f2f0;
}
*/

/* kodza-guard 082d3102586c — 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-header-mega__logo):not(#_),
:where(.kodza-header-mega__trigger):not(#_),
:where(.kodza-header-mega__card):not(#_),
:where(.kodza-header-mega__top):not(#_),
:where(.kodza-header-mega__sign):not(#_),
:where(.kodza-header-mega__cta):not(#_),
:where(.kodza-header-mega__toggle):not(#_),
:where(.kodza-header-mega__close):not(#_) {
  outline: revert;
  outline-offset: revert;
}

:where(.kodza-header-mega__entry):not(#_) {
  outline: revert;
  background-color: revert;
  background-image: revert;
  outline-offset: revert;
}

:where(.kodza-header-mega__panel):not(#_),
:where(.kodza-header-mega__back):not(#_) {
  outline: revert;
}

:where(.kodza-header-mega__start):not(#_),
:where(.kodza-header-mega__more a:where(:not([class*="kodza-"]))):not(#_) {
  outline: revert;
  outline-offset: revert;
  text-underline-offset: revert;
}

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

.kodza-header-mega:not(#_) {
  border: revert;
  border-radius: revert;
  outline: revert;
  text-decoration: revert;
  transition: revert;
  list-style: 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-indent: revert;
  text-shadow: revert;
  vertical-align: revert;
}

.kodza-header-mega__logo:not(#_) {
  border: revert;
  padding: revert;
  transition: revert;
  list-style: revert;
  box-sizing: revert;
  float: revert;
  position: revert;
  background-color: revert;
  background-image: revert;
  box-shadow: revert;
  appearance: revert;
  max-width: revert;
  max-height: 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-header-mega__menu:not(#_) {
  border: revert;
  border-radius: revert;
  outline: revert;
  text-decoration: revert;
  transition: revert;
  list-style: revert;
  box-sizing: revert;
  float: 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-header-mega__links:not(#_),
.kodza-header-mega__spot:not(#_),
.kodza-header-mega__item:not(#_) {
  border: revert;
  border-radius: revert;
  outline: revert;
  text-decoration: revert;
  transition: revert;
  box-sizing: revert;
  float: 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-header-mega__trigger:not(#_) {
  list-style: revert;
  float: revert;
  box-shadow: revert;
  appearance: revert;
  max-width: revert;
  max-height: revert;
  text-transform: revert;
  text-underline-offset: revert;
  text-align: revert;
  text-indent: revert;
  text-shadow: revert;
  vertical-align: revert;
  min-height: revert;
}

.kodza-header-mega__inner:not(#_),
.kodza-header-mega__col:not(#_),
.kodza-header-mega__name:not(#_),
.kodza-header-mega__line:not(#_),
.kodza-header-mega__media:not(#_),
.kodza-header-mega__tag:not(#_),
.kodza-header-mega__headline:not(#_),
.kodza-header-mega__glide:not(#_),
.kodza-header-mega__end:not(#_),
.kodza-header-mega__lines:not(#_),
.kodza-header-mega__shade:not(#_),
.kodza-header-mega__bar:not(#_),
.kodza-header-mega__cross:not(#_),
.kodza-header-mega__say:not(#_),
.kodza-header-mega__arrow:not(#_),
.kodza-header-mega__title:not(#_),
.kodza-header-mega__heading:not(#_),
.kodza-header-mega__mask:not(#_),
.kodza-header-mega__word:not(#_),
.kodza-header-mega__meta:not(#_),
.kodza-header-mega__foot:not(#_),
.kodza-header-mega__logo span:where(:not([class*="kodza-"])):not(#_),
.kodza-header-mega__say span:where(:not([class*="kodza-"])):not(#_),
.kodza-header-mega__heading span:where(:not([class*="kodza-"])):not(#_),
.kodza-header-mega__meta span:where(:not([class*="kodza-"])):not(#_) {
  box-sizing: revert;
}

.kodza-header-mega__label:not(#_) {
  border: 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-style: revert;
  text-transform: revert;
  text-underline-offset: revert;
  text-align: revert;
  text-indent: revert;
  text-shadow: revert;
  vertical-align: revert;
}

.kodza-header-mega__entry:not(#_) {
  border: revert;
  list-style: revert;
  box-sizing: revert;
  float: revert;
  position: revert;
  box-shadow: revert;
  appearance: revert;
  max-width: revert;
  max-height: 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-header-mega__card:not(#_) {
  border: revert;
  transition: revert;
  list-style: revert;
  float: revert;
  position: revert;
  box-shadow: revert;
  appearance: revert;
  max-width: revert;
  max-height: 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-header-mega__top:not(#_),
.kodza-header-mega__sign:not(#_) {
  list-style: revert;
  float: revert;
  box-shadow: revert;
  appearance: revert;
  max-width: revert;
  max-height: revert;
  text-transform: revert;
  text-underline-offset: revert;
  text-align: revert;
  text-indent: revert;
  text-shadow: revert;
  vertical-align: revert;
}

.kodza-header-mega__cta:not(#_) {
  border: revert;
  list-style: revert;
  float: revert;
  position: revert;
  box-shadow: revert;
  appearance: revert;
  max-width: revert;
  max-height: 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-header-mega__toggle:not(#_),
.kodza-header-mega__close:not(#_) {
  text-decoration: revert;
  transition: revert;
  list-style: revert;
  box-sizing: revert;
  float: revert;
  position: revert;
  box-shadow: revert;
  appearance: revert;
  max-width: revert;
  max-height: revert;
  text-transform: revert;
  text-underline-offset: revert;
  text-align: revert;
  text-indent: revert;
  text-shadow: revert;
  vertical-align: revert;
  min-height: revert;
}

.kodza-header-mega__panel:not(#_) {
  box-shadow: revert;
  outline-offset: revert;
}

.kodza-header-mega__back:not(#_) {
  text-decoration: revert;
  transition: revert;
  list-style: revert;
  float: revert;
  box-shadow: revert;
  outline-offset: revert;
  appearance: revert;
  max-width: revert;
  max-height: revert;
  text-transform: revert;
  text-underline-offset: revert;
  text-indent: revert;
  text-shadow: revert;
  vertical-align: revert;
  white-space: revert;
  min-height: revert;
}

.kodza-header-mega__nav:not(#_) {
  border: revert;
  margin: 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-header-mega__list:not(#_) {
  border: revert;
  border-radius: revert;
  outline: revert;
  text-decoration: revert;
  transition: 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-header-mega__branch:not(#_) {
  transition: revert;
  list-style: revert;
  float: revert;
  position: revert;
  box-shadow: revert;
  outline-offset: revert;
  appearance: revert;
  max-width: revert;
  max-height: revert;
  letter-spacing: revert;
  text-transform: revert;
  text-underline-offset: revert;
  text-indent: revert;
  text-shadow: revert;
  vertical-align: revert;
  white-space: revert;
  min-height: revert;
}

.kodza-header-mega__link:not(#_) {
  transition: revert;
  list-style: revert;
  float: revert;
  position: revert;
  box-shadow: revert;
  outline-offset: revert;
  appearance: revert;
  max-width: revert;
  max-height: revert;
  letter-spacing: revert;
  text-transform: revert;
  text-underline-offset: revert;
  text-indent: revert;
  text-shadow: revert;
  vertical-align: revert;
}

.kodza-header-mega__more: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;
  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-header-mega__start:not(#_) {
  border: revert;
  margin: revert;
  transition: revert;
  list-style: revert;
  float: revert;
  position: revert;
  box-shadow: revert;
  appearance: revert;
  max-width: revert;
  max-height: revert;
  font-family: revert;
  font-size: revert;
  font-weight: revert;
  font-style: revert;
  letter-spacing: revert;
  text-transform: revert;
  text-align: revert;
  text-indent: revert;
  text-shadow: revert;
  vertical-align: revert;
}

.kodza-header-mega__small: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-style: revert;
  text-transform: revert;
  text-underline-offset: revert;
  text-align: revert;
  text-indent: revert;
  text-shadow: revert;
  vertical-align: revert;
}

.kodza-header-mega__logo svg:where(:not([class*="kodza-"])):not(#_) {
  border: revert;
  margin: 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-header-mega__media img:where(:not([class*="kodza-"])):not(#_) {
  border: revert;
  padding: revert;
  border-radius: revert;
  outline: revert;
  text-decoration: 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;
  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-header-mega__more a:where(:not([class*="kodza-"])):not(#_) {
  border: revert;
  margin: revert;
  padding: revert;
  transition: revert;
  list-style: revert;
  box-sizing: revert;
  float: revert;
  position: revert;
  background-color: revert;
  background-image: revert;
  box-shadow: revert;
  appearance: revert;
  max-width: revert;
  max-height: revert;
  font-family: revert;
  font-size: revert;
  font-weight: revert;
  font-style: revert;
  line-height: revert;
  letter-spacing: revert;
  text-transform: revert;
  text-align: revert;
  text-indent: revert;
  text-shadow: revert;
  vertical-align: revert;
}

.kodza-header-mega__label:not(#_):where(:not([hidden])),
.kodza-header-mega__small:not(#_):where(:not([hidden])),
.kodza-header-mega__more a:where(:not([class*="kodza-"])):not(#_):where(:not([hidden])) {
  display: revert;
}

.kodza-header-mega__label:not(#_):where([hidden]),
.kodza-header-mega__small:not(#_):where([hidden]),
.kodza-header-mega__more a:where(:not([class*="kodza-"])):not(#_):where([hidden]) {
  display: none;
}

.kodza-header-mega__trigger:not(#_)::after,
.kodza-header-mega__top:not(#_)::after,
.kodza-header-mega__drop:not(#_)::before,
.kodza-header-mega__name:not(#_)::after,
.kodza-header-mega__lines:not(#_)::before,
.kodza-header-mega__lines:not(#_)::after,
.kodza-header-mega__cross:not(#_)::before,
.kodza-header-mega__cross:not(#_)::after,
.kodza-header-mega__arrow:not(#_)::before,
.kodza-header-mega__item:not(#_)::after,
.kodza-header-mega__meta:not(#_)::before {
  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-header-mega:not(#_) {
  position: relative;
  display: block;
  box-sizing: border-box;
  padding: 0;
  background: var(--kz-header-mega-bg);
  color: var(--kz-header-mega-fg);
  font-size: 15px;
  line-height: 1.4;
  text-align: left;
}

.kodza-header-mega__logo:not(#_) {
  display: inline-flex;
  margin: 0 20px 0 0;
  border-radius: 6px;
  color: inherit;
  font-size: 18px;
  font-weight: 600;
  line-height: 1;
  letter-spacing: -0.04em;
  text-decoration: none;
}

.kodza-header-mega__logo svg:not(#_) {
  display: block;
}

.kodza-header-mega__menu:not(#_),
.kodza-header-mega__spot:not(#_) {
  position: static;
  display: block;
  margin: 0;
  padding: 0;
}

.kodza-header-mega__links:not(#_) {
  position: static;
  display: flex;
  margin: 0;
  padding: 0;
  list-style: none;
}

.kodza-header-mega__spot:not(#_) {
  list-style: none;
}

.kodza-header-mega__trigger:not(#_) {
  position: relative;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  box-sizing: border-box;
  height: 40px;
  margin: 0;
  padding: 0 14px;
  border: 0;
  border-radius: 999px;
  background: none;
  color: var(--kz-header-mega-muted);
  font: inherit;
  font-size: 15px;
  font-weight: 500;
  line-height: 1;
  letter-spacing: -0.01em;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: color 0.2s;
  -webkit-tap-highlight-color: transparent;
}

.kodza-header-mega__top:not(#_),
.kodza-header-mega__sign:not(#_) {
  position: relative;
  display: inline-flex;
  box-sizing: border-box;
  margin: 0;
  padding: 0 14px;
  border: 0;
  border-radius: 999px;
  background: none;
  color: var(--kz-header-mega-muted);
  font: inherit;
  font-size: 15px;
  font-weight: 500;
  line-height: 1;
  letter-spacing: -0.01em;
  text-decoration: none;
  transition: color 0.2s;
}

.kodza-header-mega__trigger:hover:not(#_),
.kodza-header-mega__trigger[aria-expanded="true"]:not(#_),
.kodza-header-mega__trigger:focus-visible:not(#_),
.kodza-header-mega__trigger.is-current:not(#_) {
  color: var(--kz-header-mega-fg);
}

.kodza-header-mega__top:hover:not(#_),
.kodza-header-mega__sign:hover:not(#_),
.kodza-header-mega__top:focus-visible:not(#_),
.kodza-header-mega__sign:focus-visible:not(#_),
.kodza-header-mega__top[aria-current="page"]:not(#_) {
  color: var(--kz-header-mega-fg);
}

.kodza-header-mega__trigger:focus-visible:not(#_) {
  outline: 2px solid var(--kz-header-mega-fg);
  outline-offset: 2px;
}

.kodza-header-mega__top:focus-visible:not(#_),
.kodza-header-mega__sign:focus-visible:not(#_),
.kodza-header-mega__logo:focus-visible:not(#_) {
  outline: 2px solid var(--kz-header-mega-fg);
  outline-offset: 2px;
}

.kodza-header-mega__trigger.is-current:not(#_)::after {
  content: "";
  position: absolute;
  bottom: 3px;
  left: 50%;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: currentColor;
  translate: -50% 0;
}

.kodza-header-mega__trigger.is-current:not(#_)::after {
  left: calc(50% - 6.5px);
}

.kodza-header-mega__cta:not(#_) {
  display: inline-flex;
  box-sizing: border-box;
  margin: 0;
  padding: 0 18px;
  border-radius: 999px;
  background: var(--kz-header-mega-ink);
  color: var(--kz-header-mega-on-ink);
  font-size: 15px;
  font-weight: 500;
  line-height: 1;
  letter-spacing: -0.01em;
  text-decoration: none;
  transition: background-color 0.2s;
}

@media (hover: hover) {
  .kodza-header-mega__cta:hover:not(#_) {
    background: color-mix(in srgb, var(--kz-header-mega-ink) 84%, transparent);
  }
}

.kodza-header-mega__cta:focus-visible:not(#_) {
  outline: 2px solid var(--kz-header-mega-ink);
  outline-offset: 3px;
}

.kodza-header-mega__label:not(#_) {
  margin: 0;
  padding: 10px 12px 6px;
  color: var(--kz-header-mega-muted);
  font-family: "Geist Mono", ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 12px;
  font-weight: 400;
  line-height: 1.4;
  letter-spacing: 0;
}

.kodza-header-mega__entry:not(#_) {
  display: grid;
  margin: 0;
  padding: 10px 12px;
  border-radius: 10px;
  color: inherit;
  text-decoration: none;
  transition: background-color 0.2s;
}

@media (hover: hover) {
  .kodza-header-mega__entry:hover:not(#_) {
    background: var(--kz-header-mega-soft);
  }
}

.kodza-header-mega__entry:focus-visible:not(#_),
.kodza-header-mega__card:focus-visible:not(#_) {
  outline: 2px solid var(--kz-header-mega-fg);
  outline-offset: -2px;
}

.kodza-header-mega__card:not(#_) {
  display: grid;
  box-sizing: border-box;
  margin: 0 0 0 4px;
  padding: 8px 8px 14px;
  border-radius: 10px;
  background: var(--kz-header-mega-soft);
  color: inherit;
  text-decoration: none;
}

.kodza-header-mega__media img:not(#_) {
  display: block;
  max-width: none;
  margin: 0;
  transition: scale 0.6s cubic-bezier(0.22, 1, 0.36, 1);
}

.kodza-header-mega__toggle:not(#_),
.kodza-header-mega__close:not(#_) {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  height: 40px;
  margin: 0;
  padding: 0 18px 0 16px;
  border: 0;
  border-radius: 999px;
  font: inherit;
  font-size: 15px;
  font-weight: 500;
  line-height: 1;
  letter-spacing: -0.01em;
  white-space: nowrap;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.kodza-header-mega__toggle:not(#_) {
  background: var(--kz-header-mega-menu-ink);
  color: var(--kz-header-mega-menu-on-ink);
}

.kodza-header-mega__close:not(#_) {
  background: var(--kz-header-mega-menu-fg);
  color: var(--kz-header-mega-menu-bg);
}

.kodza-header-mega__toggle:focus-visible:not(#_) {
  outline: 2px solid var(--kz-header-mega-fg);
  outline-offset: 3px;
}

.kodza-header-mega__close:focus-visible:not(#_) {
  outline: 2px solid var(--kz-header-mega-menu-fg);
  outline-offset: 4px;
}

.kodza-header-mega__more a:focus-visible:not(#_) {
  outline: 2px solid var(--kz-header-mega-menu-fg);
  outline-offset: 4px;
}

.kodza-header-mega__panel:focus:not(#_) {
  outline: none;
}

.kodza-header-mega__back:not(#_) {
  position: relative;
  display: flex;
  align-items: center;
  box-sizing: border-box;
  width: 100%;
  height: 48px;
  margin: 8px 0 0;
  padding: 0 var(--kz-header-mega-menu-pad);
  border: 0;
  border-radius: 0;
  background: none;
  color: var(--kz-header-mega-menu-fg);
  font: inherit;
  font-size: 15px;
  font-weight: 500;
  line-height: 1;
  letter-spacing: -0.01em;
  text-align: left;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.kodza-header-mega__back:disabled:not(#_) {
  color: var(--kz-header-mega-menu-muted);
  opacity: 1;
  cursor: default;
}

.kodza-header-mega__back:focus-visible:not(#_) {
  outline: none;
}

.kodza-header-mega__nav:not(#_) {
  display: grid;
  padding: 4px var(--kz-header-mega-menu-pad) 8px;
}

.kodza-header-mega__list:not(#_) {
  display: block;
  margin: 0;
  padding: 0;
  list-style: none;
}

.kodza-header-mega__list[hidden]:not(#_) {
  display: none;
}

.kodza-header-mega__item:not(#_) {
  position: relative;
  display: block;
  margin: 0;
  padding: 0;
  list-style: none;
}

.kodza-header-mega__link:not(#_) {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  box-sizing: border-box;
  width: 100%;
  margin: 0;
  padding: 14px 0 13px;
  border: 0;
  border-radius: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  outline: none;
  -webkit-tap-highlight-color: transparent;
}

.kodza-header-mega__foot p:not(#_) {
  margin: 0;
}

.kodza-header-mega__more:not(#_) {
  display: flex;
  font-size: 15px;
  font-weight: 500;
}

.kodza-header-mega__more a:not(#_) {
  border-radius: 2px;
  color: var(--kz-header-mega-menu-fg);
  text-decoration: none;
}

.kodza-header-mega__more a:hover:not(#_) {
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}

.kodza-header-mega__more .kodza-header-mega__start:not(#_) {
  display: inline-flex;
  box-sizing: border-box;
  padding: 0 18px;
  border-radius: 999px;
  background: var(--kz-header-mega-menu-fg);
  color: var(--kz-header-mega-menu-bg);
  line-height: 1;
}

.kodza-header-mega__more .kodza-header-mega__start:hover:not(#_) {
  text-decoration: none;
}

.kodza-header-mega__small:not(#_) {
  color: var(--kz-header-mega-menu-muted);
  font-family: "Geist Mono", ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 12px;
  font-weight: 400;
  line-height: 1.4;
  letter-spacing: 0;
}

@container (max-width: 899px) {
  .kodza-header-mega__menu:not(#_),
  .kodza-header-mega__sign:not(#_) {
    display: none;
  }
}

@container (min-width: 900px) {
  .kodza-header-mega__toggle:not(#_) {
    display: none;
  }
}

@container (max-width: 519px) {
  .kodza-header-mega__cta:not(#_) {
    display: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .kodza-header-mega__media img:not(#_) {
    transition-duration: 0.01ms !important;
    transition-delay: 0s !important;
  }
}
```

```js
// Kodza License: free to use, client work included. Not to resell or redistribute, ported or not: https://kodza.dev/docs/#print-license
// Kodza — Header Mega
// A whole site header. Its big menus share one panel under the bar: from a link to the next the
// panel doesn't close, it changes shape and slides, the old columns leave on the side you come
// from as the new ones come in from the other, and a soft pill glides along the bar. Below
// 900 px the links make way for a Menu button and a drawer with levels (Multi-Level Menu),
// written from the same links unless you write its list yourself.
(() => {
  // The two lines that change from one menu of the family to the next
  const NAME = 'header-mega';
  const VARS = ['menu-bg', 'menu-fg', 'menu-muted', 'menu-rule', 'menu-size', 'menu-width', 'menu-gap', 'menu-radius', 'menu-pad', 'menu-shade', 'menu-shadow'];

  // ---------- Menu engine (begin) ----------
  // The same block in every burger menu of the family (Trapdoor, Fullscreen, Side Reveal...),
  // copied as is. .kodza-NAME__toggle opens .kodza-NAME__panel and the menu's opening plays as
  // one timeline, backwards to close, from wherever it is. While open the panel leaves the menu
  // with its variables (VARS), the page stays put (wheel, swipe, keys), Tab stays inside, Escape
  // closes and the focus goes back to the button. A page that takes part (data-NAME-page,
  // data-page="…" on the menu, or a theme's wrapper) takes its fixed elements along and is put
  // back exactly as it was. Each menu only writes how it moves: see `menu`.
  const SELECTOR = `.kodza-${NAME}`;
  const motion = window.matchMedia('(prefers-reduced-motion: reduce)');
  const RISE = 'cubic-bezier(0.22, 1, 0.36, 1)';
  const SWING = 'cubic-bezier(0.65, 0, 0.35, 1)';
  const BACK = 1.6; // closing replays the opening backwards, this much faster
  const FOCUSABLE = 'a[href], button:not([disabled]), input, select, textarea, [tabindex]:not([tabindex="-1"])';
  const taken = new WeakMap(); // page → how to put it back at once (one menu moves a page at a time)

  const transparent = (color) => !color || color === 'transparent' || /^rgba\(.*,\s*0\)$/.test(color);

  // [time, progress] pairs along a cubic-bezier(): when does the edge pass a given row?
  const curve = (easing) => {
    const n = (easing.match(/-?\d*\.?\d+/g) || []).map(Number);
    const [x1, y1, x2, y2] = n.length === 4 ? n : [0.76, 0, 0.24, 1];
    const at = (s, a, b) => 3 * (1 - s) * (1 - s) * s * a + 3 * (1 - s) * s * s * b + s * s * s;
    return Array.from({ length: 101 }, (_, i) => [at(i / 100, x1, x2), at(i / 100, y1, y2)]);
  };

  // A transform makes the page the containing block of its fixed elements: they are found
  // first, then moved back to where they were on screen (see `rideAlong`)
  const fixedInside = (page) => {
    const found = [];
    const walker = document.createTreeWalker(page, NodeFilter.SHOW_ELEMENT, {
      acceptNode: (el) => {
        const style = getComputedStyle(el);
        if (style.display === 'none') return NodeFilter.FILTER_REJECT;
        if (style.position !== 'fixed') return NodeFilter.FILTER_SKIP;
        found.push({ el, box: el.getBoundingClientRect(), shift: style.translate, inline: el.style.translate, attr: el.hasAttribute('style') });
        return NodeFilter.FILTER_REJECT;
      },
    });
    while (walker.nextNode()) {
      // acceptNode collects them
    }
    return found;
  };

  const rideAlong = (fixed, k) => fixed.forEach((item) => {
    const now = item.el.getBoundingClientRect();
    const [x = 0, y = 0] = item.shift === 'none' ? [] : item.shift.split(' ').map(parseFloat);
    item.el.style.translate = `${x + (item.box.left - now.left) / k}px ${y + (item.box.top - now.top) / k}px`;
  });

  const letGo = (fixed) => fixed.forEach(({ el, inline, attr }) => {
    el.style.translate = inline;
    if (!attr && !el.getAttribute('style')) el.removeAttribute('style');
  });

  // Where the page is, in screen pixels: its box, its scale (a scaled parent changes nothing
  // once divided by `k`), and the part of it that is on screen
  const view = (page) => {
    const r = page.getBoundingClientRect();
    const { clientWidth: vw, clientHeight: vh } = document.documentElement;
    return {
      r,
      k: page.offsetWidth ? r.width / page.offsetWidth : 1,
      top: Math.max(r.top, 0),
      bottom: Math.min(r.bottom, vh),
      left: Math.max(r.left, 0),
      right: Math.min(r.right, vw),
    };
  };

  // One menu. `setup(m)` receives its parts (toggle, panel, closer, links, one, all, css, cut, and
  // while open page, reduced) and returns what the menu does itself:
  //   page    'under' when the menu lies under the page that moves, 'over' when it covers the
  //           page that moves under it; without it the page stays as it is
  //   rig(page)     sets the flat page up for the ride, returns its geometry with `k` (its scale);
  //                 again on resize
  //   frames(g)     the page's keyframes, set again on resize
  //   build(add, mode, g)   the opening, add(el, keyframes, delay, duration, easing, options) per
  //                 animation; mode is 'page', 'flat' (no page found) or 'reduced'
  //   ready(), entry(), key(event), resize(), reset()   optional: before it opens, where the
  //                 keyboard enters, a key of its own (true once handled), after a resize, once closed
  const menu = (root, signal, setup) => {
    const toggle = root.querySelector(`.kodza-${NAME}__toggle`);
    const panel = root.querySelector(`.kodza-${NAME}__panel`);
    if (!toggle || !panel) return;
    const one = (s) => panel.querySelector(s);
    const all = (s) => [...panel.querySelectorAll(s)];
    const closer = one(`.kodza-${NAME}__close`);
    const links = all(`.kodza-${NAME}__link`);
    const home = [panel.parentNode, panel.nextSibling];
    const css = (name) => getComputedStyle(root).getPropertyValue(`--kz-${NAME}-${name}`).trim();

    let state = 'closed'; // closed, opening, open, closing
    let sequence = []; // the opening: animations on one clock, so it can run backwards
    let session = null; // what only lives while the menu is open
    let moving = null; // the page taking part, and what it looked like before
    let reduced = false;
    let run = 0;
    let then = null;

    // The page: named on the menu (data-page="…"), or the marked wrapper around it, or else
    // the wrapper of a WordPress theme (block themes, Astra, GeneratePress, Kadence, OceanWP,
    // Blocksy, Divi…). Without one, the menu plays its sequence without the page
    const findPage = () => {
      let el = null;
      try {
        el = root.dataset.page ? document.querySelector(root.dataset.page) : null;
      } catch {
        el = null;
      }
      el = el || root.closest(`[data-${NAME}-page]`) || document.querySelector(`[data-${NAME}-page]`) || root.closest('.wp-site-blocks, #page, #outer-wrap, #main-container, #page-container');
      if (!el || el === document.body || el === document.documentElement) return null;
      // It has to fill the screen (below the WordPress admin bar): otherwise part of the site, a
      // header outside it or the space under a short page, would be left out of the ride
      const r = el.getBoundingClientRect();
      const bar = parseFloat(getComputedStyle(document.documentElement).getPropertyValue('--wp-admin--admin-bar--height')) || 0;
      const { clientWidth: vw, clientHeight: vh } = document.documentElement;
      return r.top <= bar + 1 && r.left <= 1 && r.right >= vw - 1 && r.bottom >= vh - 1 ? el : null;
    };

    // The colour under the page's content, so the menu never shows through it
    const ground = (page) => {
      const own = css('page');
      if (own && own !== 'auto') return own;
      for (let el = page.parentElement; el; el = el.parentElement) {
        const color = getComputedStyle(el).backgroundColor;
        if (!transparent(color)) return color;
      }
      return '#ffffff';
    };

    // A flex or grid page shares its height out among its children: for the ride their sizes
    // are held as they are, so a shorter page box changes nothing inside
    const hold = (page, style) => {
      if (style.display.includes('grid')) {
        page.style.gridTemplateRows = style.gridTemplateRows;
        page.style.gridTemplateColumns = style.gridTemplateColumns;
      } else if (style.display.includes('flex')) {
        const column = style.flexDirection.startsWith('column');
        moving.held = [...page.children].map((el) => {
          const own = getComputedStyle(el);
          const item = { el, flex: el.style.flex, attr: el.hasAttribute('style') };
          el.style.flex = `0 0 ${column ? own.height : own.width}`;
          return item;
        });
      }
    };

    const release = () => {
      moving.held.forEach(({ el, flex, attr }) => {
        el.style.flex = flex;
        if (!attr && !el.getAttribute('style')) el.removeAttribute('style');
      });
      moving.held = [];
      Object.assign(moving.page.style, moving.cut);
    };

    // The page's box ends at `at` (its own pixels from its top) for the ride, the space it
    // leaves kept as margin: what moves below it would stretch the scroll area. Checked on the
    // spot: if that moves anything inside, even with the children held, the page is left as it
    // was. Returns where the box ends
    const cut = (at) => {
      const { page } = moving;
      const full = page.offsetHeight;
      if (at >= full) return full;
      const style = getComputedStyle(page);
      const edges = style.boxSizing === 'border-box' ? 0 : ['paddingTop', 'paddingBottom', 'borderTopWidth', 'borderBottomWidth'].reduce((sum, side) => sum + parseFloat(style[side]), 0);
      const margin = parseFloat(style.marginBottom) || 0;
      const boxes = (els) => els.map((el) => el.getBoundingClientRect()).map((r) => [r.top, r.left, r.width, r.height]);
      const kids = [...page.children];
      const before = boxes(kids);
      const shorten = () => {
        page.style.height = `${Math.max(at - edges, 0)}px`;
        page.style.overflowY = 'clip';
        page.style.marginBottom = `${margin + full - at}px`;
        return boxes(kids).some((box, i) => box.some((n, j) => Math.abs(n - before[i][j]) > 0.5));
      };
      let moved = shorten();
      if (moved && /flex|grid/.test(style.display)) {
        release();
        hold(page, style);
        moved = shorten();
      }
      if (moved) release();
      return moved ? full : at;
    };

    const m = {
      root, toggle, panel, closer, links, one, all, css, cut,
      get page() {
        return moving ? moving.page : null;
      },
      get reduced() {
        return reduced;
      },
    };
    const own = setup(m);

    // With the page untransformed: where its fixed elements are, then the menu's own geometry
    const rig = () => {
      letGo(moving.fixed);
      release();
      moving.fixed = fixedInside(moving.page);
      return own.rig(moving.page);
    };

    // The opening, as one timeline. Every animation gets an end delay that makes it as long as
    // the longest, so running them all backwards is the exact reverse, even halfway through.
    const timeline = (mode, g) => {
      const steps = [];
      const add = (el, frames, delay, duration, easing = RISE, extra = {}) => {
        if (el) steps.push([el, frames, { delay, duration, easing, ...extra }]);
      };
      own.build(add, mode, g);
      const total = Math.max(...steps.map(([, , o]) => o.delay + o.duration));
      return steps.map(([el, frames, o]) => el.animate(frames, { ...o, fill: 'both', endDelay: total - o.delay - o.duration }));
    };

    const play = (forward) => {
      const token = ++run;
      sequence.forEach((animation) => {
        animation.updatePlaybackRate(forward ? 1 : -BACK);
        animation.play();
      });
      Promise.all(sequence.map((animation) => animation.finished)).then(() => {
        if (token !== run) return;
        if (forward) state = 'open';
        else land();
      }, () => {});
    };

    // Closed again: the animations, the borrowed styles and the listeners go, the panel goes home
    const land = () => {
      run++;
      sequence.forEach((animation) => animation.cancel());
      panel.getAnimations({ subtree: true }).forEach((animation) => animation.cancel());
      sequence = [];
      session?.abort();
      session = null;
      if (moving) {
        const { page, saved, attr } = moving;
        letGo(moving.fixed);
        release();
        Object.assign(page.style, saved);
        if (!attr && !page.getAttribute('style')) page.removeAttribute('style');
        if (taken.get(page) === land) taken.delete(page);
        moving = null;
      }
      try {
        if (panel.popover && panel.matches(':popover-open')) panel.hidePopover();
      } catch {
        // no popover support: it was a plain fixed layer
      }
      panel.removeAttribute('popover');
      panel.removeAttribute('style');
      panel.hidden = true;
      if (panel.parentNode !== home[0]) home[0].insertBefore(panel, home[1]?.parentNode === home[0] ? home[1] : null);
      own.reset?.();
      state = 'closed';
      const next = then;
      then = null;
      next?.();
    };

    // While open the page stays put: a wheel or a swipe only scrolls what scrolls in the menu
    const scrollsInside = (target, x, y, dy) => {
      for (let el = target; el instanceof Element && panel.contains(el); el = el.parentElement) {
        const box = el.getBoundingClientRect();
        if (x < box.left || x > box.right || y < box.top || y > box.bottom) continue;
        if (!/(auto|scroll)/.test(getComputedStyle(el).overflowY) || el.scrollHeight <= el.clientHeight + 1) continue;
        if (dy > 0 ? el.scrollTop + el.clientHeight < el.scrollHeight - 1 : el.scrollTop > 0) return true;
      }
      return false;
    };

    // The links you can reach now (a menu with levels hides the others)
    const rows = () => links.filter((link) => link.getClientRects().length && !link.closest('[inert]'));

    const keydown = (event) => {
      if (state === 'closing' || event.defaultPrevented) return;
      if (own.key?.(event)) return;
      if (event.key === 'Escape') {
        event.preventDefault();
        shut();
        return;
      }
      const current = document.activeElement;
      if (event.key === 'Tab') {
        const stops = all(FOCUSABLE).filter((el) => el.getClientRects().length && !el.closest('[inert]'));
        const first = stops[0];
        const last = stops[stops.length - 1];
        const outside = !panel.contains(current) || current === panel;
        if (outside || (event.shiftKey && current === first) || (!event.shiftKey && current === last)) {
          event.preventDefault();
          (event.shiftKey ? last : first)?.focus();
        }
        return;
      }
      const list = rows();
      const i = list.indexOf(current);
      if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
        event.preventDefault();
        const step = event.key === 'ArrowDown' ? 1 : -1;
        list[i < 0 ? (step > 0 ? 0 : list.length - 1) : (i + step + list.length) % list.length]?.focus();
      } else if (event.key === 'Home' || event.key === 'End') {
        event.preventDefault();
        list[event.key === 'Home' ? 0 : list.length - 1]?.focus();
      } else if ([' ', 'PageUp', 'PageDown'].includes(event.key) && !current?.closest?.('button, input, select, textarea')) {
        event.preventDefault();
      }
    };

    const listen = (s) => {
      let last = 0;
      const at = [scrollX, scrollY];
      document.addEventListener('wheel', (event) => {
        if (!scrollsInside(event.target, event.clientX, event.clientY, event.deltaY)) event.preventDefault();
      }, { passive: false, signal: s });
      document.addEventListener('touchstart', (event) => (last = event.touches[0].clientY), { passive: true, signal: s });
      document.addEventListener('touchmove', (event) => {
        const touch = event.touches[0];
        const dy = last - touch.clientY;
        last = touch.clientY;
        if (!scrollsInside(event.target, touch.clientX, touch.clientY, dy)) event.preventDefault();
      }, { passive: false, signal: s });
      document.addEventListener('keydown', keydown, { signal: s });
      // A scrollbar dragged anyway: the page goes back to where it was
      window.addEventListener('scroll', () => {
        if (scrollX !== at[0] || scrollY !== at[1]) window.scrollTo({ left: at[0], top: at[1], behavior: 'instant' });
      }, { signal: s });
      let queued = 0;
      window.addEventListener('resize', () => {
        if (queued) return;
        queued = requestAnimationFrame(() => {
          queued = 0;
          if (s.aborted) return;
          if (moving) {
            const effect = sequence.find((animation) => animation.effect.target === moving.page)?.effect;
            effect?.setKeyframes([]);
            const g = rig();
            effect?.setKeyframes(own.frames(g));
            rideAlong(moving.fixed, g.k);
          }
          own.resize?.();
        });
      }, { signal: s });
      s.addEventListener('abort', () => cancelAnimationFrame(queued));
      // A click on the page that moved aside brings it back
      moving?.page.addEventListener('click', (event) => {
        if (state === 'closing') return;
        event.preventDefault();
        event.stopPropagation();
        shut();
      }, { capture: true, signal: s });
    };

    const enter = (keyboard) => {
      const target = keyboard ? own.entry?.() || links.find((link) => link.getAttribute('aria-current')) || rows()[0] || closer : panel;
      target?.focus({ preventScroll: true });
    };

    const open = (keyboard) => {
      if (state === 'opening' || state === 'open') return;
      toggle.setAttribute('aria-expanded', 'true');
      if (state === 'closing') {
        state = 'opening';
        then = null;
        panel.style.pointerEvents = '';
        play(true);
        enter(keyboard);
        return;
      }
      reduced = motion.matches;
      const page = reduced || !own.page ? null : findPage();
      const under = page && own.page === 'under';
      let g = null;
      if (page) {
        taken.get(page)?.();
        const { transformOrigin, backgroundColor, position, zIndex, height, overflowY, marginBottom, gridTemplateRows, gridTemplateColumns } = page.style;
        moving = { page, saved: { transformOrigin, backgroundColor, position, zIndex }, cut: { height, overflowY, marginBottom, gridTemplateRows, gridTemplateColumns }, attr: page.hasAttribute('style'), fixed: [], held: [] };
        taken.set(page, land);
        g = rig();
        const shown = getComputedStyle(page);
        if (transparent(shown.backgroundColor)) page.style.backgroundColor = ground(page);
        // Under the page: page over panel over the rest of the theme (a header, a sticky bar),
        // under the admin bar
        const still = shown.position === 'static';
        if (under && (!still || ['top', 'right', 'bottom', 'left'].every((side) => shown[side] === 'auto'))) {
          if (still) page.style.position = 'relative';
          page.style.zIndex = '99991';
          panel.style.zIndex = '99990';
        }
      }
      // The panel leaves the menu while open, so the styles of a theme's content (links,
      // paragraphs) never reach it: right under the page, or on top of everything
      VARS.forEach((name) => panel.style.setProperty(`--kz-${NAME}-${name}`, css(name)));
      panel.style.fontFamily = getComputedStyle(root).fontFamily;
      if (under) page.before(panel);
      else document.body.append(panel);
      panel.hidden = false;
      if (!under) {
        try {
          panel.popover = 'manual';
          panel.showPopover();
        } catch {
          // no popover support: a fixed layer over the page
        }
      }
      own.ready?.();
      sequence = timeline(page ? 'page' : reduced ? 'reduced' : 'flat', g);
      if (moving) rideAlong(moving.fixed, g.k);
      session = new AbortController();
      listen(session.signal);
      state = 'opening';
      play(true);
      enter(keyboard);
    };

    const shut = (after) => {
      if (state === 'closed' || state === 'closing') return;
      then = after || null;
      state = 'closing';
      toggle.setAttribute('aria-expanded', 'false');
      toggle.focus({ preventScroll: true });
      // On its way out it lets clicks through: Menu, under it, opens it again from where it is
      panel.style.pointerEvents = 'none';
      play(false);
    };

    // Keyboard (Enter, Space) clicks have no detail: the focus then goes into the menu
    toggle.addEventListener('click', (event) => open(event.detail === 0), { signal });
    closer?.addEventListener('click', () => shut(), { signal });
    // A link to this very page (#section, or just #) closes the menu first, then goes there
    links.forEach((link) => link.addEventListener('click', (event) => {
      const here = link.origin === location.origin && link.pathname === location.pathname && link.search === location.search;
      if (!here || event.defaultPrevented || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
      event.preventDefault();
      const hash = link.hash;
      shut(hash ? () => (location.hash = hash) : null);
    }, { signal }));
    // Back from another page (bfcache): the menu is closed, the page as it was
    window.addEventListener('pageshow', (event) => event.persisted && state !== 'closed' && land(), { signal });

    // Stopped while open: the page is put back at once and the panel goes back in place
    signal.addEventListener('abort', () => {
      then = null;
      if (state !== 'closed') land();
    });
  };
  // ---------- Menu engine (end) ----------

  // Below 900 px the drawer shows the bar's own links: its list is written from them (a big
  // menu becomes a level), unless you wrote one yourself, a wp_nav_menu() for instance
  const drawer = (root) => {
    const list = root.querySelector(`.kodza-${NAME}__nav > ul`);
    if (!list || list.children.length) return;
    const text = (el) => el?.textContent.trim() || '';
    const copy = (from, label) => {
      const a = document.createElement('a');
      a.href = from.getAttribute('href') || '#';
      a.textContent = label;
      if (from.hasAttribute('aria-current')) a.setAttribute('aria-current', from.getAttribute('aria-current'));
      return a;
    };
    root.querySelectorAll(`.kodza-${NAME}__links > li`).forEach((spot) => {
      const trigger = spot.querySelector(`.kodza-${NAME}__trigger`);
      const top = spot.querySelector(`.kodza-${NAME}__top`);
      const li = document.createElement('li');
      if (trigger) {
        const a = document.createElement('a');
        a.textContent = text(trigger);
        const sub = document.createElement('ul');
        spot.querySelectorAll(`.kodza-${NAME}__entry`).forEach((entry) => {
          const item = document.createElement('li');
          item.append(copy(entry, text(entry.querySelector(`.kodza-${NAME}__name`)) || text(entry)));
          sub.append(item);
        });
        li.append(a, sub);
      } else if (top) li.append(copy(top, text(top)));
      else return;
      list.append(li);
    });
  };

  // Where an easing is at time t (0 to 1), from the engine's samples
  const progress = (samples, t) => {
    const i = samples.findIndex(([x]) => x >= t);
    if (i <= 0) return i === 0 ? samples[0][1] : 1;
    const [x0, y0] = samples[i - 1];
    const [x1, y1] = samples[i];
    return y0 + (y1 - y0) * (x1 > x0 ? (t - x0) / (x1 - x0) : 1);
  };
  const mix = (a, b, p) => a + (b - a) * p;

  // The bar, from 900 px: one panel for every big menu, and a pill that follows the pointer
  const mega = (root, signal) => {
    const c = (part) => `.kodza-${NAME}__${part}`;
    const nav = root.querySelector(c('menu'));
    const list = nav?.querySelector(c('links'));
    const row = root.querySelector(c('row'));
    if (!list || !row) return;
    const items = [...list.querySelectorAll(`:scope > li > ${c('trigger')}, :scope > li > ${c('top')}`)];
    const panes = new Map(items.map((el) => [el, el.matches(c('trigger')) ? el.parentElement.querySelector(c('pane')) : null]).filter(([, pane]) => pane));
    const triggers = [...panes.keys()];
    const toggle = root.querySelector(c('toggle'));
    const closer = root.querySelector(c('close'));
    const css = (name) => getComputedStyle(root).getPropertyValue(`--kz-${NAME}-${name}`).trim();
    const rise = curve(RISE);
    const swing = curve(SWING);

    const layer = (part) => {
      const el = document.createElement('span');
      el.className = `kodza-${NAME}__${part}`;
      el.setAttribute('aria-hidden', 'true');
      nav.append(el);
      return el;
    };
    const glide = layer('glide');
    const drop = layer('drop');

    // The section you are in: a dot under its name
    triggers.forEach((trigger) => trigger.classList.toggle('is-current', !!panes.get(trigger).querySelector('[aria-current="page"]')));

    const desktop = () => getComputedStyle(nav).display !== 'none';
    // Boxes in the header's own pixels: a parent scaled with transform doesn't shift them
    const box = (el) => {
      const o = root.getBoundingClientRect();
      const k = root.offsetWidth ? o.width / root.offsetWidth : 1;
      const r = el.getBoundingClientRect();
      return { x: (r.left - o.left) / k, y: (r.top - o.top) / k, w: r.width / k, h: r.height / k };
    };

    // ---------- The pill ----------
    let over = false; // the pointer is on the bar or in a menu
    const glideTo = (el) => {
      if (!el) {
        glide.classList.remove('is-on');
        return;
      }
      const b = box(el);
      const clip = `inset(${b.y}px ${root.offsetWidth - b.x - b.w}px ${root.offsetHeight - b.y - b.h}px ${b.x}px round 999px)`;
      // Out of sight, it is put in place first and fades in there; still showing, it glides
      if (parseFloat(getComputedStyle(glide).opacity) < 0.05) {
        glide.style.transition = 'none';
        glide.style.clipPath = clip;
        void glide.offsetWidth;
        glide.style.transition = '';
      } else glide.style.clipPath = clip;
      glide.classList.add('is-on');
    };

    // ---------- The panel ----------
    let current = null; // the trigger whose menu is open
    let last = null; // the one on show, still there while the panel closes
    let W = null; // the panel as drawn: x, y, w, h, and a for its opacity
    let frame = 0;
    const boxes = new Map(); // each menu on show, and its box
    const slides = new Map(); // the columns on their way out or in

    // A menu's box: centred under its link, kept inside the row, just under the bar
    const where = (trigger) => {
      const pane = panes.get(trigger);
      pane.hidden = false;
      pane.style.left = '0px';
      pane.style.top = '0px';
      const p = box(pane);
      const t = box(trigger);
      const n = box(list);
      const r = box(row);
      const pad = parseFloat(getComputedStyle(row).paddingLeft) || 0;
      const lo = r.x + pad;
      const x = Math.max(lo, Math.min(t.x + t.w / 2 - p.w / 2, r.x + r.w - pad - p.w));
      const y = n.y + n.h + (parseFloat(css('gap')) || 0);
      pane.style.left = `${x}px`;
      pane.style.top = `${y}px`;
      const b = { x, y, w: p.w, h: p.h };
      boxes.set(pane, b);
      return b;
    };

    // The panel draws its box, and each menu on show is cut to it
    const paint = () => {
      const radius = parseFloat(css('radius')) || 0;
      drop.style.transform = `translate(${W.x}px, ${W.y}px)`;
      drop.style.width = `${W.w}px`;
      drop.style.height = `${W.h}px`;
      drop.style.opacity = String(W.a);
      boxes.forEach((b, pane) => {
        const top = Math.max(0, W.y - b.y);
        const right = Math.max(0, b.x + b.w - W.x - W.w);
        const bottom = Math.max(0, b.y + b.h - W.y - W.h);
        const left = Math.max(0, W.x - b.x);
        pane.style.clipPath = `inset(${top}px ${right}px ${bottom}px ${left}px round ${radius}px)`;
        pane.style.opacity = String(W.a);
      });
    };

    // From where the panel is now to a box. `fade`: the opacity gets there in that first part of
    // the time, on its own (below zero: in that last part); `done` once there. A new move stops
    // the one under way
    const morph = (to, duration, samples, fade, done) => {
      cancelAnimationFrame(frame);
      const from = { ...W };
      const start = performance.now();
      const quick = motion.matches || !duration;
      const step = (now) => {
        const t = quick ? 1 : Math.min(1, (now - start) / duration);
        const p = progress(samples, t);
        W = {
          x: mix(from.x, to.x, p),
          y: mix(from.y, to.y, p),
          w: mix(from.w, to.w, p),
          h: mix(from.h, to.h, p),
          a: mix(from.a, to.a, !fade ? p : fade > 0 ? Math.min(1, t / fade) : Math.max(0, (t - 1 - fade) / -fade)),
        };
        paint();
        if (t < 1) frame = requestAnimationFrame(step);
        else done?.();
      };
      step(start);
    };

    // Columns on their way: from where they are now if they were already moving
    const slide = (el, frames, options) => {
      const was = slides.get(el);
      if (was) {
        const now = getComputedStyle(el);
        frames[0] = { ...frames[0], opacity: now.opacity, transform: now.transform === 'none' ? 'translateX(0px)' : now.transform };
        was.cancel();
      }
      const animation = el.animate(frames, options);
      slides.set(el, animation);
      return animation;
    };

    const hide = (pane) => {
      pane.hidden = true;
      pane.inert = true;
      pane.style.removeProperty('clip-path');
      pane.style.removeProperty('opacity');
      pane.querySelectorAll('*').forEach((el) => {
        slides.get(el)?.cancel();
        slides.delete(el);
      });
      boxes.delete(pane);
    };

    // The rows rise one after the other, and the picture settles
    const enter = (pane) => {
      if (motion.matches) return;
      pane.querySelectorAll(`${c('label')}, ${c('entry')}, ${c('card')}`).forEach((el, i) => {
        slide(el, [{ opacity: 0, transform: 'translateY(8px)' }, { opacity: 1, transform: 'translateY(0px)' }], { duration: 500, delay: 40 + i * 25, easing: RISE, fill: 'backwards' });
      });
      settle(pane);
    };
    const settle = (pane) => {
      const img = pane.querySelector(`${c('media')} img`);
      if (img && !motion.matches) slide(img, [{ transform: 'scale(1.08)' }, { transform: 'scale(1)' }], { duration: 900, delay: 60, easing: RISE, fill: 'backwards' });
    };

    const open = (trigger) => {
      clearTimeout(timer);
      if (trigger === current) return;
      const pane = panes.get(trigger);
      const prev = last;
      const from = prev && prev !== trigger && W && W.a > 0 ? panes.get(prev) : null;
      const again = prev === trigger && W && W.a > 0;
      triggers.forEach((t) => t.setAttribute('aria-expanded', String(t === trigger)));
      current = trigger;
      last = trigger;
      root.classList.add('is-open');
      drop.style.visibility = 'visible';
      const b = where(trigger);
      pane.inert = false;
      if (from) {
        // From one menu to the next: the panel changes shape on the way, the old columns leave
        // on the side you come from as the new ones come in from the other
        const dir = Math.sign(triggers.indexOf(trigger) - triggers.indexOf(prev)) || 1;
        from.inert = true;
        const old = from.querySelector(c('inner'));
        const fresh = pane.querySelector(c('inner'));
        if (motion.matches) hide(from);
        else {
          slide(old, [{ opacity: 1, transform: 'translateX(0px)', easing: SWING }, { opacity: 0, offset: 0.6 }, { opacity: 0, transform: `translateX(${-48 * dir}px)` }], { duration: 240, fill: 'forwards' })
            .finished.then(() => {
              if (panes.get(current) !== from && panes.get(last) !== from) hide(from);
            }, () => {});
          slide(fresh, [{ opacity: 0, transform: `translateX(${56 * dir}px)` }, { opacity: 1, transform: 'translateX(0px)' }], { duration: 450, delay: 40, easing: RISE, fill: 'backwards' });
          settle(pane);
        }
        morph({ ...b, a: 1 }, 450, rise);
      } else if (again) {
        // Opened again on its way out: it comes back from where it is
        morph({ ...b, a: 1 }, 450, rise);
      } else {
        // Unrolled from under the bar, the rows rising as it comes
        W = { x: b.x, y: b.y - 4, w: b.w, h: 0, a: 0 };
        enter(pane);
        morph({ ...b, a: 1 }, 450, rise, 0.12);
      }
      if (!over && !trigger.matches(':focus-visible')) glideTo(trigger);
    };

    const close = (focus) => {
      clearTimeout(timer);
      if (!current) return;
      const was = current;
      current = null;
      triggers.forEach((t) => t.setAttribute('aria-expanded', 'false'));
      panes.forEach((pane) => (pane.inert = true));
      root.classList.remove('is-open');
      morph({ ...W, h: 0, a: 0 }, 280, swing, -0.35, () => {
        boxes.forEach((b, pane) => hide(pane));
        drop.style.removeProperty('visibility');
        last = null;
        W = null;
      });
      if (focus) was.focus();
      if (!over) glideTo(null);
    };

    // Shut at once, nothing on its way (the layout changed under it)
    const snap = () => {
      cancelAnimationFrame(frame);
      clearTimeout(timer);
      boxes.forEach((b, pane) => hide(pane));
      triggers.forEach((t) => t.setAttribute('aria-expanded', 'false'));
      root.classList.remove('is-open');
      drop.style.removeProperty('visibility');
      drop.style.opacity = '0';
      current = null;
      last = null;
      W = null;
    };

    // ---------- Pointer ----------
    let timer = 0;
    let hovered = null; // a menu just opened by the pointer: the click that follows keeps it open
    const later = (fn, ms) => {
      clearTimeout(timer);
      timer = setTimeout(fn, ms);
    };
    const mouse = (event) => event.pointerType === 'mouse';
    items.forEach((el) => {
      el.addEventListener('pointerenter', (event) => {
        if (!mouse(event) || !desktop()) return;
        glideTo(el);
        if (panes.has(el)) {
          later(() => {
            if (current === el) return;
            open(el);
            hovered = { el, at: performance.now() };
          }, current ? 60 : 90);
        } else if (current) later(() => close(), 120);
        else clearTimeout(timer);
      }, { signal });
      el.addEventListener('focusin', () => {
        if (!el.matches(':focus-visible')) return;
        glideTo(el);
        // Tabbed onto another link of the bar: the open menu makes way
        if (current && current !== el) close();
      }, { signal });
    });
    triggers.forEach((trigger) => trigger.addEventListener('click', (event) => {
      if (!desktop()) return;
      if (current !== trigger) open(trigger);
      else if (event.detail === 0 || !hovered || hovered.el !== trigger || performance.now() - hovered.at > 400) close(event.detail === 0);
    }, { signal }));
    list.addEventListener('pointerenter', (event) => {
      if (mouse(event)) over = true;
    }, { signal });
    list.addEventListener('pointerleave', (event) => {
      if (!mouse(event)) return;
      over = false;
      glideTo(current);
    }, { signal });
    nav.addEventListener('pointerenter', (event) => mouse(event) && clearTimeout(timer), { signal });
    nav.addEventListener('pointerleave', (event) => {
      if (mouse(event) && current) later(() => close(), 220);
    }, { signal });
    document.addEventListener('pointerdown', (event) => {
      if (current && !nav.contains(event.target)) close();
    }, { signal });
    nav.addEventListener('focusout', (event) => {
      if (event.relatedTarget && nav.contains(event.relatedTarget)) return;
      if (event.relatedTarget && current) close();
      if (!over) glideTo(current);
    }, { signal });

    // ---------- Keyboard ----------
    const rows = (trigger) => [...panes.get(trigger).querySelectorAll(`${c('entry')}, ${c('card')}`)];
    document.addEventListener('keydown', (event) => {
      if (event.key !== 'Escape' || !current || event.defaultPrevented) return;
      close(nav.contains(document.activeElement));
    }, { signal });
    nav.addEventListener('keydown', (event) => {
      if (!desktop() || event.altKey || event.ctrlKey || event.metaKey) return;
      const { key, target } = event;
      const at = items.indexOf(target);
      if (at >= 0 && (key === 'ArrowRight' || key === 'ArrowLeft')) {
        // Along the bar; an open menu follows
        const next = items[(at + (key === 'ArrowRight' ? 1 : -1) + items.length) % items.length];
        if (current) {
          if (panes.has(next)) open(next);
          else close();
        }
        next.focus();
      } else if (at >= 0 && key === 'ArrowDown' && panes.has(target)) {
        open(target);
        rows(target)[0]?.focus();
      } else if (current && panes.get(current).contains(target) && ['ArrowDown', 'ArrowUp', 'Home', 'End'].includes(key)) {
        const all = rows(current);
        const i = all.indexOf(target.closest(`${c('entry')}, ${c('card')}`));
        const n = key === 'Home' ? 0 : key === 'End' ? all.length - 1 : i + (key === 'ArrowDown' ? 1 : -1);
        if (n < 0) current.focus();
        else all[Math.min(n, all.length - 1)].focus();
      } else return;
      event.preventDefault();
    }, { signal });

    // ---------- Resize ----------
    // Under 900 px the menu shuts at once; back over it, the drawer closes. Open, it follows
    const watch = new ResizeObserver(() => {
      if (!desktop()) {
        if (W) snap();
        glide.classList.remove('is-on');
        return;
      }
      if (toggle?.getAttribute('aria-expanded') === 'true') closer?.click();
      if (!current) return;
      cancelAnimationFrame(frame);
      boxes.forEach((b, pane) => pane !== panes.get(current) && hide(pane));
      W = { ...where(current), a: 1 };
      paint();
      if (!over) glideTo(current);
    });
    watch.observe(root);

    signal.addEventListener('abort', () => {
      cancelAnimationFrame(frame);
      clearTimeout(timer);
      watch.disconnect();
      slides.forEach((animation) => animation.cancel());
    }, { once: true });
  };

  // A menu written the WordPress way (li > a, with a ul.sub-menu under a row that has children)
  // becomes levels: each sub-menu is taken out of its row and stacked in the nav, and its row
  // becomes a button that opens it. The list as written comes back on kodza:destroy
  const decorate = (root) => {
    const nav = root.querySelector(`.kodza-${NAME}__nav`);
    const top = nav?.querySelector('ul');
    if (!top) return null;
    const c = (part) => `kodza-${NAME}__${part}`;
    const heading = root.querySelector(`.${c('heading')}`);
    const levels = new Map([[top, { title: heading?.textContent.trim() || nav.getAttribute('aria-label') || 'Menu', branch: null, parent: null }]]);
    // A row that is a page of its own stays reachable, first in its level: "All Keyboards"
    const prefix = root.dataset.all ?? 'All';
    // The current page has aria-current="page" (wp_nav_menu() writes it), and every row on the
    // way to it is marked too. WordPress's own classes count as well
    const path = /(^|\s)current(-menu-|_page_)(item|ancestor|parent)(\s|$)/;
    const here = /(^|\s)current(-menu-item|_page_item)(\s|$)/;
    const dress = (link, current) => {
      const mask = document.createElement('span');
      const word = document.createElement('span');
      const meta = document.createElement('span');
      mask.className = c('mask');
      word.className = c('word');
      meta.className = c('meta');
      meta.setAttribute('aria-hidden', 'true');
      word.append(...link.childNodes);
      mask.append(word);
      link.append(mask, meta);
      link.classList.add(c('link'));
      link.classList.toggle('is-current', current);
      return meta;
    };
    top.classList.add(c('list'));
    [...top.querySelectorAll('li')].forEach((li) => {
      li.classList.add(c('item'));
      const a = li.querySelector(':scope > a');
      const sub = li.querySelector(':scope > ul');
      if (!a) return;
      const onPath = path.test(li.className) || !!li.querySelector('a[aria-current="page"]');
      if (!sub) {
        dress(a, onPath);
        return;
      }
      const title = a.textContent.trim();
      const count = sub.querySelectorAll(':scope > li').length;
      const branch = document.createElement('button');
      branch.type = 'button';
      branch.className = c('branch');
      branch.setAttribute('aria-expanded', 'false');
      branch.append(...a.childNodes);
      a.replaceWith(branch);
      const number = document.createElement('span');
      number.textContent = String(count).padStart(2, '0');
      dress(branch, onPath).append(number);
      const href = a.getAttribute('href');
      if (prefix && href && href !== '#') {
        const all = document.createElement('li');
        all.className = `${c('item')} ${c('all')}`;
        a.textContent = `${prefix} ${title}`;
        all.append(a);
        dress(a, here.test(li.className) || a.getAttribute('aria-current') === 'page');
        sub.prepend(all);
      }
      sub.classList.add(c('list'));
      sub.setAttribute('aria-label', title);
      sub.hidden = true;
      sub.inert = true;
      levels.set(sub, { title, branch, parent: li.closest('ul') });
      nav.append(sub);
    });
    return levels;
  };

  function mount(root, signal) {
    drawer(root);
    mega(root, signal);
    const levels = decorate(root);
    if (!levels) return;
    menu(root, signal, (m) => {
      const { panel, toggle, closer, one, all, css } = m;
      const shade = one('.kodza-header-mega__shade');
      const sheet = one('.kodza-header-mega__sheet');
      const bar = one('.kodza-header-mega__bar');
      const back = one('.kodza-header-mega__back');
      const heading = one('.kodza-header-mega__heading');
      const nav = one('.kodza-header-mega__nav');
      const cross = one('.kodza-header-mega__cross');
      const say = one('.kodza-header-mega__say > span');
      const foot = all('.kodza-header-mega__foot > *');
      const lines = toggle.querySelector('.kodza-header-mega__lines');
      const [top] = levels.keys();
      const first = heading?.firstElementChild;
      const opens = new Map([...levels].filter(([, info]) => info.branch).map(([list, info]) => [info.branch, list]));
      const slides = new Map(); // the rows on their way from one level to the other
      let level = top;

      // A click on the dimmed page closes it
      shade?.addEventListener('click', () => closer?.click(), { signal });

      // The lines cross on the button, and Close arrives with them crossed
      const turn = (el, sign) => {
        const pseudoElement = sign > 0 ? '::before' : '::after';
        return (add) => {
          add(el, [{ translate: '0 0' }, { translate: `0 ${3 * sign}px` }], 0, 200, SWING, { pseudoElement });
          add(el, [{ rotate: '0deg' }, { rotate: `${45 * sign}deg` }], 170, 300, RISE, { pseudoElement });
        };
      };

      const build = (add, mode) => {
        if (mode === 'reduced') {
          add(panel, [{ opacity: 0 }, { opacity: 1 }], 0, 180, 'ease');
          return;
        }
        const time = (parseFloat(css('menu-duration')) || 0.8) * 1000;
        const ease = css('menu-ease') || 'cubic-bezier(0.32, 0.72, 0, 1)';
        const samples = curve(ease);
        // When the drawer is that far in, from 0 to 1
        const at = (p) => (samples.find(([, q]) => q >= p) || [1])[0] * time;
        const dir = panel.classList.contains('is-left') ? -1 : 1;

        add(shade, [{ opacity: 0 }, { opacity: 1 }], 0, time, ease);
        add(sheet, [{ transform: `translateX(calc(${dir * 100}% + ${dir * 80}px))` }, { transform: 'translateX(0px)' }], 0, time, ease);
        if (lines) [turn(lines, 1), turn(lines, -1)].forEach((spin) => spin(add));
        if (cross) [turn(cross, 1), turn(cross, -1)].forEach((spin) => spin(add));
        if (closer) add(say, [{ transform: 'translateY(110%)' }, { transform: 'translateY(0%)' }], at(0.85), 600);

        // Once the drawer is mostly in, the level's name rises, then each row from its mask
        const start = at(0.6);
        const last = rise(add, first, top, start);
        foot.forEach((el, i) => add(el, [{ opacity: 0, transform: 'translateY(10px)' }, { opacity: 1, transform: 'translateY(0px)' }], last + 120 + i * 70, 650));
      };

      // A level coming in with the drawer: its name, then each row from its mask, its hairline
      // drawn from the left. Returns when the last row starts
      const rise = (add, name, list, start) => {
        if (name) add(name, [{ transform: 'translateY(110%)' }, { transform: 'translateY(0%)' }], Math.max(start - 40, 0), 700);
        let last = start;
        [...list.children].forEach((li, i) => {
          last = start + i * 55;
          add(li.querySelector('.kodza-header-mega__word'), [{ transform: 'translateY(135%)' }, { transform: 'translateY(0%)' }], last, 900);
          add(li, [{ transform: 'scaleX(0)' }, { transform: 'scaleX(1)' }], last + 80, 800, RISE, { pseudoElement: '::after' });
          add(li.querySelector('.kodza-header-mega__meta'), [{ opacity: 0, transform: 'translateX(-6px)' }, { opacity: 1, transform: 'translateX(0px)' }], last + 240, 600);
        });
        return last;
      };

      // Closed from a level down, the opening runs backwards on the first level, out of sight:
      // the level on screen goes back into its masks the same way, at the same pace
      let leaving = [];
      const watch = new MutationObserver(() => {
        if (toggle.getAttribute('aria-expanded') === 'true') {
          // Opened again on the way out: it comes back up at the opening's pace
          leaving.forEach((animation) => {
            animation.updatePlaybackRate(-1 / BACK);
            animation.play();
          });
          return;
        }
        leaving.forEach((animation) => animation.cancel());
        leaving = [];
        if (level === top || m.reduced || motion.matches) return;
        const steps = [];
        rise((el, frames, delay, duration, easing = RISE, extra = {}) => {
          if (el) steps.push([el, frames, { delay, duration, easing, ...extra }]);
        }, heading?.lastElementChild, level, 40);
        const total = Math.max(...steps.map(([, , o]) => o.delay + o.duration));
        leaving = steps.map(([el, frames, o]) => el.animate(frames, { ...o, direction: 'reverse', delay: (total - o.delay - o.duration) / BACK, duration: o.duration / BACK, fill: 'forwards' }));
      });
      watch.observe(toggle, { attributes: true, attributeFilter: ['aria-expanded'] });
      signal.addEventListener('abort', () => watch.disconnect(), { once: true });

      // A row on its way: from where it is now if it was already moving
      const slide = (li, frames, options) => {
        const was = slides.get(li);
        if (was) {
          const now = getComputedStyle(li);
          frames[0] = { ...frames[0], opacity: now.opacity, transform: now.transform === 'none' ? 'translateX(0px)' : now.transform };
          was.cancel();
        }
        const animation = li.animate(frames, options);
        slides.set(li, animation);
        return animation;
      };

      // One level down or back up: the rows you leave go out on one side as the new ones come
      // in from the other, and the name on top rolls
      const go = (to, keyboard, up = false) => {
        if (!to || to === level) return;
        const from = level;
        const dir = up ? -1 : 1;
        const quick = m.reduced || motion.matches;
        const { title, parent } = levels.get(to);
        level = to;
        from.inert = true;
        to.hidden = false;
        to.inert = false;

        // The row on top is the way back from the second level on
        if (back) {
          back.disabled = !parent;
          back.classList.toggle('is-deep', !!parent);
          if (parent) {
            back.removeAttribute('aria-hidden');
            back.setAttribute('aria-label', `${title}, back to ${levels.get(parent).title}`);
          } else {
            back.setAttribute('aria-hidden', 'true');
            back.removeAttribute('aria-label');
          }
        }
        levels.get(up ? from : to).branch?.setAttribute('aria-expanded', String(!up));

        if (heading) {
          const name = document.createElement('span');
          name.textContent = title;
          const old = [...heading.children];
          heading.append(name);
          if (quick) old.forEach((el) => el.remove());
          else {
            old.forEach((el) => {
              const transform = getComputedStyle(el).transform;
              el.animate([{ transform: transform === 'none' ? 'translateY(0%)' : transform }, { transform: `translateY(${-110 * dir}%)` }], { duration: 560, easing: RISE, fill: 'forwards' })
                .finished.then(() => el.remove(), () => {});
            });
            // Same pace as the one leaving: the two names roll together, one above the other
            name.animate([{ transform: `translateY(${110 * dir}%)` }, { transform: 'translateY(0%)' }], { duration: 560, easing: RISE });
          }
        }

        if (quick) {
          from.hidden = true;
          slides.forEach((animation) => animation.cancel());
          slides.clear();
        } else {
          // A row is gone (faded by halfway) before the new one takes its place: they never share it
          const out = [...from.children].map((li, i) => slide(li, [{ opacity: 1, transform: 'translateX(0px)', easing: SWING }, { opacity: 0, offset: 0.55 }, { opacity: 0, transform: `translateX(${-48 * dir}px)` }], { duration: 240, delay: i * 20, fill: 'forwards' }));
          [...to.children].forEach((li, i) => slide(li, [{ opacity: 0, transform: `translateX(${56 * dir}px)` }, { opacity: 1, transform: 'translateX(0px)' }], { duration: 560, delay: 170 + i * 40, easing: RISE, fill: 'backwards' }));
          Promise.all(out.map((animation) => animation.finished)).then(() => {
            if (level === from) return;
            from.hidden = true;
            [...from.children].forEach((li) => {
              slides.get(li)?.cancel();
              slides.delete(li);
            });
          }, () => {});
        }
        nav.scrollTo({ top: 0, behavior: quick ? 'auto' : 'smooth' });

        // From the keyboard the focus follows: the first row down there, or the row you came from
        const target = keyboard ? (up ? levels.get(from).branch : to.querySelector(':scope > li > .kodza-header-mega__link')) : panel;
        target?.focus({ preventScroll: true });
      };

      opens.forEach((list, branch) => branch.addEventListener('click', (event) => go(list, event.detail === 0), { signal }));
      back?.addEventListener('click', (event) => go(levels.get(level).parent, event.detail === 0, true), { signal });

      const key = (event) => {
        const branch = event.target.closest?.('.kodza-header-mega__branch');
        const up = levels.get(level).parent;
        if (event.key === 'ArrowRight' && branch) go(opens.get(branch), true);
        else if ((event.key === 'ArrowLeft' || event.key === 'Escape') && up) go(up, true, true);
        else return false;
        event.preventDefault();
        return true;
      };

      const entry = () => top.querySelector(':scope > li > .kodza-header-mega__link.is-current') || top.querySelector(':scope > li > .kodza-header-mega__link');

      // The drawer opens on the button's side, and Close lands right where Menu was
      const ready = () => {
        const t = toggle.getBoundingClientRect();
        const left = root.dataset.side ? root.dataset.side === 'left' : t.left + t.width / 2 < document.documentElement.clientWidth / 2;
        panel.classList.toggle('is-left', left);
        bar.removeAttribute('style');
        const s = sheet.getBoundingClientRect();
        const k = sheet.offsetWidth ? s.width / sheet.offsetWidth : 1;
        const dy = (t.top - s.top) / k;
        const dx = left ? (t.left - s.left) / k : (s.right - t.right) / k;
        if (dy >= 0 && dy <= 96 && dx >= 0 && dx <= sheet.offsetWidth / 3) {
          bar.style.paddingTop = `${dy}px`;
          bar.style[left ? 'paddingLeft' : 'paddingRight'] = `${dx}px`;
        }
      };

      const reset = () => {
        slides.forEach((animation) => animation.cancel());
        slides.clear();
        leaving.forEach((animation) => animation.cancel());
        leaving = [];
        levels.forEach((info, list) => {
          list.hidden = list !== top;
          list.inert = list !== top;
          info.branch?.setAttribute('aria-expanded', 'false');
        });
        level = top;
        if (first) {
          first.getAnimations().forEach((animation) => animation.cancel());
          heading.replaceChildren(first);
        }
        if (back) {
          back.disabled = true;
          back.setAttribute('aria-hidden', 'true');
          back.removeAttribute('aria-label');
          back.classList.remove('is-deep');
        }
        bar.removeAttribute('style');
        panel.classList.remove('is-left');
        nav.scrollTop = 0;
      };
      return { build, ready, entry, key, reset };
    });
  }

  // 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. Each running root
  // keeps its markup as written under KODZA, shared by every Kodza script on the page: a Kodza
  // component inside another one is saved as written, not half-started, and stopped with it.
  const KODZA = Symbol.for('kodza');
  const instances = new WeakMap();
  const find = (scope = document) => [scope, ...scope.querySelectorAll(SELECTOR)].filter((el) => el.matches?.(SELECTOR));
  const attrsOf = (el) => [...el.attributes].map((a) => [a.name, a.value]);
  const put = (el, saved) => {
    [...el.attributes].forEach((a) => el.removeAttribute(a.name));
    saved.attrs.forEach(([name, value]) => el.setAttribute(name, value));
    el.innerHTML = saved.html;
  };
  // The markup inside a root as written: Kodza components already running in it give back their own
  const written = (root) => {
    const all = [...root.querySelectorAll('*')];
    if (!all.some((el) => el[KODZA])) return root.innerHTML;
    const copy = root.cloneNode(true);
    const twins = copy.querySelectorAll('*');
    let last = null;
    all.forEach((el, i) => {
      if (!el[KODZA] || last?.contains(el)) return;
      last = el;
      put(twins[i], el[KODZA]);
    });
    return copy.innerHTML;
  };
  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();
    const saved = { html: written(root), attrs: attrsOf(root), stop: () => destroy(root) };
    instances.set(root, { controller, saved });
    root[KODZA] = saved;
    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);
    // The Kodza components running inside it stop first: once its markup is put back, their own
    // script could not find them any more
    root.querySelectorAll('*').forEach((el) => el[KODZA]?.stop());
    instance.controller.abort();
    delete root[KODZA];
    put(root, instance.saved);
  });
  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).
