Burger Button
A menu button: the lines meet, then cross, and Menu rolls to Close at a fixed width. It follows aria-expanded and fires kodza:burger, so it drives the menu you already have.
View codeA bar of letters over a grouped list: a click glides to the group, an ink pill follows the letter you read, empty letters stay dimmed. Sticks to the top.
With your AI
Say it to your AI. It gets the exact code below, not its best guess, and knows where it goes.
Add the Kodza A–Z Index to my page.
claude mcp add -t http kodza https://kodza.dev/api/mcp
<div class="kodza-az-index" data-kodza-a-z-index>
<section class="kodza-az-index__group">
<div class="kodza-az-index__head">
<h2 class="kodza-az-index__heading">A</h2>
<p class="kodza-az-index__sub">3 terms</p>
</div>
<ul class="kodza-az-index__list">
<li class="kodza-az-index__item"><span class="kodza-az-index__name">Accessibility</span><span class="kodza-az-index__note">Everyone can use it. Including you, on a train, with one thumb.</span></li>
<li class="kodza-az-index__item"><span class="kodza-az-index__name">Animation</span><span class="kodza-az-index__note">Movement that explains something. Otherwise it is a screensaver.</span></li>
<li class="kodza-az-index__item"><span class="kodza-az-index__name">aria-label</span><span class="kodza-az-index__note">A name for something with no visible text. Not a place for SEO keywords.</span></li>
</ul>
</section>
<section class="kodza-az-index__group">
<div class="kodza-az-index__head">
<h2 class="kodza-az-index__heading">B</h2>
<p class="kodza-az-index__sub">2 terms</p>
</div>
<ul class="kodza-az-index__list">
<li class="kodza-az-index__item"><span class="kodza-az-index__name">box-sizing</span><span class="kodza-az-index__note">border-box. Always. The other value is a trap from 1996.</span></li>
<li class="kodza-az-index__item"><span class="kodza-az-index__name">Breakpoint</span><span class="kodza-az-index__note">Where the layout changes its mind. Fewer than you think.</span></li>
</ul>
</section>
<section class="kodza-az-index__group">
<div class="kodza-az-index__head">
<h2 class="kodza-az-index__heading">C</h2>
<p class="kodza-az-index__sub">3 terms</p>
</div>
<ul class="kodza-az-index__list">
<li class="kodza-az-index__item"><span class="kodza-az-index__name">CDN</span><span class="kodza-az-index__note">Someone else's server, closer to your visitor.</span></li>
<li class="kodza-az-index__item"><span class="kodza-az-index__name">clip-path</span><span class="kodza-az-index__note">Cuts an element to a shape. The pill above slides on it.</span></li>
<li class="kodza-az-index__item"><span class="kodza-az-index__name">Container query</span><span class="kodza-az-index__note">The component asks how wide its parent is, not the screen.</span></li>
</ul>
</section>
<section class="kodza-az-index__group">
<div class="kodza-az-index__head">
<h2 class="kodza-az-index__heading">D</h2>
<p class="kodza-az-index__sub">2 terms</p>
</div>
<ul class="kodza-az-index__list">
<li class="kodza-az-index__item"><span class="kodza-az-index__name">Dark mode</span><span class="kodza-az-index__note">Six variables, not a second stylesheet.</span></li>
<li class="kodza-az-index__item"><span class="kodza-az-index__name">Dependency</span><span class="kodza-az-index__note">Code you did not write and now have to update. We ship zero.</span></li>
</ul>
</section>
<section class="kodza-az-index__group">
<div class="kodza-az-index__head">
<h2 class="kodza-az-index__heading">E</h2>
<p class="kodza-az-index__sub">1 term</p>
</div>
<ul class="kodza-az-index__list">
<li class="kodza-az-index__item"><span class="kodza-az-index__name">Easing</span><span class="kodza-az-index__note">The curve between start and end. Never linear, unless it is a progress bar.</span></li>
</ul>
</section>
<section class="kodza-az-index__group">
<div class="kodza-az-index__head">
<h2 class="kodza-az-index__heading">F</h2>
<p class="kodza-az-index__sub">2 terms</p>
</div>
<ul class="kodza-az-index__list">
<li class="kodza-az-index__item"><span class="kodza-az-index__name">Focus ring</span><span class="kodza-az-index__note">Shows keyboard users where they are. Restyle it, never remove it.</span></li>
<li class="kodza-az-index__item"><span class="kodza-az-index__name">FOUC</span><span class="kodza-az-index__note">The page shows up before its CSS does. Briefly, embarrassingly.</span></li>
</ul>
</section>
<section class="kodza-az-index__group">
<div class="kodza-az-index__head">
<h2 class="kodza-az-index__heading">G</h2>
<p class="kodza-az-index__sub">1 term</p>
</div>
<ul class="kodza-az-index__list">
<li class="kodza-az-index__item"><span class="kodza-az-index__name">Grid</span><span class="kodza-az-index__note">Rows and columns, no maths. display: grid does it for you.</span></li>
</ul>
</section>
<section class="kodza-az-index__group">
<div class="kodza-az-index__head">
<h2 class="kodza-az-index__heading">H</h2>
<p class="kodza-az-index__sub">1 term</p>
</div>
<ul class="kodza-az-index__list">
<li class="kodza-az-index__item"><span class="kodza-az-index__name">Hydration</span><span class="kodza-az-index__note">React waking up markup that was already there.</span></li>
</ul>
</section>
<section class="kodza-az-index__group">
<div class="kodza-az-index__head">
<h2 class="kodza-az-index__heading">I</h2>
<p class="kodza-az-index__sub">1 term</p>
</div>
<ul class="kodza-az-index__list">
<li class="kodza-az-index__item"><span class="kodza-az-index__name">IIFE</span><span class="kodza-az-index__note">A function that runs once and keeps its variables to itself.</span></li>
</ul>
</section>
<section class="kodza-az-index__group">
<div class="kodza-az-index__head">
<h2 class="kodza-az-index__heading">K</h2>
<p class="kodza-az-index__sub">1 term</p>
</div>
<ul class="kodza-az-index__list">
<li class="kodza-az-index__item"><span class="kodza-az-index__name">Keyframes</span><span class="kodza-az-index__note">The named steps of an animation. Ours all start with kodza-.</span></li>
</ul>
</section>
<section class="kodza-az-index__group">
<div class="kodza-az-index__head">
<h2 class="kodza-az-index__heading">L</h2>
<p class="kodza-az-index__sub">1 term</p>
</div>
<ul class="kodza-az-index__list">
<li class="kodza-az-index__item"><span class="kodza-az-index__name">Layout shift</span><span class="kodza-az-index__note">The page jumps while you read it. We measure it so it does not.</span></li>
</ul>
</section>
<section class="kodza-az-index__group">
<div class="kodza-az-index__head">
<h2 class="kodza-az-index__heading">M</h2>
<p class="kodza-az-index__sub">2 terms</p>
</div>
<ul class="kodza-az-index__list">
<li class="kodza-az-index__item"><span class="kodza-az-index__name">MCP</span><span class="kodza-az-index__note">How your AI assistant reads the library instead of guessing it.</span></li>
<li class="kodza-az-index__item"><span class="kodza-az-index__name">Motion scale</span><span class="kodza-az-index__note">One variable that speeds up, slows down or stops every animation.</span></li>
</ul>
</section>
<section class="kodza-az-index__group">
<div class="kodza-az-index__head">
<h2 class="kodza-az-index__heading">N</h2>
<p class="kodza-az-index__sub">1 term</p>
</div>
<ul class="kodza-az-index__list">
<li class="kodza-az-index__item"><span class="kodza-az-index__name">npm</span><span class="kodza-az-index__note">Where packages live. You will not need it here.</span></li>
</ul>
</section>
<section class="kodza-az-index__group">
<div class="kodza-az-index__head">
<h2 class="kodza-az-index__heading">O</h2>
<p class="kodza-az-index__sub">1 term</p>
</div>
<ul class="kodza-az-index__list">
<li class="kodza-az-index__item"><span class="kodza-az-index__name">overflow: clip</span><span class="kodza-az-index__note">What does not fit stays out. And nothing turns into a scroll box.</span></li>
</ul>
</section>
<section class="kodza-az-index__group">
<div class="kodza-az-index__head">
<h2 class="kodza-az-index__heading">P</h2>
<p class="kodza-az-index__sub">2 terms</p>
</div>
<ul class="kodza-az-index__list">
<li class="kodza-az-index__item"><span class="kodza-az-index__name">position: sticky</span><span class="kodza-az-index__note">It stays. You scroll. The bar above is one.</span></li>
<li class="kodza-az-index__item"><span class="kodza-az-index__name">prefers-reduced-motion</span><span class="kodza-az-index__note">The visitor asked for less movement. Everything arrives at once.</span></li>
</ul>
</section>
<section class="kodza-az-index__group">
<div class="kodza-az-index__head">
<h2 class="kodza-az-index__heading">S</h2>
<p class="kodza-az-index__sub">2 terms</p>
</div>
<ul class="kodza-az-index__list">
<li class="kodza-az-index__item"><span class="kodza-az-index__name">Scroll snap</span><span class="kodza-az-index__note">The page lands on a card instead of between two.</span></li>
<li class="kodza-az-index__item"><span class="kodza-az-index__name">Specificity</span><span class="kodza-az-index__note">Why your CSS is ignored. Add a class, never !important.</span></li>
</ul>
</section>
<section class="kodza-az-index__group">
<div class="kodza-az-index__head">
<h2 class="kodza-az-index__heading">T</h2>
<p class="kodza-az-index__sub">2 terms</p>
</div>
<ul class="kodza-az-index__list">
<li class="kodza-az-index__item"><span class="kodza-az-index__name">Tabular numbers</span><span class="kodza-az-index__note">Digits of equal width, so prices line up in a column.</span></li>
<li class="kodza-az-index__item"><span class="kodza-az-index__name">Theme</span><span class="kodza-az-index__note">WordPress's opinion about your page. Usually about margins.</span></li>
</ul>
</section>
<section class="kodza-az-index__group">
<div class="kodza-az-index__head">
<h2 class="kodza-az-index__heading">V</h2>
<p class="kodza-az-index__sub">2 terms</p>
</div>
<ul class="kodza-az-index__list">
<li class="kodza-az-index__item"><span class="kodza-az-index__name">Vanilla</span><span class="kodza-az-index__note">HTML, CSS and JavaScript, the way the browser ships them.</span></li>
<li class="kodza-az-index__item"><span class="kodza-az-index__name">View transitions</span><span class="kodza-az-index__note">The browser animates from one state to the next. Firefox is on its way.</span></li>
</ul>
</section>
<section class="kodza-az-index__group">
<div class="kodza-az-index__head">
<h2 class="kodza-az-index__heading">W</h2>
<p class="kodza-az-index__sub">2 terms</p>
</div>
<ul class="kodza-az-index__list">
<li class="kodza-az-index__item"><span class="kodza-az-index__name">Web Animations</span><span class="kodza-az-index__note">Keyframes from JavaScript. Reversible, and no library to install.</span></li>
<li class="kodza-az-index__item"><span class="kodza-az-index__name">WordPress</span><span class="kodza-az-index__note">Runs a huge part of the web. Every theme disagrees on margins.</span></li>
</ul>
</section>
</div>
/* Kodza License: free to use, client work included. Not to resell or redistribute, ported or not: https://kodza.dev/docs/#print-license */
/* Kodza — A–Z Index
A bar of letters above a grouped list (glossary, directory, long FAQ). A click glides to the
group; as you scroll, an ink pill slides to the letter of the group you read, and the letters
with nothing under them stay dimmed. The bar sticks to the top, takes a frosted veil once
something passes under it, and scrolls sideways when it runs out of room.
Everything is tuned with the --kz-az-index-* variables. */
.kodza-az-index {
--kz-az-index-fg: #0a0a0a;
--kz-az-index-muted: rgba(10, 10, 10, 0.62);
--kz-az-index-faint: rgba(10, 10, 10, 0.34); /* letters with nothing under them */
--kz-az-index-line: rgba(10, 10, 10, 0.1);
--kz-az-index-hover: rgba(10, 10, 10, 0.06);
--kz-az-index-ink: #0a0a0a;
--kz-az-index-on-ink: #ffffff;
--kz-az-index-veil: rgba(255, 255, 255, 0.78); /* behind the bar once it sticks */
--kz-az-index-top: 0px; /* height of a fixed header above it */
--kz-az-index-bleed: 0px; /* how far the veil reaches past the sides */
--kz-az-index-aside: 160px; /* the column of a group's letter, on wide layouts */
--kz-az-index-duration: 0.5s;
--kz-az-index-ease: cubic-bezier(0.22, 1, 0.36, 1);
container-type: inline-size;
color: var(--kz-az-index-fg);
}
/* The bar (built by the script) */
.kodza-az-index__bar {
position: sticky;
top: var(--kz-az-index-top);
z-index: 2;
padding: 10px 0;
}
.kodza-az-index__bar::before {
content: "";
position: absolute;
inset: 0 calc(var(--kz-az-index-bleed) * -1);
background: var(--kz-az-index-veil);
-webkit-backdrop-filter: blur(14px);
backdrop-filter: blur(14px);
box-shadow: 0 1px 0 var(--kz-az-index-line);
opacity: 0;
transition: opacity calc(0.15s * var(--kz-motion-scale, 1)) ease;
pointer-events: none;
}
.kodza-az-index.is-stuck .kodza-az-index__bar::before {
opacity: 1;
}
/* One row; more letters than room, it scrolls sideways, its edges fading on the side that has more */
.kodza-az-index__track {
--kz-az-index-fade-l: 0px;
--kz-az-index-fade-r: 0px;
position: relative;
display: flex;
gap: 2px;
padding: 4px;
overflow-x: auto;
overscroll-behavior-x: contain;
scrollbar-width: none;
}
.kodza-az-index__track::-webkit-scrollbar {
display: none;
}
.kodza-az-index__track.is-scroll {
-webkit-mask-image: linear-gradient(90deg, transparent, #000 var(--kz-az-index-fade-l), #000 calc(100% - var(--kz-az-index-fade-r)), transparent);
mask-image: linear-gradient(90deg, transparent, #000 var(--kz-az-index-fade-l), #000 calc(100% - var(--kz-az-index-fade-r)), transparent);
}
.kodza-az-index__track.has-before {
--kz-az-index-fade-l: 32px;
}
.kodza-az-index__track.has-after {
--kz-az-index-fade-r: 32px;
}
.kodza-az-index__key,
.kodza-az-index__ghost {
display: inline-flex;
flex: none;
align-items: center;
justify-content: center;
box-sizing: border-box;
min-width: 32px;
height: 32px;
padding: 0 9px;
border-radius: var(--kz-radius-pill, 999px);
font-family: var(--kz-font-label, var(--kz-font-mono, "Geist Mono", ui-monospace, "SF Mono", Menlo, monospace));
font-size: var(--kz-font-label-size, 12.5px);
font-weight: var(--kz-font-label-weight, 500);
line-height: 1;
letter-spacing: var(--kz-font-label-tracking, normal);
text-transform: var(--kz-font-label-case, none);
white-space: nowrap;
}
.kodza-az-index__key {
position: relative;
color: var(--kz-az-index-fg);
text-decoration: none;
transition: background-color calc(0.2s * var(--kz-motion-scale, 1)) ease;
}
/* A letter with nothing under it */
span.kodza-az-index__key {
color: var(--kz-az-index-faint);
}
@media (hover: hover) {
a.kodza-az-index__key:not([aria-current]):hover {
background: var(--kz-az-index-hover);
}
}
a.kodza-az-index__key:focus-visible {
outline: 2px solid var(--kz-az-index-fg);
outline-offset: 2px;
}
/* The pill covers the whole row (scrolled part included) and is cut down to the letter read:
only clip-path moves. A white copy of the letters, cut the same way, turns them white exactly
at its edges */
.kodza-az-index__indicator,
.kodza-az-index__mirror {
position: absolute;
inset: 0 auto 0 0;
box-sizing: border-box;
width: var(--kz-az-index-track, 100%);
clip-path: inset(4px var(--kz-az-index-r, 100%) 4px var(--kz-az-index-l, 0) round var(--kz-radius-pill, 999px));
pointer-events: none;
}
.kodza-az-index__indicator {
background: var(--kz-az-index-ink);
}
.kodza-az-index__mirror {
display: flex;
gap: 2px;
padding: 4px;
color: var(--kz-az-index-on-ink);
}
.kodza-az-index.is-live .kodza-az-index__indicator,
.kodza-az-index.is-live .kodza-az-index__mirror {
transition: clip-path calc(var(--kz-az-index-duration) * var(--kz-motion-scale, 1)) var(--kz-az-index-ease);
}
/* The groups */
.kodza-az-index__group {
display: grid;
gap: 14px;
padding: 28px 0 32px;
border-top: 1px solid var(--kz-az-index-line);
}
.kodza-az-index__head {
min-width: 0;
}
.kodza-az-index__heading {
width: fit-content;
margin: 0;
color: var(--kz-az-index-fg);
font-family: var(--kz-font-display, inherit);
font-size: calc(44px * var(--kz-font-display-scale, 1));
font-weight: var(--kz-font-display-weight, 600);
line-height: 1;
letter-spacing: var(--kz-font-display-tracking, -0.045em);
text-transform: var(--kz-font-display-case, inherit);
}
.kodza-az-index__heading:focus-visible {
outline: 2px solid var(--kz-az-index-fg);
outline-offset: 6px;
border-radius: calc(4px * var(--kz-radius-scale, 1));
}
.kodza-az-index__sub {
margin: 10px 0 0;
color: var(--kz-az-index-muted);
font-family: var(--kz-font-label, var(--kz-font-mono, "Geist Mono", ui-monospace, "SF Mono", Menlo, monospace));
font-size: var(--kz-font-label-size, 12px);
font-weight: var(--kz-font-label-weight, 400);
line-height: var(--kz-font-label-leading, 1.4);
letter-spacing: var(--kz-font-label-tracking, normal);
text-transform: var(--kz-font-label-case, none);
}
.kodza-az-index__list {
margin: 0;
padding: 0;
list-style: none;
}
.kodza-az-index__item {
display: grid;
gap: 4px;
padding: 14px 0;
border-top: 1px solid var(--kz-az-index-line);
}
.kodza-az-index__item:first-child {
padding-top: 4px;
border-top: 0;
}
.kodza-az-index__item:last-child {
padding-bottom: 0;
}
.kodza-az-index__name {
color: var(--kz-az-index-fg);
font-size: 17px;
font-weight: 500;
line-height: 1.4;
letter-spacing: -0.01em;
}
.kodza-az-index__note {
color: var(--kz-az-index-muted);
font-size: 15px;
line-height: 1.55;
text-wrap: pretty;
}
/* Wide: the letter keeps to its column and stays in view while its list scrolls by */
@container (min-width: 640px) {
.kodza-az-index__group {
grid-template-columns: var(--kz-az-index-aside) minmax(0, 1fr);
gap: 24px;
padding: 32px 0 36px;
}
.kodza-az-index__head {
position: sticky;
top: calc(var(--kz-az-index-top) + var(--kz-az-index-bar, 52px) + 28px);
align-self: start;
}
.kodza-az-index__item {
grid-template-columns: minmax(0, 2fr) minmax(0, 3fr);
gap: 24px;
align-items: baseline;
}
}
@media (prefers-reduced-motion: reduce) {
.kodza-az-index.is-live .kodza-az-index__indicator,
.kodza-az-index.is-live .kodza-az-index__mirror,
.kodza-az-index__bar::before {
transition: none;
}
}
/* kodza-guard 4c28cb1df9ae — 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-az-index__key):not(#_) {
outline: revert;
background-color: revert;
background-image: revert;
outline-offset: revert;
}
:where(.kodza-az-index__heading):not(#_) {
border-radius: revert;
outline: revert;
outline-offset: revert;
}
/* What the browser gives, on everything the component leaves to it */
.kodza-az-index:not(#_),
.kodza-az-index__track:not(#_),
.kodza-az-index__group:not(#_),
.kodza-az-index__head:not(#_),
.kodza-az-index__name:not(#_),
.kodza-az-index__note:not(#_) {
box-sizing: revert;
}
.kodza-az-index__bar:not(#_) {
border: revert;
margin: 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-az-index__key:not(#_) {
border: revert;
margin: revert;
list-style: revert;
float: revert;
box-shadow: revert;
appearance: revert;
max-width: revert;
max-height: revert;
font-style: revert;
text-underline-offset: revert;
text-align: revert;
text-indent: revert;
text-shadow: revert;
vertical-align: revert;
}
.kodza-az-index__heading:not(#_) {
border: revert;
padding: 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;
appearance: revert;
max-width: revert;
max-height: revert;
font-style: revert;
text-underline-offset: revert;
text-align: revert;
text-indent: revert;
text-shadow: revert;
vertical-align: revert;
}
.kodza-az-index__sub: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-underline-offset: revert;
text-align: revert;
text-indent: revert;
text-shadow: revert;
vertical-align: revert;
}
.kodza-az-index__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-az-index__item:not(#_) {
margin: revert;
border-radius: revert;
outline: revert;
text-decoration: revert;
transition: revert;
list-style: revert;
box-sizing: revert;
float: revert;
position: revert;
border-right-width: revert;
border-bottom-width: revert;
border-left-width: revert;
border-right-style: revert;
border-bottom-style: revert;
border-left-style: revert;
border-right-color: revert;
border-bottom-color: revert;
border-left-color: 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-az-index__bar:not(#_):where(:not([hidden])),
.kodza-az-index__heading:not(#_):where(:not([hidden])),
.kodza-az-index__sub:not(#_):where(:not([hidden])),
.kodza-az-index__list:not(#_):where(:not([hidden])) {
display: revert;
}
.kodza-az-index__bar:not(#_):where([hidden]),
.kodza-az-index__heading:not(#_):where([hidden]),
.kodza-az-index__sub:not(#_):where([hidden]),
.kodza-az-index__list:not(#_):where([hidden]) {
display: none;
}
.kodza-az-index__bar: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-az-index__bar:not(#_) {
position: sticky;
padding: 10px 0;
}
.kodza-az-index__key:where(a):not(#_) {
display: inline-flex;
box-sizing: border-box;
padding: 0 9px;
border-radius: var(--kz-radius-pill, 999px);
font-family: var(--kz-font-label, var(--kz-font-mono, "Geist Mono", ui-monospace, "SF Mono", Menlo, monospace));
font-size: var(--kz-font-label-size, 12.5px);
font-weight: var(--kz-font-label-weight, 500);
line-height: 1;
letter-spacing: var(--kz-font-label-tracking, normal);
text-transform: var(--kz-font-label-case, none);
}
.kodza-az-index__key:where(a):not(#_) {
position: relative;
color: var(--kz-az-index-fg);
text-decoration: none;
transition: background-color calc(0.2s * var(--kz-motion-scale, 1)) ease;
}
@media (hover: hover) {
a.kodza-az-index__key:not([aria-current]):hover:not(#_) {
background: var(--kz-az-index-hover);
}
}
a.kodza-az-index__key:focus-visible:not(#_) {
outline: 2px solid var(--kz-az-index-fg);
outline-offset: 2px;
}
.kodza-az-index__heading:not(#_) {
margin: 0;
color: var(--kz-az-index-fg);
font-family: var(--kz-font-display, inherit);
font-size: calc(44px * var(--kz-font-display-scale, 1));
font-weight: var(--kz-font-display-weight, 600);
line-height: 1;
letter-spacing: var(--kz-font-display-tracking, -0.045em);
text-transform: var(--kz-font-display-case, inherit);
}
.kodza-az-index__heading:focus-visible:not(#_) {
outline: 2px solid var(--kz-az-index-fg);
outline-offset: 6px;
border-radius: calc(4px * var(--kz-radius-scale, 1));
}
.kodza-az-index__sub:not(#_) {
margin: 10px 0 0;
color: var(--kz-az-index-muted);
font-family: var(--kz-font-label, var(--kz-font-mono, "Geist Mono", ui-monospace, "SF Mono", Menlo, monospace));
font-size: var(--kz-font-label-size, 12px);
font-weight: var(--kz-font-label-weight, 400);
line-height: var(--kz-font-label-leading, 1.4);
letter-spacing: var(--kz-font-label-tracking, normal);
text-transform: var(--kz-font-label-case, none);
}
.kodza-az-index__list:not(#_) {
margin: 0;
padding: 0;
list-style: none;
}
.kodza-az-index__item:not(#_) {
display: grid;
padding: 14px 0;
border-top: 1px solid var(--kz-az-index-line);
}
.kodza-az-index__item:first-child:not(#_) {
padding-top: 4px;
border-top: 0;
}
.kodza-az-index__item:last-child:not(#_) {
padding-bottom: 0;
}
// Kodza License: free to use, client work included. Not to resell or redistribute, ported or not: https://kodza.dev/docs/#print-license
// Kodza — A–Z Index
// Builds the bar of letters from the groups below it: a click glides to the group, and as you
// scroll, the ink pill slides to the letter of the group you read. Settings: the --kz-az-index-*
// variables, the constants below, and these attributes:
// data-letters on the root: absent, A to Z; "groups", one label per group, in their order
// (months, years, chapters); anything else, the letters as written ("#ABC…") or separated by
// commas ("2024, 2025, 2026")
// data-nav-label on the root: the bar's name for screen readers ("Jump to")
// data-key on a group: the letter it files under (by default its heading's first letter,
// accents dropped); data-label: its text in the bar with data-letters="groups"
(() => {
const SELECTOR = '[data-kodza-a-z-index]';
const ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
// A group counts as the one being read once its top passes this far under the bar (px)
const READ_LINE = 24;
// After a click the pill stays on the chosen letter until the page stops scrolling; this is the
// longest it waits for that, a time to wait and not a movement (ms)
const GLIDE_WAIT = 1600;
const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
// The site's pace, --kz-motion-scale (set on :root, read on the instance): every duration of the
// script times it, and 0 moves nothing, like reduced motion
const paceOf = (el) => {
const pace = parseFloat(getComputedStyle(el).getPropertyValue('--kz-motion-scale'));
return pace >= 0 ? pace : 1;
};
// Accented letters file under their plain letter
const fold = (text) => text.normalize('NFD').replace(/[̀-ͯ]/g, '').trim().toUpperCase();
const slug = (text) => fold(text).toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '') || 'group';
// One instance, with its own state: two copies on a page never interfere. Everything it
// starts is tied to `signal` (listeners get { signal }, observers and timers stop on its
// 'abort' event), so kodza:destroy stops it all.
function mount(root, signal) {
const pace = paceOf(root);
const still = reduceMotion || !pace; // the still path: no motion asked, by the visitor or the site
const groups = [...root.querySelectorAll('.kodza-az-index__group')].filter((group) => group.closest(SELECTOR) === root);
if (!groups.length) return;
const headingOf = (group) => group.querySelector('.kodza-az-index__heading');
const fileOf = (group) => fold(group.dataset.key || headingOf(group)?.textContent.trim().charAt(0) || '');
// The keys of the bar, each with the group it jumps to (none: nothing under that letter)
const mode = root.dataset.letters?.trim();
let keys;
if (mode === 'groups') {
keys = groups.map((group) => ({ label: group.dataset.label || group.dataset.key || headingOf(group)?.textContent.trim() || '', group }));
} else {
const letters = mode ? (mode.includes(',') ? mode.split(',') : [...mode]).map((s) => s.trim()).filter(Boolean) : [...ALPHABET];
const taken = new Set();
keys = letters.map((label) => {
const group = groups.find((g) => !taken.has(g) && fileOf(g) === fold(label)) || null;
if (group) taken.add(group);
return { label, group };
});
}
const keyOf = new Map(keys.filter((key) => key.group).map((key) => [key.group, key]));
// Groups get an id to link to (the markup comes back as written on kodza:destroy)
keyOf.forEach((key, group) => {
if (group.id) return;
const base = `kz-az-${slug(key.label)}`;
let id = base;
for (let n = 2; document.getElementById(id); n += 1) id = `${base}-${n}`;
group.id = id;
});
const bar = document.createElement('nav');
bar.className = 'kodza-az-index__bar';
bar.setAttribute('aria-label', root.dataset.navLabel || 'Jump to');
const track = document.createElement('div');
track.className = 'kodza-az-index__track';
const indicator = document.createElement('span');
indicator.className = 'kodza-az-index__indicator';
indicator.setAttribute('aria-hidden', 'true');
const mirror = document.createElement('span');
mirror.className = 'kodza-az-index__mirror';
mirror.setAttribute('aria-hidden', 'true');
keys.forEach((key) => {
key.el = document.createElement(key.group ? 'a' : 'span');
key.el.className = 'kodza-az-index__key';
key.el.textContent = key.label;
if (key.group) key.el.href = `#${key.group.id}`;
else key.el.setAttribute('aria-hidden', 'true');
const ghost = document.createElement('span');
ghost.className = 'kodza-az-index__ghost';
ghost.textContent = key.label;
mirror.append(ghost);
});
track.append(indicator, ...keys.map((key) => key.el), mirror);
bar.append(track);
root.prepend(bar);
let current = null;
let live = false;
// The pill, cut down to the current key; the row scrolled so that key is in view
const measure = () => {
track.style.removeProperty('--kz-az-index-track');
const width = track.scrollWidth;
track.classList.toggle('is-scroll', width > track.clientWidth + 1);
track.style.setProperty('--kz-az-index-track', `${width}px`);
if (!current) return;
const { offsetLeft: left, offsetWidth: size } = current.el;
track.style.setProperty('--kz-az-index-l', `${left}px`);
track.style.setProperty('--kz-az-index-r', `${width - left - size}px`);
};
const edges = () => {
const end = track.scrollWidth - track.clientWidth;
track.classList.toggle('has-before', track.scrollLeft > 1);
track.classList.toggle('has-after', track.scrollLeft < end - 1);
};
const follow = (smooth) => {
if (!current || !track.classList.contains('is-scroll')) return;
const left = current.el.offsetLeft - (track.clientWidth - current.el.offsetWidth) / 2;
track.scrollTo({ left: Math.max(0, left), behavior: smooth && !still ? 'smooth' : 'auto' });
};
const show = (key, smooth = true) => {
if (!key || key === current) return;
const first = !current;
current?.el.removeAttribute('aria-current');
current = key;
key.el.setAttribute('aria-current', 'true');
// The first letter is placed, not slid in from the edge (a band that was hidden)
if (first) root.classList.remove('is-live');
measure();
follow(smooth && !first);
if (first && live) {
void track.offsetWidth;
root.classList.add('is-live');
}
};
const stickAt = () => parseFloat(getComputedStyle(bar).top) || 0;
// The group being read: the last one whose top has passed under the bar. At the very end of
// the page, the last group in view (a short last group never reaches the bar). A letter clicked
// stays lit until the page moves again, even when its group cannot reach the bar
let gliding = false;
let held = null;
let heldAt = 0;
let frame = 0;
const spy = () => {
frame = 0;
if (!bar.offsetHeight) return; // hidden (display: none): measured once it shows
const below = stickAt() + bar.offsetHeight;
root.classList.toggle('is-stuck', groups[0].getBoundingClientRect().top < bar.getBoundingClientRect().bottom - 0.5);
if (gliding) return;
if (held) {
if (Math.abs(window.scrollY - heldAt) < 2) return show(held);
held = null;
}
let read = null;
const end = window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - 2
&& root.getBoundingClientRect().bottom <= window.innerHeight + 2;
for (const group of groups) {
const top = group.getBoundingClientRect().top;
if (top > (end ? window.innerHeight - 1 : below + READ_LINE)) break;
if (keyOf.has(group)) read = group;
}
show(keyOf.get(read) || keyOf.get(groups.find((group) => keyOf.has(group))));
};
const schedule = () => {
if (!frame) frame = requestAnimationFrame(spy);
};
let wait = 0;
const land = () => {
if (!gliding) return;
gliding = false;
heldAt = window.scrollY;
clearTimeout(wait);
schedule();
};
const go = (key, smooth) => {
const y = key.group.getBoundingClientRect().top + window.scrollY - stickAt() - bar.offsetHeight;
show(key);
held = key;
if (smooth) {
gliding = true;
clearTimeout(wait);
wait = setTimeout(land, GLIDE_WAIT);
}
window.scrollTo({ top: Math.max(0, Math.round(y)), behavior: smooth ? 'smooth' : 'auto' });
if (!smooth) heldAt = window.scrollY;
};
track.addEventListener('click', (event) => {
const key = keys.find((k) => k.group && k.el === event.target.closest?.('.kodza-az-index__key'));
if (!key || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
event.preventDefault();
go(key, !still);
history.replaceState(history.state, '', `#${key.group.id}`);
// From the keyboard, the focus follows to the group's heading
if (event.detail === 0) {
const target = headingOf(key.group) || key.group;
if (!target.hasAttribute('tabindex')) target.setAttribute('tabindex', '-1');
target.focus({ preventScroll: true });
}
}, { signal });
track.addEventListener('scroll', edges, { passive: true, signal });
window.addEventListener('scroll', schedule, { passive: true, signal });
window.addEventListener('scrollend', land, { signal });
window.addEventListener('resize', schedule, { signal });
// The bar's height, for the groups' sticky letters; the widths, once fonts load or the row resizes
const resize = new ResizeObserver(() => {
root.style.setProperty('--kz-az-index-bar', `${bar.offsetHeight}px`);
measure();
edges();
schedule();
});
[bar, track, ...keys.map((key) => key.el)].forEach((el) => resize.observe(el));
signal.addEventListener('abort', () => {
resize.disconnect();
cancelAnimationFrame(frame);
clearTimeout(wait);
});
// A link to one of the groups (#kz-az-c) lands on it once the bar exists
const landed = keys.find((key) => key.group && `#${key.group.id}` === decodeURIComponent(location.hash));
if (landed) go(landed, false);
spy();
follow(false);
edges();
requestAnimationFrame(() => {
if (signal.aborted) return;
live = true;
root.classList.add('is-live');
});
}
// 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. Two
// components on one root (a reveal on a stat card) both start, and stop together.
const KODZA = Symbol.for('kodza');
const RUNNING = Symbol.for(`kodza ${SELECTOR}`); // a root this component runs on, for every copy of this script
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) => {
// Already running, maybe from another copy of this script. Marked is-ready with no Kodza component
// running on it, a root is a copy of a started one (a cloned slide): it is left as it is
if (root[RUNNING] || (root.classList.contains('is-ready') && !root[KODZA])) return;
// Rendered by React (data-kodza-manual): React starts it, with an init aimed at that markup once
// it has hydrated it. Any earlier start would change markup React has not taken over yet
const owner = root.closest('[data-kodza-manual]');
if (owner && !(scope && owner.contains(scope))) return;
const controller = new AbortController();
// Another Kodza component already running on this root: its markup as written is kept, and the
// two stop together
const before = root[KODZA];
const saved = { html: before ? before.html : written(root), attrs: before ? before.attrs : attrsOf(root), stop: () => destroy(root) };
if (before) before.above = saved;
instances.set(root, { controller, saved, before });
root[KODZA] = saved;
root[RUNNING] = true;
mount(root, controller.signal);
root.classList.add('is-ready');
});
const destroy = (scope) => find(scope).forEach((root) => {
const instance = instances.get(root);
if (!instance) return;
instances.delete(root);
instance.saved.above?.stop(); // a Kodza component started after it on the same root stops first
// 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[RUNNING];
// Started on top of another one: that one stops next, and puts the markup back
if (instance.before) {
delete instance.before.above;
root[KODZA] = instance.before;
return instance.before.stop();
}
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 });
})();
'use client';
// Kodza — A–Z Index, for React. From https://kodza.dev/components/a-z-index/
// 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: a-z-index.css and a-z-index.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.
// data-kodza-manual keeps the script from starting it on its own: the effect below does, once
// React has hydrated the markup (on a page that streams in, the script can load first).
// 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 './a-z-index.css';
export type AZIndexProps = Omit<ComponentPropsWithoutRef<'div'>, "children" | "vars"> & {
/** Settings, as CSS variables on the root */
vars?: {
/** default: #0a0a0a */
"--kz-az-index-fg"?: string | number;
/** default: rgba(10, 10, 10, 0.62) */
"--kz-az-index-muted"?: string | number;
/** letters with nothing under them — default: rgba(10, 10, 10, 0.34) */
"--kz-az-index-faint"?: string | number;
/** default: rgba(10, 10, 10, 0.1) */
"--kz-az-index-line"?: string | number;
/** default: rgba(10, 10, 10, 0.06) */
"--kz-az-index-hover"?: string | number;
/** default: #0a0a0a */
"--kz-az-index-ink"?: string | number;
/** default: #ffffff */
"--kz-az-index-on-ink"?: string | number;
/** behind the bar once it sticks — default: rgba(255, 255, 255, 0.78) */
"--kz-az-index-veil"?: string | number;
/** height of a fixed header above it — default: 0px */
"--kz-az-index-top"?: string | number;
/** how far the veil reaches past the sides — default: 0px */
"--kz-az-index-bleed"?: string | number;
/** the column of a group's letter, on wide layouts — default: 160px */
"--kz-az-index-aside"?: string | number;
/** default: 0.5s */
"--kz-az-index-duration"?: string | number;
/** default: cubic-bezier(0.22, 1, 0.36, 1) */
"--kz-az-index-ease"?: string | number;
};
className?: string;
};
export function AZIndex({ vars, className, style, ...rest }: AZIndexProps) {
const ref = useRef<ComponentRef<'div'>>(null);
const classes = ["kodza-az-index", 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('./a-z-index.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 (
<div data-kodza-a-z-index="" key={key} {...rest} className={classes} data-kodza-manual style={{ ...vars, ...style } as CSSProperties} ref={ref}>
<section className="kodza-az-index__group">
<div className="kodza-az-index__head">
<h2 className="kodza-az-index__heading">A</h2>
<p className="kodza-az-index__sub">3 terms</p>
</div>
<ul className="kodza-az-index__list">
<li className="kodza-az-index__item">
<span className="kodza-az-index__name">Accessibility</span>
<span className="kodza-az-index__note">Everyone can use it. Including you, on a train, with one thumb.</span>
</li>
<li className="kodza-az-index__item">
<span className="kodza-az-index__name">Animation</span>
<span className="kodza-az-index__note">Movement that explains something. Otherwise it is a screensaver.</span>
</li>
<li className="kodza-az-index__item">
<span className="kodza-az-index__name">aria-label</span>
<span className="kodza-az-index__note">A name for something with no visible text. Not a place for SEO keywords.</span>
</li>
</ul>
</section>
<section className="kodza-az-index__group">
<div className="kodza-az-index__head">
<h2 className="kodza-az-index__heading">B</h2>
<p className="kodza-az-index__sub">2 terms</p>
</div>
<ul className="kodza-az-index__list">
<li className="kodza-az-index__item">
<span className="kodza-az-index__name">box-sizing</span>
<span className="kodza-az-index__note">border-box. Always. The other value is a trap from 1996.</span>
</li>
<li className="kodza-az-index__item">
<span className="kodza-az-index__name">Breakpoint</span>
<span className="kodza-az-index__note">Where the layout changes its mind. Fewer than you think.</span>
</li>
</ul>
</section>
<section className="kodza-az-index__group">
<div className="kodza-az-index__head">
<h2 className="kodza-az-index__heading">C</h2>
<p className="kodza-az-index__sub">3 terms</p>
</div>
<ul className="kodza-az-index__list">
<li className="kodza-az-index__item">
<span className="kodza-az-index__name">CDN</span>
<span className="kodza-az-index__note">Someone else's server, closer to your visitor.</span>
</li>
<li className="kodza-az-index__item">
<span className="kodza-az-index__name">clip-path</span>
<span className="kodza-az-index__note">Cuts an element to a shape. The pill above slides on it.</span>
</li>
<li className="kodza-az-index__item">
<span className="kodza-az-index__name">Container query</span>
<span className="kodza-az-index__note">The component asks how wide its parent is, not the screen.</span>
</li>
</ul>
</section>
<section className="kodza-az-index__group">
<div className="kodza-az-index__head">
<h2 className="kodza-az-index__heading">D</h2>
<p className="kodza-az-index__sub">2 terms</p>
</div>
<ul className="kodza-az-index__list">
<li className="kodza-az-index__item">
<span className="kodza-az-index__name">Dark mode</span>
<span className="kodza-az-index__note">Six variables, not a second stylesheet.</span>
</li>
<li className="kodza-az-index__item">
<span className="kodza-az-index__name">Dependency</span>
<span className="kodza-az-index__note">Code you did not write and now have to update. We ship zero.</span>
</li>
</ul>
</section>
<section className="kodza-az-index__group">
<div className="kodza-az-index__head">
<h2 className="kodza-az-index__heading">E</h2>
<p className="kodza-az-index__sub">1 term</p>
</div>
<ul className="kodza-az-index__list">
<li className="kodza-az-index__item">
<span className="kodza-az-index__name">Easing</span>
<span className="kodza-az-index__note">The curve between start and end. Never linear, unless it is a progress bar.</span>
</li>
</ul>
</section>
<section className="kodza-az-index__group">
<div className="kodza-az-index__head">
<h2 className="kodza-az-index__heading">F</h2>
<p className="kodza-az-index__sub">2 terms</p>
</div>
<ul className="kodza-az-index__list">
<li className="kodza-az-index__item">
<span className="kodza-az-index__name">Focus ring</span>
<span className="kodza-az-index__note">Shows keyboard users where they are. Restyle it, never remove it.</span>
</li>
<li className="kodza-az-index__item">
<span className="kodza-az-index__name">FOUC</span>
<span className="kodza-az-index__note">The page shows up before its CSS does. Briefly, embarrassingly.</span>
</li>
</ul>
</section>
<section className="kodza-az-index__group">
<div className="kodza-az-index__head">
<h2 className="kodza-az-index__heading">G</h2>
<p className="kodza-az-index__sub">1 term</p>
</div>
<ul className="kodza-az-index__list">
<li className="kodza-az-index__item">
<span className="kodza-az-index__name">Grid</span>
<span className="kodza-az-index__note">Rows and columns, no maths. display: grid does it for you.</span>
</li>
</ul>
</section>
<section className="kodza-az-index__group">
<div className="kodza-az-index__head">
<h2 className="kodza-az-index__heading">H</h2>
<p className="kodza-az-index__sub">1 term</p>
</div>
<ul className="kodza-az-index__list">
<li className="kodza-az-index__item">
<span className="kodza-az-index__name">Hydration</span>
<span className="kodza-az-index__note">React waking up markup that was already there.</span>
</li>
</ul>
</section>
<section className="kodza-az-index__group">
<div className="kodza-az-index__head">
<h2 className="kodza-az-index__heading">I</h2>
<p className="kodza-az-index__sub">1 term</p>
</div>
<ul className="kodza-az-index__list">
<li className="kodza-az-index__item">
<span className="kodza-az-index__name">IIFE</span>
<span className="kodza-az-index__note">A function that runs once and keeps its variables to itself.</span>
</li>
</ul>
</section>
<section className="kodza-az-index__group">
<div className="kodza-az-index__head">
<h2 className="kodza-az-index__heading">K</h2>
<p className="kodza-az-index__sub">1 term</p>
</div>
<ul className="kodza-az-index__list">
<li className="kodza-az-index__item">
<span className="kodza-az-index__name">Keyframes</span>
<span className="kodza-az-index__note">The named steps of an animation. Ours all start with kodza-.</span>
</li>
</ul>
</section>
<section className="kodza-az-index__group">
<div className="kodza-az-index__head">
<h2 className="kodza-az-index__heading">L</h2>
<p className="kodza-az-index__sub">1 term</p>
</div>
<ul className="kodza-az-index__list">
<li className="kodza-az-index__item">
<span className="kodza-az-index__name">Layout shift</span>
<span className="kodza-az-index__note">The page jumps while you read it. We measure it so it does not.</span>
</li>
</ul>
</section>
<section className="kodza-az-index__group">
<div className="kodza-az-index__head">
<h2 className="kodza-az-index__heading">M</h2>
<p className="kodza-az-index__sub">2 terms</p>
</div>
<ul className="kodza-az-index__list">
<li className="kodza-az-index__item">
<span className="kodza-az-index__name">MCP</span>
<span className="kodza-az-index__note">How your AI assistant reads the library instead of guessing it.</span>
</li>
<li className="kodza-az-index__item">
<span className="kodza-az-index__name">Motion scale</span>
<span className="kodza-az-index__note">One variable that speeds up, slows down or stops every animation.</span>
</li>
</ul>
</section>
<section className="kodza-az-index__group">
<div className="kodza-az-index__head">
<h2 className="kodza-az-index__heading">N</h2>
<p className="kodza-az-index__sub">1 term</p>
</div>
<ul className="kodza-az-index__list">
<li className="kodza-az-index__item">
<span className="kodza-az-index__name">npm</span>
<span className="kodza-az-index__note">Where packages live. You will not need it here.</span>
</li>
</ul>
</section>
<section className="kodza-az-index__group">
<div className="kodza-az-index__head">
<h2 className="kodza-az-index__heading">O</h2>
<p className="kodza-az-index__sub">1 term</p>
</div>
<ul className="kodza-az-index__list">
<li className="kodza-az-index__item">
<span className="kodza-az-index__name">overflow: clip</span>
<span className="kodza-az-index__note">What does not fit stays out. And nothing turns into a scroll box.</span>
</li>
</ul>
</section>
<section className="kodza-az-index__group">
<div className="kodza-az-index__head">
<h2 className="kodza-az-index__heading">P</h2>
<p className="kodza-az-index__sub">2 terms</p>
</div>
<ul className="kodza-az-index__list">
<li className="kodza-az-index__item">
<span className="kodza-az-index__name">position: sticky</span>
<span className="kodza-az-index__note">It stays. You scroll. The bar above is one.</span>
</li>
<li className="kodza-az-index__item">
<span className="kodza-az-index__name">prefers-reduced-motion</span>
<span className="kodza-az-index__note">The visitor asked for less movement. Everything arrives at once.</span>
</li>
</ul>
</section>
<section className="kodza-az-index__group">
<div className="kodza-az-index__head">
<h2 className="kodza-az-index__heading">S</h2>
<p className="kodza-az-index__sub">2 terms</p>
</div>
<ul className="kodza-az-index__list">
<li className="kodza-az-index__item">
<span className="kodza-az-index__name">Scroll snap</span>
<span className="kodza-az-index__note">The page lands on a card instead of between two.</span>
</li>
<li className="kodza-az-index__item">
<span className="kodza-az-index__name">Specificity</span>
<span className="kodza-az-index__note">Why your CSS is ignored. Add a class, never !important.</span>
</li>
</ul>
</section>
<section className="kodza-az-index__group">
<div className="kodza-az-index__head">
<h2 className="kodza-az-index__heading">T</h2>
<p className="kodza-az-index__sub">2 terms</p>
</div>
<ul className="kodza-az-index__list">
<li className="kodza-az-index__item">
<span className="kodza-az-index__name">Tabular numbers</span>
<span className="kodza-az-index__note">Digits of equal width, so prices line up in a column.</span>
</li>
<li className="kodza-az-index__item">
<span className="kodza-az-index__name">Theme</span>
<span className="kodza-az-index__note">WordPress's opinion about your page. Usually about margins.</span>
</li>
</ul>
</section>
<section className="kodza-az-index__group">
<div className="kodza-az-index__head">
<h2 className="kodza-az-index__heading">V</h2>
<p className="kodza-az-index__sub">2 terms</p>
</div>
<ul className="kodza-az-index__list">
<li className="kodza-az-index__item">
<span className="kodza-az-index__name">Vanilla</span>
<span className="kodza-az-index__note">HTML, CSS and JavaScript, the way the browser ships them.</span>
</li>
<li className="kodza-az-index__item">
<span className="kodza-az-index__name">View transitions</span>
<span className="kodza-az-index__note">The browser animates from one state to the next. Firefox is on its way.</span>
</li>
</ul>
</section>
<section className="kodza-az-index__group">
<div className="kodza-az-index__head">
<h2 className="kodza-az-index__heading">W</h2>
<p className="kodza-az-index__sub">2 terms</p>
</div>
<ul className="kodza-az-index__list">
<li className="kodza-az-index__item">
<span className="kodza-az-index__name">Web Animations</span>
<span className="kodza-az-index__note">Keyframes from JavaScript. Reversible, and no library to install.</span>
</li>
<li className="kodza-az-index__item">
<span className="kodza-az-index__name">WordPress</span>
<span className="kodza-az-index__note">Runs a huge part of the web. Every theme disagrees on margins.</span>
</li>
</ul>
</section>
</div>
);
}
Checked
Robots poked at it before you did. Every component goes through the same checks, and again each time it changes.