{
  "kit": "2.0.3",
  "generated": "2026-09-07",
  "count": 64,
  "components": [
    {
      "name": "IngotAccentSwatches",
      "display": "AccentSwatches",
      "tag": ".swatches",
      "status": "beta",
      "version": "1.3",
      "tokens": [
        "--accent",
        "--ink",
        "--border-strong"
      ],
      "classNameNote": {
        "cs": "Bere `className`, ale jen na rozvržení — šířku, mezery, umístění v mřížce. Vzhled drží primitivum.",
        "en": "Takes `className`, but for layout only — width, spacing, placement in a grid. The look stays with the primitive."
      },
      "summary": {
        "cs": "Volba akcentové rodiny — pět puntíků, každý obarvený tokenem, který nabízí. Žádný hex v kódu.",
        "en": "The accent-family picker — five dots, each painted by the token it offers. No hex in the code."
      },
      "avoidWhen": {
        "cs": [
          "Vybírá se z pojmenovaných možností, které nejsou barvy. Na to je IngotSegmented nebo IngotSelect.",
          "Má se ukázat, JAK rodina vypadá napříč tokeny. To je ukázka palety v průvodci Základy, ne přepínač."
        ],
        "en": [
          "The options are named things that are not colours. That is IngotSegmented or IngotSelect.",
          "The point is to show HOW a family looks across tokens. That is the palette demo in the Basics guide, not a picker."
        ]
      },
      "a11y": {
        "cs": [
          "Puntíky jsou přepínače v pojmenované skupině, takže se mezi nimi chodí šipkami a odečítač hlásí, který je vybraný.",
          "Terč je 28×28, i když tečka zůstává 18×18 — sáhnout se musí dát prstem. Zvětšit kolečko by rozbilo lištu, proto roste plocha kolem něj.",
          "Vybraný puntík má prstenec v barvě inkoustu, ne v barvě rodiny: prstenec ve vlastní barvě není proti tečce, kterou obkružuje, vidět."
        ],
        "en": [
          "The dots are radios in a named group, so arrow keys move between them and a screen reader announces which is selected.",
          "The target is 28×28 even though the dot stays 18×18 — it has to be reachable with a finger. Growing the circle would break the bar, so the area around it grows instead.",
          "The selected dot has an ink-coloured ring, not a family-coloured one: a ring in its own colour is invisible against the dot it circles."
        ]
      },
      "props": [
        {
          "name": "value",
          "type": "AccentChoice",
          "required": true
        },
        {
          "name": "onChange",
          "type": "(choice: AccentChoice) => void",
          "required": true
        },
        {
          "name": "groupLabel",
          "type": "string",
          "required": true
        },
        {
          "name": "optionLabel",
          "type": "(choice: AccentChoice) => string",
          "required": true
        },
        {
          "name": "disabled",
          "type": "boolean",
          "required": false
        },
        {
          "name": "className",
          "type": "string",
          "required": false
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/accent-swatches",
        "en": "https://ingot.forgmatic.com/en/komponenty/accent-swatches"
      }
    },
    {
      "name": "IngotActionBar",
      "display": "ActionBar",
      "tag": ".actionbar",
      "status": "beta",
      "version": "1.0",
      "tokens": [
        "--surface",
        "--border",
        "--ink-3",
        "--warn",
        "--s-3",
        "--s-4"
      ],
      "classNameNote": {
        "cs": "Bere `className`, ale jen na umístění — kde v obsahu stojí. Ne na vzhled: lišta akcí, která vypadá na každé obrazovce jinak, přestane být tím místem, kde uživatel akce hledá.",
        "en": "Takes `className`, but only for placement — where in the content it stands. Not for the look: an action bar that looks different on every screen stops being the place a user goes to look for actions."
      },
      "summary": {
        "cs": "Lišta pod dlouhým formulářem: to, kvůli čemu čtenář přišel, je v dosahu i po devátém poli. Přilepená, ne plovoucí — nic nezakrývá.",
        "en": "The bar under a long form: what the reader came to do, still in reach after the ninth field. Sticky, not floating — it covers nothing."
      },
      "avoidWhen": {
        "cs": [
          "Krátký formulář, který se vejde na obrazovku. Přilepená lišta pod ním je rám kolem tlačítka, které bylo vidět celou dobu.",
          "Akce nad seznamem — filtry, hromadné operace, „Nový záznam“. To je IngotToolbar a stojí nad daty, ne pod nimi.",
          "Potvrzení v modálním okně. To má vlastní patičku a vlastní pravidla fokusu — IngotModal, IngotConfirm."
        ],
        "en": [
          "A short form that fits on the screen. A sticky bar under it is a frame around a button that was visible the whole time.",
          "Actions above a list — filters, bulk operations, \"New record\". That is IngotToolbar and it stands above the data, not below it.",
          "A confirmation in a dialog. That has its own footer and its own focus rules — IngotModal, IngotConfirm."
        ]
      },
      "a11y": {
        "cs": [
          "Lišta nezakrývá nic, protože je sticky, ne fixed: zůstává v toku dokumentu, takže si na konci obsahu vezme vlastní výšku a při rolování jede po spodní hraně. Plovoucí lišta by potřebovala změřit svou výšku, přidat ji kontejneru jako odsazení a hlídat `resize` — tři věci, z nichž jedna je vždycky špatně na obrazovce, kterou nikdo nezkoušel.",
          "Stav nenese jen barva: tečka je dekorace (aria-hidden) a to, co znamená, stojí vedle ní slovem. Odečítač přečte větu, ne „tečka“.",
          "Lišta si nebere fokus a nemá past. Je to poslední blok obsahu, takže ji klávesnice potká na konci — tam, kde ji potká i oko."
        ],
        "en": [
          "The bar covers nothing because it is sticky, not fixed: it stays in the document's flow, so it takes its own height at the end of the content and rides the bottom edge while there is more to scroll. A floating bar would need its height measured, that height added to the container as padding, and a watch on `resize` — three things, one of which is always wrong on the screen nobody tested.",
          "The state is not carried by colour: the dot is decoration (aria-hidden) and what it means stands beside it in words. A screen reader reads the sentence, not \"dot\".",
          "The bar takes no focus and traps none. It is the last block of the content, so the keyboard meets it at the end — where the eye meets it too."
        ]
      },
      "props": [
        {
          "name": "primary",
          "type": "ReactNode",
          "required": true
        },
        {
          "name": "secondary",
          "type": "ReactNode",
          "required": false
        },
        {
          "name": "status",
          "type": "ReactNode",
          "required": false
        },
        {
          "name": "dirty",
          "type": "boolean",
          "required": false
        },
        {
          "name": "onSave",
          "type": "() => void",
          "required": false
        },
        {
          "name": "className",
          "type": "string",
          "required": false
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/action-bar",
        "en": "https://ingot.forgmatic.com/en/komponenty/action-bar"
      }
    },
    {
      "name": "IngotAppFrame",
      "display": "AppFrame",
      "tag": ".appframe",
      "status": "beta",
      "version": "1.0",
      "tokens": [
        "--bg",
        "--frame",
        "--s-4",
        "--s-5"
      ],
      "classNameNote": {
        "cs": "Bere `className`, ale jen na svislý rytmus — odsazení shora a zdola. Šířku ne: ta je celý smysl tohoto primitiva, a kdyby ji brala zvenčí, vrátí se stav, kvůli kterému vzniklo.",
        "en": "Takes `className`, but only for vertical rhythm — padding above and below. Not width: width is the whole point of this primitive, and taking it from outside would restore the state it was made to end."
      },
      "summary": {
        "cs": "Rám celé obrazovky: lišta nahoře, šířka, kam obsah smí, a okraje vedle něj. Šířka je token, ne číslo v každé aplikaci.",
        "en": "The frame of a whole screen: the bar at the top, the width the content may reach, and the margins beside it. The width is a token, not a number each application repeats."
      },
      "avoidWhen": {
        "cs": [
          "Uvnitř jiného rámu. Dva rámy nad sebou znamenají dvoje okraje a obsah, který začíná jinde, než čtenář čeká podle lišty.",
          "Šířka jednoho bloku uvnitř stránky. Čtecí šířka dlouhého textu je IngotPageLayout s width=\"reading\".",
          "Modální okno nebo boční panel. Ty mají vlastní šířku a stojí nad rámem, ne v něm."
        ],
        "en": [
          "Inside another frame. Two frames stacked mean two sets of margins and content that starts somewhere other than the bar led the reader to expect.",
          "The width of one block inside a page. The reading width of long prose is IngotPageLayout with width=\"reading\".",
          "A modal or a side panel. Those carry their own width and stand above the frame, not inside it."
        ]
      },
      "a11y": {
        "cs": [
          "Rám nekreslí orientační bod. Lišta si svůj nav nese sama a main patří obrazovce — rám neví, který z jeho potomků je hlavní obsah, a hádat by znamenalo dát odečítači špatnou mapu stránky.",
          "Přilepená lišta má z-30: pod překryvy (modál, panel, toast) a nad vším, co kreslí stránka. Lišta, za kterou zmizí otevřený popover, je horší než lišta, která se nelepí vůbec."
        ],
        "en": [
          "The frame draws no landmark. The bar carries its own nav and main belongs to the screen — the frame does not know which of its children is the main content, and guessing would hand a screen reader the wrong map of the page.",
          "The sticky bar sits at z-30: below the overlays (modal, drawer, toast) and above everything a page draws. A bar an open popover disappears behind is worse than one that does not stick at all."
        ]
      },
      "props": [
        {
          "name": "children",
          "type": "ReactNode",
          "required": true
        },
        {
          "name": "width",
          "type": "\"app\" | \"full\"",
          "required": false,
          "values": [
            "app",
            "full"
          ]
        },
        {
          "name": "bar",
          "type": "ReactNode",
          "required": false
        },
        {
          "name": "className",
          "type": "string",
          "required": false
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "extraProps": [
        {
          "name": "INGOT_FRAME_ROW",
          "props": [
            {
              "name": "INGOT_FRAME_ROW",
              "type": "string",
              "required": false
            }
          ]
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/app-frame",
        "en": "https://ingot.forgmatic.com/en/komponenty/app-frame"
      }
    },
    {
      "name": "IngotAttentionPanel",
      "display": "AttentionPanel",
      "tag": ".attention",
      "status": "beta",
      "version": "1.1",
      "tokens": [
        "--ink",
        "--bg",
        "--warn",
        "--r-lg",
        "--shadow-md"
      ],
      "classNameNote": {
        "cs": "`className` nebere. Vypadá stejně na každé obrazovce; rozvržení patří obalu kolem něj.",
        "en": "Does not take `className`. It looks the same on every screen; layout belongs to the wrapper around it."
      },
      "summary": {
        "cs": "Tmavý panel „co po tobě obrazovka chce teď“ v hlavě přehledu. Jediné místo, kde je karta tmavší než pozadí — signál, který drží, jen dokud je vzácný.",
        "en": "The dark “what this screen wants from you now” panel at the head of an overview. The one place where a card is darker than the page — a signal that holds only while it stays rare."
      },
      "avoidWhen": {
        "cs": [
          "Druhý tmavý blok na téže stránce — dva panely nejsou dva signály, ale druhé pozadí. Další sdělení patří do Card nebo metrik.",
          "Chybová nebo výstražná hláška k akci, která právě proběhla — na to je toast a inline text, ne plocha přes půl obrazovky.",
          "Marketingové zvýraznění („novinka!“). Panel znamená práci, která čeká; cokoli jiného ho ředí."
        ],
        "en": [
          "A second dark block on the same page — two panels are not two signals but a second background. Further messages belong in Card or the metrics.",
          "An error or warning for an action that just ran — that is a toast and inline text, not half a screen of surface.",
          "Marketing emphasis (“new!”). The panel means work that waits; anything else dilutes it."
        ]
      },
      "a11y": {
        "cs": [
          "Panel je section pojmenovaná nadpisem — pro odečítač orientační bod, ne jen tmavý div.",
          "Kreslí se tokeny --ink/--bg, takže v tmavém režimu se obrátí a kontrast textu drží v obou motivech sám od sebe.",
          "Naléhavost nese text a počty, ne barva sama — panel je signál polohou a plochou i pro toho, kdo barvy nevidí."
        ],
        "en": [
          "The panel is a section named by its heading — a landmark for a screen reader, not just a dark div.",
          "It is drawn with the --ink/--bg tokens, so it inverts in dark mode and text contrast holds in both themes by itself.",
          "Urgency is carried by text and counts, not by colour alone — the panel signals by position and surface even to someone who does not see colour."
        ]
      },
      "props": [
        {
          "name": "title",
          "type": "string",
          "required": true
        },
        {
          "name": "children",
          "type": "ReactNode",
          "required": true
        },
        {
          "name": "aside",
          "type": "ReactNode",
          "required": false
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/attention-panel",
        "en": "https://ingot.forgmatic.com/en/komponenty/attention-panel"
      }
    },
    {
      "name": "IngotAvatar",
      "display": "Avatar",
      "tag": ".avatar",
      "status": "beta",
      "version": "1.1",
      "tokens": [
        "--ink",
        "--bg",
        "--font-mono"
      ],
      "classNameNote": {
        "cs": "Bere `className`, ale jen na okraje a zarovnání. Velikost je `size` a barva je pevná — kolečko, které má na každé obrazovce jinou velikost, přestane být tím, podle čeho čtenář pozná člověka.",
        "en": "Takes `className`, but only for margins and alignment. The size is `size` and the colour is fixed — a circle that is a different size on every screen stops being the thing a reader recognises a person by."
      },
      "summary": {
        "cs": "Člověk, jak malý člověk bývá: iniciály, nebo jeho obrázek. Iniciály dodává volající.",
        "en": "A person, as small as a person gets: initials, or their picture. The initials come from the caller."
      },
      "avoidWhen": {
        "cs": [
          "Ikona nebo logo. To je IngotIcon — avatar znamená „člověk“ a kolečko s piktogramem to tvrdí o něčem, co člověk není.",
          "Stav nebo počet v kolečku. Stav je IngotBadge, počet IngotCountPill.",
          "Avatar jako tlačítko. Kolečko nic neovládá — obal ho udělá klikatelným, když má být klikatelný, a nese pak vlastní jméno i fokus."
        ],
        "en": [
          "An icon or a logo. That is IngotIcon — an avatar means \"a person\", and a circle with a pictogram claims that about something that is not one.",
          "A state or a count in a circle. A state is IngotBadge, a count is IngotCountPill.",
          "An avatar as a button. The circle operates nothing — a wrapper makes it clickable where it should be, and then carries its own name and its own focus."
        ]
      },
      "a11y": {
        "cs": [
          "Kolečko je jeden prvek se jménem (role=\"img\" + aria-label), ne jméno na obrázku uvnitř nepojmenované krabice. Odečítač pak přečte člověka jednou — ať se obrázek načetl, nebo ne.",
          "Obrázek uvnitř má alt=\"\" a aria-hidden: jméno nese obal a druhé by ho zopakovalo.",
          "Iniciály nejsou barevně odlišené podle člověka. Barva jako jediný nositel identity je přesně to, co WCAG zakazuje — a v kitu, kde si akcent vybírá uživatel, by navíc znamenala pokaždé jinou paletu."
        ],
        "en": [
          "The circle is one named element (role=\"img\" + aria-label), not a name on a picture inside an unnamed box. A screen reader then reads the person once — whether the image loaded or not.",
          "The image inside carries alt=\"\" and aria-hidden: the wrapper holds the name and a second one would repeat it.",
          "The initials are not colour-coded per person. Colour as the sole carrier of identity is exactly what WCAG forbids — and in a kit where the user picks the accent it would mean a different palette every time."
        ]
      },
      "props": [
        {
          "name": "initials",
          "type": "string",
          "required": true
        },
        {
          "name": "label",
          "type": "string",
          "required": true
        },
        {
          "name": "src",
          "type": "string",
          "required": false
        },
        {
          "name": "size",
          "type": "\"sm\" | \"md\"",
          "required": false,
          "values": [
            "sm",
            "md"
          ]
        },
        {
          "name": "decorative",
          "type": "boolean",
          "required": false
        },
        {
          "name": "className",
          "type": "string",
          "required": false
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/avatar",
        "en": "https://ingot.forgmatic.com/en/komponenty/avatar"
      }
    },
    {
      "name": "IngotBadge",
      "display": "Badge",
      "tag": ".badge",
      "status": "stable",
      "version": "1.2",
      "tokens": [
        "--surface-2",
        "--border",
        "--ink",
        "--ink-2",
        "--accent-ink",
        "--accent-bg",
        "--accent-border",
        "--ok",
        "--ok-bg",
        "--ok-border",
        "--warn",
        "--warn-bg",
        "--warn-border",
        "--danger",
        "--danger-bg",
        "--danger-border",
        "--font-mono",
        "--r-xs"
      ],
      "classNameNote": {
        "cs": "`className` nebere schválně: `bg-*` zvenčí by tiše přebilo tón a dvě různá sdělení by se kreslila stejně.",
        "en": "Deliberately does not take `className`: a `bg-*` from outside would quietly override the tone and two different messages would look alike."
      },
      "summary": {
        "cs": "Stavový štítek: stav entity jedním slovem, mono a verzálkami. Pojmenovává stav, ne akci.",
        "en": "A status badge: the state of an entity in one word, mono and upper-case. It names a state, not an action."
      },
      "avoidWhen": {
        "cs": [
          "Je to počet. Odznak s číslem je jiné primitivum s jinou specifikací: kulatý, práh 99+, povinný popisek pro odečítač, nula se nekreslí — a kit ho zatím nemá. Číslo není stav a plést je znamená, že jednou přibude štítek, který svítí pořád.",
          "Má se na to dát kliknout. Filtr, který jde zapnout, je chip a musí být tlačítko s fokusem a klávesovou obsluhou. Štítek je text, ne ovládací prvek — onClick na něm proto nenajdeš.",
          "Na jeden řádek tabulky by jich šlo víc než jeden. Dva štítky vedle sebe se čtou jako jeden složený stav, který nikdo nedefinoval; slož je do jednoho slova, nebo ten druhý dej do sloupce.",
          "Sdělení je věta, ne stav. Verzálky ji udělají nečitelnou a IngotEmptyState nebo obyčejný odstavec ji unesou líp."
        ],
        "en": [
          "It is a count. A badge with a number is a different primitive with a different specification: round, a 99+ threshold, a mandatory screen-reader label, zero is not drawn — and the kit does not have it yet. A number is not a state, and confusing the two ends with a badge that is always lit.",
          "It is meant to be clicked. A filter you can switch on is a chip and has to be a button, with focus and keyboard handling. A badge is text, not a control — which is why it has no onClick.",
          "More than one of them could end up in a single table row. Two badges side by side read as one compound state nobody defined; fold them into one word, or move the second into a column of its own.",
          "The message is a sentence, not a state. Upper case makes it unreadable, and IngotEmptyState or an ordinary paragraph carries it better."
        ]
      },
      "a11y": {
        "cs": [
          "Barva význam nenese nikdy sama. Text je povinný právě proto, že barva je pro část čtenářů neviditelná a v tisku zmizí úplně.",
          "Všech šest tónů drží kontrast textu proti vlastnímu pozadí nad 4,5 : 1 ve světlém i tmavém motivu. Není to slib v próze — měří se to nad skutečnými hodnotami tokenů.",
          "Štítek není interaktivní: nemá roli, fokus ani klávesovou obsluhu. Kdyby ji dostal, musel by být tlačítkem — a to už je chip, ne štítek.",
          "dot je aria-hidden. Tečka, kterou by odečítač přečetl, by hlásila znak navíc, ne stav."
        ],
        "en": [
          "Colour never carries the meaning on its own. The text is required precisely because colour is invisible to some readers and disappears entirely in print.",
          "All six tones keep the text above 4.5 : 1 against their own background in both light and dark. That is not a promise in prose — it is measured against the real token values.",
          "The badge is not interactive: no role, no focus, no keyboard handling. If it had them it would have to be a button — and that is a chip, not a badge.",
          "dot is aria-hidden. A dot a screen reader would read out announces an extra character, not a state."
        ]
      },
      "props": [
        {
          "name": "children",
          "type": "ReactNode",
          "required": true
        },
        {
          "name": "tone",
          "type": "neutral | ok | warn | danger | accent | ink",
          "required": false
        },
        {
          "name": "dot",
          "type": "boolean",
          "required": false
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/badge",
        "en": "https://ingot.forgmatic.com/en/komponenty/badge"
      }
    },
    {
      "name": "IngotBreadcrumbs",
      "display": "Breadcrumbs",
      "tag": ".crumbs",
      "status": "stable",
      "version": "1.2",
      "tokens": [
        "--ink",
        "--ink-3",
        "--ink-4",
        "--font-mono"
      ],
      "classNameNote": {
        "cs": "`className` nebere. Vypadá stejně na každé obrazovce; rozvržení patří obalu kolem něj.",
        "en": "Does not take `className`. It looks the same on every screen; layout belongs to the wrapper around it."
      },
      "summary": {
        "cs": "Drobečky nad hlavičkou stránky — kde jsem a jak zpátky. V aplikaci bez bočního menu nesou celou orientaci do hloubky.",
        "en": "Breadcrumbs above the page header — where I am and how to get back. In an application without a side menu they carry all the orientation in depth."
      },
      "avoidWhen": {
        "cs": [
          "Kořenová stránka sekce. Jediný článek neřekne nic, co by hlavička neřekla líp — komponenta proto pro méně než dva články vrátí null a lišta se nekreslí vůbec.",
          "Navigace mezi sourozenci — záložky nad obsahem, přepínač pohledů. Drobečky vedou nahoru, ne do stran; na kroky vedle je IngotTabs.",
          "Náhrada hlavního menu. Drobečky říkají, kde stojíš, ne kam se dá jít — sekce aplikace patří do IngotTopNav."
        ],
        "en": [
          "The root page of a section. A single crumb says nothing the header does not say better — so for fewer than two crumbs the component returns null and no trail is drawn at all.",
          "Navigating between siblings — tabs above the content, a view switcher. Breadcrumbs lead upwards, not sideways; steps to the side are IngotTabs.",
          "A replacement for the main menu. Breadcrumbs say where you stand, not where you can go — the application's sections belong to IngotTopNav."
        ]
      },
      "a11y": {
        "cs": [
          "Poslední článek není odkaz. Je to místo, kde stojíš, a odkaz sám na sebe je slib prokliku, který nikam nevede. Komponenta ho vykreslí jako text, i kdyby href dostala.",
          "Ten poslední článek nese aria-current=\"page\", takže odečítač aktuální stránku pojmenuje, místo aby ji jen tiše přečetl jako další text v řadě.",
          "Oddělovač je lomítko a je aria-hidden — je to kresba mezi články, ne slovo, které má někdo slyšet dvacetkrát za cestu.",
          "Celek je nav se seznamem uvnitř, takže se dá přeskočit jedním krokem; label je proto povinný, ne doplňkový."
        ],
        "en": [
          "The last crumb is not a link. It is the place where you stand, and a link to itself is a promise of a click-through that leads nowhere. The component renders it as text even if it was given an href.",
          "That last crumb carries aria-current=\"page\", so a screen reader names the current page instead of quietly reading it as one more piece of text in the row.",
          "The separator is a slash and it is aria-hidden — it is a mark between crumbs, not a word anyone should hear twenty times along the path.",
          "The whole is a nav with a list inside, so it can be skipped in one step; that is why label is required, not optional."
        ]
      },
      "props": [
        {
          "name": "items",
          "type": "readonly IngotCrumb[]",
          "required": true
        },
        {
          "name": "label",
          "type": "string",
          "required": true
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "extraProps": [
        {
          "name": "IngotCrumb",
          "props": [
            {
              "name": "label",
              "type": "string",
              "required": true
            },
            {
              "name": "href",
              "type": "string",
              "required": false
            }
          ]
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/breadcrumbs",
        "en": "https://ingot.forgmatic.com/en/komponenty/breadcrumbs"
      }
    },
    {
      "name": "Button",
      "display": "Button",
      "tag": ".btn",
      "status": "stable",
      "version": "1.5",
      "tokens": [
        "--bg",
        "--surface",
        "--surface-2",
        "--border-strong",
        "--ink",
        "--ink-2",
        "--ink-4",
        "--accent",
        "--accent-ink",
        "--ok",
        "--danger",
        "--r-md"
      ],
      "classNameNote": {
        "cs": "Bere `className` (a další atributy tlačítka) na rozvržení — šířku a zarovnání. Vzhled určují `variant` a `size`.",
        "en": "Takes `className` (and the other button attributes) for layout — width and alignment. `variant` and `size` decide the look."
      },
      "summary": {
        "cs": "Tlačítko se sedmi variantami, a s as=\"a\" i odkaz, který tak vypadá. Varianta nese význam akce, ne barvu — a v tmavém režimu drží kontrast, který opsané třídy ztrácejí.",
        "en": "The button, in seven variants — and with as=\"a\" a link that looks like one. A variant carries the meaning of the action, not a colour, and in dark mode it holds a contrast that copied classes lose."
      },
      "avoidWhen": {
        "cs": [
          "Je to odkaz uvnitř věty. Ten se sází jako text, ne jako tlačítko: as=\"a\" dává odkazu tvar tlačítka a ten uprostřed odstavce trhá řádek.",
          "Chceš jinou barvu, než která z variant vychází. Barva tady není volba, ale význam; nová potřeba je nová varianta, ne className."
        ],
        "en": [
          "It is a link inside a sentence. That is set as text, not as a button: as=\"a\" gives a link the shape of a button, and that shape tears a line of prose apart.",
          "You want a colour other than the one the variant gives. Colour here is not a choice but a meaning; a new need is a new variant, not a className."
        ]
      },
      "a11y": {
        "cs": [
          "type je vždycky \"button\", pokud neřekneš jinak. Bez toho tlačítko uvnitř formuláře formulář odešle — chyba, která se projeví až v provozu a jen někdy.",
          "loading nasadí aria-busy a tlačítko zamkne, takže dvojklik nespustí mutaci dvakrát.",
          "Fokus kreslí prstenec 2px v barvě --accent s odsazením 2px. outline: none na tlačítku nikdy — kdo ovládá aplikaci klávesnicí, tím přijde o jedinou informaci, kde se nachází.",
          "iconOnly bez aria-label (nebo aria-labelledby) je chyba — ve vývojovém režimu na ni tlačítko upozorní do konzole. Popisek začíná slovesem: „Smazat řádek“, ne „Koš“.",
          "Klikatelná plocha má být nejméně 32×32 px, na dotykovém zařízení 44×44 px. Velikost sm je 28 px, a patří proto do hustých míst ovládaných myší — typicky do řádku tabulky. Na dotyk sáhni po md a výš.",
          "🚨 Varianty danger, accent a ok mají v tmavém režimu výjimku: tmavá paleta ty tokeny zesvětluje, aby se daly číst jako text, a bílý text na nich klesá pod kontrastní minimum WCAG AA. Popisek se proto invertuje na barvu pozadí stránky. Kdo si tlačítko opíše, tuhle výjimku neopíše — a v tmavém režimu vyrobí nečitelné tlačítko.",
          "as=\"a\" vykreslí opravdový odkaz, ne tlačítko, které naviguje. Odečítač ho hlásí jako odkaz, do pořadí tabulátoru se dostane přes href a Enter ho následuje — nic z toho tlačítko ve tvaru odkazu neumí. Proto je href povinný, a ne nepovinný."
        ],
        "en": [
          "type is always \"button\" unless you say otherwise. Without that, a button inside a form submits the form — a bug that shows up in production and only sometimes.",
          "loading sets aria-busy and locks the button, so a double click cannot fire the mutation twice.",
          "Focus draws a 2px ring in --accent with a 2px offset. Never outline: none on a button — anyone driving the application from the keyboard loses the only clue to where they are.",
          "iconOnly without an aria-label (or aria-labelledby) is a bug — in development mode the button says so in the console. The label starts with a verb: “Delete row”, not “Bin”.",
          "The clickable area should be at least 32×32 px, and 44×44 px on touch. The sm size is 28 px, so it belongs in dense, pointer-driven places — a table row, typically. For touch, reach for md or larger.",
          "🚨 The danger, accent and ok variants carry a dark-mode exception: the dark palette lightens those tokens so they stay readable as text, and white text on them drops below the WCAG AA contrast minimum. The label therefore inverts to the page background colour. Anyone copying the button will not copy that exception — and ships an unreadable button in dark mode.",
          "as=\"a\" renders a real link, not a button that navigates. A screen reader announces it as a link, it enters the tab order through href and Enter follows it — which is exactly what a button shaped like a link cannot do. That is also why href is required rather than optional."
        ]
      },
      "props": [
        {
          "name": "as",
          "type": "\"button\" | \"a\"",
          "required": false,
          "values": [
            "button",
            "a"
          ]
        },
        {
          "name": "href",
          "type": "string",
          "required": false
        },
        {
          "name": "variant",
          "type": "\"primary\" | \"accent\" | \"ok\" | \"secondary\" | \"ghost\" | \"danger\" | \"inverse\"",
          "required": false,
          "values": [
            "primary",
            "accent",
            "ok",
            "secondary",
            "ghost",
            "danger",
            "inverse"
          ]
        },
        {
          "name": "size",
          "type": "\"sm\" | \"md\" | \"lg\"",
          "required": false,
          "values": [
            "sm",
            "md",
            "lg"
          ]
        },
        {
          "name": "iconOnly",
          "type": "boolean",
          "required": false
        },
        {
          "name": "loading",
          "type": "boolean",
          "required": false
        },
        {
          "name": "leadingIcon",
          "type": "ReactNode",
          "required": false
        },
        {
          "name": "trailingIcon",
          "type": "ReactNode",
          "required": false
        },
        {
          "name": "disabled",
          "type": "boolean",
          "required": false
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/button",
        "en": "https://ingot.forgmatic.com/en/komponenty/button"
      }
    },
    {
      "name": "IngotCallout",
      "display": "Callout",
      "tag": ".callout",
      "status": "beta",
      "version": "2.0",
      "tokens": [
        "--accent",
        "--accent-bg",
        "--accent-border",
        "--ok",
        "--ok-bg",
        "--ok-border",
        "--warn",
        "--warn-bg",
        "--warn-border",
        "--danger",
        "--danger-bg",
        "--danger-border",
        "--ink",
        "--ink-2",
        "--r-lg"
      ],
      "classNameNote": {
        "cs": "`className` nebere. Tón určuje plochu, rám i ikonu; kdyby šly přepsat zvenčí, varování by v jedné aplikaci vypadalo třemi způsoby — přesně to, co tohle primitivum ruší.",
        "en": "Does not take `className`. The tone decides the surface, the border and the icon; if they could be overridden from outside, one warning would look three ways in one application — exactly what this primitive exists to end."
      },
      "summary": {
        "cs": "Podbarvený blok s poznámkou, varováním nebo důsledkem u obsahu, ke kterému patří. Tón určuje i to, jestli ho odečítač ohlásí.",
        "en": "A tinted block with a note, a warning or a consequence, next to the content it belongs to. The tone also decides whether a screen reader announces it."
      },
      "avoidWhen": {
        "cs": [
          "Text je nápověda k celé obrazovce a řídí ho volba účtu — to je IngotPageHint.",
          "Blok má otevírat přehled a nést, co čeká na akci — to je IngotAttentionPanel, a na stránce smí být jeden.",
          "Zpráva je výsledek akce, který nemá zastavit práci — to je toast.",
          "Chyba patří ke konkrétnímu poli. Ta patří k němu, ne do bloku nahoře."
        ],
        "en": [
          "The text is help for the whole screen and is driven by an account preference — that is IngotPageHint.",
          "The block should open an overview and carry what awaits action — that is IngotAttentionPanel, and a page may hold one.",
          "The message is the result of an action and must not stop the work — that is a toast.",
          "The error belongs to one field. It belongs at the field, not in a block at the top."
        ]
      },
      "a11y": {
        "cs": [
          "warn a danger nesou role=\"alert\" — odečítač je ohlásí, jakmile se objeví. info a ok se čtou v pořadí, v jakém stojí na stránce.",
          "Blok, který křičí pokaždé, je blok, který se lidé naučí přeskakovat: proto alert jen u dvou tónů ze čtyř.",
          "Barva není jediný signál — každý tón má vlastní ikonu a text říká totéž slovy."
        ],
        "en": [
          "warn and danger carry role=\"alert\" — a screen reader announces them as they appear. info and ok are read in the order they stand on the page.",
          "A block that shouts every time is a block people learn to skip: hence the alert on two tones out of four.",
          "Colour is not the only signal — each tone has its own icon and the text says the same thing in words."
        ]
      },
      "props": [
        {
          "name": "tone",
          "type": "Extract<IngotTone, \"accent\" | \"ok\" | \"warn\" | \"danger\">",
          "required": false
        },
        {
          "name": "title",
          "type": "ReactNode",
          "required": false
        },
        {
          "name": "children",
          "type": "ReactNode",
          "required": true
        },
        {
          "name": "actions",
          "type": "ReactNode",
          "required": false
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/callout",
        "en": "https://ingot.forgmatic.com/en/komponenty/callout"
      }
    },
    {
      "name": "Card",
      "display": "Card",
      "tag": ".card",
      "status": "stable",
      "version": "2.1",
      "tokens": [
        "--bg",
        "--surface",
        "--surface-2",
        "--border",
        "--border-strong",
        "--ink",
        "--r-md",
        "--shadow-sm",
        "--shadow-lg"
      ],
      "classNameNote": {
        "cs": "Bere `className` na rozvržení — mezery a umístění karty. Rám, rádius a stín drží primitivum.",
        "en": "Takes `className` for layout — spacing and placement of the card. The frame, radius and shadow stay with the primitive."
      },
      "summary": {
        "cs": "Plocha, na které obsah stojí — ne rámeček, který se kreslí kolem něj. Umí se zvednout při najetí a jednou za obrazovku se obrátit do tmavé.",
        "en": "The surface content sits on — not a border drawn around it. It can lift on hover, and once per screen it can invert to dark."
      },
      "avoidWhen": {
        "cs": [
          "Karta by obsahovala jedinou další kartu. Vnořené plochy čtenáři neřeknou nic navíc a odsazení se sčítá, dokud obsah nezmizí uprostřed.",
          "Chceš jen odsazení. Na to je className na obalu; karta navíc kreslí okraj a pozadí, které tam nikdo nechtěl."
        ],
        "en": [
          "The card would contain a single other card. Nested surfaces tell the reader nothing extra, and the padding compounds until the content is lost in the middle.",
          "You only want padding. That is a className on a wrapper; a card also draws a border and a background nobody asked for."
        ]
      },
      "a11y": {
        "cs": [
          "Karta je <div>, ne <section>: vizuální plocha sama o sobě není orientační bod a odečítač by hlásil oblasti, které nikam nevedou. Potřebuješ-li osnovu, obal kartu IngotSection.",
          "CardTitle je nadpis vizuálně, ne významově. Když má opravdu být v osnově stránky, patří dovnitř skutečný nadpis přes IngotSection.",
          "Kontrast okraje i pozadí drží tokeny, takže karta funguje ve světlém i tmavém režimu bez zásahu. Uvnitř tone=\"dark\" ale nepiš barvy textu natvrdo — obrácená plocha si barvu předává děděním a text-ink na ní zmizí.",
          "🚨 Klikatelná karta je <a> nebo <button>, nikdy <div> s onClick. Div se nedá zaostřit tabulátorem, neaktivuje se mezerníkem a odečítač ho neohlásí jako ovládací prvek — pro klávesnici ta dlaždice prostě neexistuje.",
          "hover nesmí být jediné, co o klikatelnosti říká. Na dotykovém zařízení najetí neexistuje a při prefers-reduced-motion se zvednutí vypíná — karta proto musí mít viditelný titulek, který napoví, kam vede."
        ],
        "en": [
          "A card is a <div>, not a <section>: a visual surface on its own is not a landmark, and a screen reader would announce regions that lead nowhere. If you need an outline, wrap the card in IngotSection.",
          "CardTitle is a heading visually, not semantically. When it genuinely belongs in the page outline, put a real heading inside via IngotSection.",
          "Border and background contrast come from tokens, so a card works in light and dark alike with no intervention. Inside tone=\"dark\", though, do not hardcode text colours — the inverted surface passes its colour down by inheritance, and text-ink disappears on it.",
          "🚨 A clickable card is an <a> or a <button>, never a <div> with onClick. A div cannot be focused with the keyboard, does not activate on space, and a screen reader does not announce it as a control — for the keyboard that tile simply does not exist.",
          "hover must not be the only thing that says the card is clickable. Touch devices have no hover, and prefers-reduced-motion turns the lift off — so the card always needs a visible title that says where it leads."
        ]
      },
      "props": [
        {
          "name": "elevation",
          "type": "\"flat\" | \"raised\"",
          "required": false,
          "values": [
            "flat",
            "raised"
          ]
        },
        {
          "name": "hover",
          "type": "boolean",
          "required": false
        },
        {
          "name": "tone",
          "type": "Extract<IngotTone, \"neutral\"> | \"dark\"",
          "required": false
        },
        {
          "name": "padded",
          "type": "boolean",
          "required": false
        },
        {
          "name": "className",
          "type": "string",
          "required": false
        }
      ],
      "extraProps": [
        {
          "name": "CardHeader / CardTitle",
          "props": [
            {
              "name": "children",
              "type": "ReactNode",
              "required": true
            },
            {
              "name": "className",
              "type": "string",
              "required": false
            }
          ]
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/card",
        "en": "https://ingot.forgmatic.com/en/komponenty/card"
      }
    },
    {
      "name": "IngotCheckbox",
      "display": "Checkbox",
      "tag": ".check",
      "status": "beta",
      "version": "1.3",
      "tokens": [
        "--accent",
        "--ink-2",
        "--ink-4"
      ],
      "classNameNote": {
        "cs": "Bere `className`, ale jen na rozvržení — šířku, mezery, umístění v mřížce. Vzhled drží primitivum.",
        "en": "Takes `className`, but for layout only — width, spacing, placement in a grid. The look stays with the primitive."
      },
      "summary": {
        "cs": "Zaškrtávátko s popiskem — filtr, souhlas, přepínač chování. Popisek je součást primitiva: klik na text zaškrtává a jméno pro odečítač jede zadarmo.",
        "en": "A checkbox with a label — a filter, a consent, a behaviour toggle. The label is part of the primitive: clicking the text toggles, and the accessible name comes for free."
      },
      "avoidWhen": {
        "cs": [
          "Přepínač s okamžitým účinkem („zapnout modul“). To je switch — slibuje, že se věc stala hned, a bude to vlastní primitivum, až si o něj obrazovka řekne.",
          "Výběr právě jedné z variant. To je IngotSelect nebo IngotOptionCard, ne skupina zaškrtávátek, která se navzájem vypínají.",
          "Hromadný výběr řádků tabulky — ten kreslí IngotTable sama a drží k němu bulk bar."
        ],
        "en": [
          "A toggle with immediate effect (“enable module”). That is a switch — it promises the thing already happened, and it will be its own primitive once a screen asks for it.",
          "Picking exactly one variant. That is IngotSelect or IngotOptionCard, not a group of checkboxes unchecking each other.",
          "Bulk row selection in a table — IngotTable draws that itself and owns the bulk bar."
        ]
      },
      "a11y": {
        "cs": [
          "Popisek je label obalující input: jméno pro odečítač a klikací plocha přes celý text, žádné htmlFor na hlídání.",
          "Zaškrtávátko bez viditelného popisku neexistuje schválně — čtvereček 16 × 16 px beze jména je past pro myš i odečítač."
        ],
        "en": [
          "The label is a label wrapping the input: an accessible name and a click target spanning the whole text, no htmlFor to keep in sync.",
          "A checkbox without a visible label does not exist on purpose — a nameless 16 × 16 px square is a trap for mouse and screen reader alike."
        ]
      },
      "props": [
        {
          "name": "checked",
          "type": "boolean",
          "required": true
        },
        {
          "name": "onChange",
          "type": "(next: boolean) => void",
          "required": true
        },
        {
          "name": "label",
          "type": "ReactNode",
          "required": true
        },
        {
          "name": "disabled",
          "type": "boolean",
          "required": false
        },
        {
          "name": "className",
          "type": "string",
          "required": false
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/checkbox",
        "en": "https://ingot.forgmatic.com/en/komponenty/checkbox"
      }
    },
    {
      "name": "IngotChip",
      "display": "Chip",
      "tag": ".chip",
      "status": "beta",
      "version": "1.2",
      "tokens": [
        "--surface",
        "--surface-2",
        "--border",
        "--border-strong",
        "--ink",
        "--ink-2",
        "--ink-3",
        "--accent",
        "--accent-bg",
        "--r-full"
      ],
      "classNameNote": {
        "cs": "`className` nebere. Chip vypadá stejně na každé obrazovce — právě proto, že si ho dosud kreslila každá zvlášť; rozvržení řady patří obalu, typicky `IngotChipGroup`.",
        "en": "Does not take `className`. A chip looks the same on every screen — which is the whole point, since until now every screen drew its own; laying the row out belongs to the wrapper, typically `IngotChipGroup`."
      },
      "summary": {
        "cs": "Malá pilulka, se kterou uživatel něco dělá: zapnutý filtr, nebo vybraná volba s křížkem. Ovládací prvek, ne štítek.",
        "en": "A small pill the user operates: a filter switched on, or a chosen value with a cross. A control, not a label."
      },
      "avoidWhen": {
        "cs": [
          "Jde o STAV záznamu, se kterým se nedá nic dělat („ve výrobě“, „hotovo“). To je IngotBadge. Kdo je slije dohromady, dostane buď badge, na kterou uživatel marně kliká, nebo chip, který odečítač ohlásí jako text.",
          "Vybírá se právě jedna hodnota z krátké sady. To je IngotSegmented — chipy neřeknou, že se vylučují.",
          "Přepíná se pohled na obsah stránky. To jsou IngotTabs."
        ],
        "en": [
          "It is a record's STATE, with nothing to do to it (“in production”, “done”). That is IngotBadge. Merge the two and you get either a badge users click at in vain, or a chip a screen reader announces as text.",
          "Exactly one value is being picked from a short set. That is IngotSegmented — chips never say that they exclude one another.",
          "A view of the page content is being switched. Those are IngotTabs."
        ]
      },
      "a11y": {
        "cs": [
          "Chip je tlačítko, ne text. Přepínač nese aria-pressed, takže odečítač hlásí „zapnuto“ i „vypnuto“ — ne jen jméno.",
          "Zapnutý chip se nepozná jen barvou: převrátí se figura a pozadí, což drží i v odstínech šedi, a aria-pressed to říká slovy.",
          "removeLabel je povinný a jmenuje konkrétní chip. Osm chipů by jinak nabídlo osm tlačítek pod jedním jménem a odečítač by neřekl, které z nich co odebere.",
          "Oba režimy se vylučují na úrovni typů. Odebratelný přepínač by vložil tlačítko do tlačítka — neplatné HTML, které si každý prohlížeč vyloží po svém.",
          "Řadu chipů pojmenuje IngotChipGroup. Bez ní odečítač potká volná tlačítka a nikdy se nedozví, co filtrují ani kolik jich je."
        ],
        "en": [
          "A chip is a button, not text. The toggle carries aria-pressed, so a screen reader announces “pressed” and “not pressed” — not only the name.",
          "A chip that is on is not told by colour: figure and ground swap, which holds in greyscale, and aria-pressed says it in words.",
          "removeLabel is required and names the particular chip. Eight chips would otherwise offer eight buttons under one name, and a screen reader would not say which of them drops what.",
          "The two modes exclude each other in the types. A removable toggle would put a button inside a button — invalid HTML that every browser resolves its own way.",
          "IngotChipGroup names the row. Without it a screen reader meets loose buttons and never learns what they filter, nor how many there are."
        ]
      },
      "props": [
        {
          "name": "children",
          "type": "ReactNode",
          "required": true
        },
        {
          "name": "pressed",
          "type": "boolean",
          "required": false
        },
        {
          "name": "onToggle",
          "type": "() => void",
          "required": false
        },
        {
          "name": "onRemove",
          "type": "() => void",
          "required": false
        },
        {
          "name": "removeLabel",
          "type": "string",
          "required": false
        },
        {
          "name": "disabled",
          "type": "boolean",
          "required": false
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "extraProps": [
        {
          "name": "IngotChipGroup",
          "props": [
            {
              "name": "label",
              "type": "string",
              "required": true
            },
            {
              "name": "children",
              "type": "ReactNode",
              "required": true
            },
            {
              "name": "testId",
              "type": "string",
              "required": false
            }
          ]
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/chip",
        "en": "https://ingot.forgmatic.com/en/komponenty/chip"
      }
    },
    {
      "name": "IngotCode",
      "display": "Code",
      "tag": ".code",
      "status": "stable",
      "version": "1.1",
      "tokens": [
        "--surface",
        "--border",
        "--font-mono",
        "--r-sm",
        "--code-comment",
        "--code-keyword",
        "--code-string",
        "--code-tag",
        "--code-attr",
        "--code-number",
        "--code-punct"
      ],
      "classNameNote": {
        "cs": "`className` nebere. Výpis i kód ve větě mají jednu sazbu, aby se kód všude četl stejně.",
        "en": "Does not take `className`. A listing and inline code share one typesetting so code reads the same everywhere."
      },
      "summary": {
        "cs": "Kód v textu, nebo výpis přes celou šířku. Výpis se umí posunout do strany — což je ta jediná věc, na které ruční výpisy padají.",
        "en": "Code inside a sentence, or a listing across the full width. The listing scrolls sideways — the one thing hand-rolled listings get wrong."
      },
      "avoidWhen": {
        "cs": [
          "Chceš jen jiné písmo nebo zvýraznění. Na důraz je <strong>; <code> říká „tohle je kód\", a když to kód není, je to lež o významu.",
          "Je to identifikátor z našich vnitřností, který uživatel nikdy neuvidí ani nenapíše — název sloupce, klíč tabulky. Takový text do rozhraní pro zákazníka nepatří vůbec, ani v kódovém písmu."
        ],
        "en": [
          "You only want a different typeface or emphasis. For emphasis there is <strong>; <code> says \"this is code\", and when it is not, that is a lie about meaning.",
          "It is an internal identifier the user will never see or type — a column name, a table key. That text does not belong in a customer-facing interface at all, monospace or otherwise."
        ]
      },
      "a11y": {
        "cs": [
          "block má overflow-x-auto, a je to ta podstatná vlastnost: kód se nezalamuje, takže bez posunu buď přeteče mimo stránku, nebo ho někdo „opraví\" zalomením a rozbije odsazení, podle kterého se v něm čte.",
          "Vykresluje se skutečný <code>, u výpisu uvnitř <pre>. Odečítač podle toho pozná, že text nemá číst jako větu.",
          "Bílé znaky uvnitř <pre> zůstávají — odsazení je u kódu obsah, ne formátování.",
          "Zvýraznění je jen barva navíc, nikdy jediný nositel významu: obarvený výpis má týž text i pořadí jako neobarvený, takže odečítač i kopírování dostanou totéž. Světlý a tmavý motiv mají vlastní sadu barev a každá role drží kontrast 4,5:1 proti ploše výpisu."
        ],
        "en": [
          "block carries overflow-x-auto, and that is the substantive part: code does not wrap, so without scrolling it either overflows the page or someone \"fixes\" it by wrapping and destroys the indentation the code is read by.",
          "It renders a real <code>, inside a <pre> for a listing. That is how a screen reader knows not to read the text as a sentence.",
          "Whitespace inside <pre> is preserved — in code, indentation is content, not formatting.",
          "Highlighting is colour on top, never the only carrier of meaning: a highlighted listing has the same text in the same order as a plain one, so a screen reader and a copy both get the same thing. Light and dark each have their own set of colours, and every role clears 4.5:1 against the listing's surface."
        ]
      },
      "props": [
        {
          "name": "children",
          "type": "ReactNode",
          "required": true
        },
        {
          "name": "block",
          "type": "boolean",
          "required": false
        },
        {
          "name": "lang",
          "type": "\"tsx\"",
          "required": false
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/code",
        "en": "https://ingot.forgmatic.com/en/komponenty/code"
      }
    },
    {
      "name": "IngotColumns",
      "display": "Columns",
      "tag": ".columns",
      "status": "beta",
      "version": "1.1",
      "tokens": [
        "--s-3",
        "--s-4",
        "--s-5"
      ],
      "classNameNote": {
        "cs": "Bere `className`, ale jen na umístění bloku — kde na stránce stojí. Ne na šířku sloupců ani na mezeru mezi nimi: přesně kvůli tomu, že si je každá obrazovka určovala sama, tohle primitivum vzniklo.",
        "en": "Takes `className`, but only for placing the block — where on the page it stands. Not for the width of the columns or the gap between them: that each screen decided those for itself is exactly why this primitive exists."
      },
      "summary": {
        "cs": "Dva nebo tři sloupce uvnitř bloku a jedno pravidlo, kdy přestanou být sloupci. Formulář, detail záznamu, karty vedle sebe.",
        "en": "Two or three columns inside a block, and one rule for when they stop being columns. A form, a record's detail, cards side by side."
      },
      "avoidWhen": {
        "cs": [
          "Rozvržení celé stránky. To je IngotPageLayout (rytmus bloků, postranní rejstřík) uvnitř IngotAppFrame (šířka, okraje). Tohle je vnitřek jednoho bloku.",
          "Dvojice popisek–hodnota v detailu. Ta má vlastní sémantiku (dl) a je to IngotDescriptionList, ne mřížka, do které někdo naskládal spany.",
          "Tabulka. Sloupce, které mají záhlaví a řadí se, jsou IngotTable — mřížka je rozvržení, ne data."
        ],
        "en": [
          "The layout of a whole page. That is IngotPageLayout (the rhythm of blocks, the side index) inside IngotAppFrame (width, margins). This is the inside of one block.",
          "Label–value pairs in a detail. Those carry their own semantics (dl) and are IngotDescriptionList, not a grid somebody stacked spans into.",
          "A table. Columns with headers that sort are IngotTable — a grid is layout, not data."
        ]
      },
      "a11y": {
        "cs": [
          "Mřížka nemění pořadí čtení: potomci jsou v DOM v tom pořadí, ve kterém je volající napsal, a odečítač i klávesnice jdou po nich stejně. Mřížka, která přesouvá prvky vizuálně jinam, než kde jsou v dokumentu, je nejběžnější způsob, jak se pořadí tabulátoru rozejde s tím, co je vidět.",
          "Sloupec sám o sobě není orientační bod ani seznam — je to rozvržení. Význam nesou bloky uvnitř (IngotSection, IngotField), a mřížka jim do něj nesahá."
        ],
        "en": [
          "The grid does not reorder reading: children sit in the DOM in the order the caller wrote them, and a screen reader and the keyboard follow the same one. A grid that moves elements visually away from where they are in the document is the commonest way for tab order to part company with what is on screen.",
          "A column is not a landmark or a list in itself — it is layout. The meaning is carried by the blocks inside (IngotSection, IngotField), and the grid does not reach into it."
        ]
      },
      "props": [
        {
          "name": "children",
          "type": "ReactNode",
          "required": true
        },
        {
          "name": "columns",
          "type": "2 | 3 | 4",
          "required": false
        },
        {
          "name": "gap",
          "type": "3 | 4 | 5",
          "required": false
        },
        {
          "name": "minItemWidth",
          "type": "number",
          "required": false
        },
        {
          "name": "collapseBelow",
          "type": "\"md\" | \"lg\"",
          "required": false,
          "values": [
            "md",
            "lg"
          ]
        },
        {
          "name": "className",
          "type": "string",
          "required": false
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "extraProps": [
        {
          "name": "IngotColumnsFull",
          "props": [
            {
              "name": "children",
              "type": "ReactNode",
              "required": true
            },
            {
              "name": "testId",
              "type": "string",
              "required": false
            }
          ]
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/columns",
        "en": "https://ingot.forgmatic.com/en/komponenty/columns"
      }
    },
    {
      "name": "IngotConfirm",
      "display": "Confirm",
      "tag": ".confirm",
      "status": "stable",
      "version": "1.4",
      "tokens": [
        "--bg",
        "--border",
        "--ink-2",
        "--warn",
        "--r-md"
      ],
      "classNameNote": {
        "cs": "`className` nebere — dialog jede přes `IngotModal` a přebírá jeho geometrii i laťku.",
        "en": "Does not take `className` — the dialog runs through `IngotModal` and inherits its geometry and its bar."
      },
      "summary": {
        "cs": "Potvrzovací dialog nad IngotModal. Spočítaný dopad smí přes useConfirmVeto potvrzení odvolat.",
        "en": "Confirmation dialog built on IngotModal. The computed impact may withdraw the confirmation through useConfirmVeto."
      },
      "avoidWhen": {
        "cs": [
          "Dialog má obsahovat formulář nebo víc než jedno rozhodnutí. Použij IngotModal přímo.",
          "Krok je vratný a levný. Potvrzení, které se odklepává mechanicky, chrání jen zdánlivě — a učí operátora klikat naslepo i tam, kde by neměl."
        ],
        "en": [
          "The dialog should hold a form or more than one decision. Use IngotModal directly.",
          "The step is reversible and cheap. A confirmation that gets clicked through mechanically only looks like protection — and it teaches the operator to click blindly where it does matter."
        ]
      },
      "a11y": {
        "cs": [
          "Celou laťku dědí z IngotModal: focus trap, ESC, aria-modal i návrat fokusu na spouštěč.",
          "Vetovaný krok tlačítko nenabídne vůbec. Zašedlé „Smazat trvale“ vedle důvodu čte operátor jako „ještě chvíli“.",
          "Důvod veta je role=\"alert\", takže ho odečítač ohlásí ve chvíli, kdy se objeví.",
          "Obě tlačítka jedou přes sdílený Button (danger a secondary), ne přes opsané třídy. Varianta danger nese v tmavém režimu výjimku, bez které by bílý text na zesvětleném podkladu klesl pod kontrastní minimum WCAG AA."
        ],
        "en": [
          "It inherits the whole floor from IngotModal: focus trap, ESC, aria-modal and focus returned to the trigger.",
          "A vetoed step does not offer the button at all. A greyed-out \"Delete permanently\" next to the reason reads to the operator as \"not just yet\".",
          "The veto reason is role=\"alert\", so a screen reader announces it the moment it appears.",
          "Both buttons go through the shared Button (danger and secondary), not through copied classes. The danger variant carries a dark-mode exception without which white text on the lightened surface would fall below the WCAG AA contrast minimum."
        ]
      },
      "props": [
        {
          "name": "title",
          "type": "string",
          "required": true
        },
        {
          "name": "description",
          "type": "ReactNode",
          "required": true
        },
        {
          "name": "confirmLabel",
          "type": "string",
          "required": true
        },
        {
          "name": "cancelLabel",
          "type": "string",
          "required": true
        },
        {
          "name": "closeLabel",
          "type": "string",
          "required": true
        },
        {
          "name": "onConfirm",
          "type": "() => void",
          "required": true
        },
        {
          "name": "onClose",
          "type": "() => void",
          "required": true
        },
        {
          "name": "busy",
          "type": "boolean",
          "required": false
        },
        {
          "name": "impact",
          "type": "ReactNode",
          "required": false
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/confirm",
        "en": "https://ingot.forgmatic.com/en/komponenty/confirm"
      }
    },
    {
      "name": "IngotCountPill",
      "display": "CountPill",
      "tag": ".countpill",
      "status": "beta",
      "version": "1.0",
      "tokens": [
        "--surface",
        "--surface-2",
        "--ink-3",
        "--font-mono"
      ],
      "classNameNote": {
        "cs": "`className` nebere. Je to jeden tvar na celý produkt — kdyby brala rozvržení, pilulka u nadpisu a pilulka v tabu se rozejdou, a přesně proto vznikla.",
        "en": "Does not take `className`. It is one shape for the whole product — take layout here and the pill by a heading drifts from the pill in a tab, which is exactly what it was made to stop."
      },
      "summary": {
        "cs": "Kulatá pilulka s číslem: kolik záznamů je za tím, vedle čeho stojí. Nula se kreslí.",
        "en": "A round pill with a number in it: how many records are behind the thing it stands next to. Zero is drawn."
      },
      "avoidWhen": {
        "cs": [
          "Jde o stav, ne o počet („ve výrobě“). To je IngotBadge — verzálkový štítek se letter-spacing, jiný tvar i jiný význam.",
          "Číslo je hlavní údaj obrazovky, ne dovětek. Velká čísla s popiskem jsou IngotMetrics.",
          "Počet se dá zmáčknout a něco tím filtrovat. Pak je to IngotChip: pilulka je text, ne ovládací prvek."
        ],
        "en": [
          "It is a state, not a count (“in production”). That is IngotBadge — an upper-case label with letter-spacing, a different shape and a different meaning.",
          "The number is the screen's main figure, not an afterthought. Large numbers with a caption are IngotMetrics.",
          "The count can be pressed and filters something. Then it is IngotChip: the pill is text, not a control."
        ]
      },
      "a11y": {
        "cs": [
          "label je nepovinný záměrně. Pilulka se skoro vždy čte spolu s textem vedle sebe — v tabu („Položky 12“) nebo za nadpisem („Struktura článku 10“) — a druhé jméno by tam řeklo totéž podruhé.",
          "Pilulka, která stojí sama, label mít musí: „10“ samo o sobě neznamená nic. S ním se čte jako jeden prvek se jménem, bez něj jako text v pořadí čtení.",
          "Nula se kreslí. U odznaku nepřečtených zpráv je nula šum, tady je to odpověď — pohled je prázdný. Skrytá nula by prázdnou sekci nechala vypadat stejně jako sekci, u které počet nikdo nezná."
        ],
        "en": [
          "label is optional on purpose. The pill is nearly always read together with the text beside it — inside a tab (“Items 12”) or after a heading (“Article structure 10”) — and a second name would say the same thing twice.",
          "A pill standing on its own must have a label: “10” by itself means nothing. With one it is read as a single named element, without one as text in reading order.",
          "Zero is drawn. On an unread-message badge a zero is noise; here it is the answer — the view is empty. A hidden zero would leave an empty section looking exactly like one whose count nobody knows."
        ]
      },
      "props": [
        {
          "name": "children",
          "type": "ReactNode",
          "required": true
        },
        {
          "name": "onInk",
          "type": "boolean",
          "required": false
        },
        {
          "name": "label",
          "type": "string",
          "required": false
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/count-pill",
        "en": "https://ingot.forgmatic.com/en/komponenty/count-pill"
      }
    },
    {
      "name": "IngotDescriptionList",
      "display": "DescriptionList",
      "tag": ".dl",
      "status": "beta",
      "version": "1.0",
      "tokens": [
        "--ink",
        "--ink-4",
        "--font-mono",
        "--s-3",
        "--s-5"
      ],
      "classNameNote": {
        "cs": "Bere `className`, ale jen na umístění bloku. Mezery mezi dvojicemi ani sazbu popisku ne — to je rytmus detailu a má být na všech obrazovkách stejný.",
        "en": "Takes `className`, but only for placing the block. Not the gaps between pairs or the setting of the label — that is the rhythm of a detail and should be the same on every screen."
      },
      "summary": {
        "cs": "Fakta o záznamu: popisek, hodnota, popisek, hodnota. Sémantický `dl`, ne mřížka spanů — a fakta jsou mono.",
        "en": "The facts about a record: label, value, label, value. A semantic `dl`, not a grid of spans — and facts are mono."
      },
      "avoidWhen": {
        "cs": [
          "Dá se to editovat. Popisek s hodnotou, do které se píše, je IngotField — a pokud má popisek stát vlevo, má na to labelPlacement=\"side\".",
          "Je toho víc než pár položek a čte se to po řádcích napříč záznamy. To je IngotTable: seznam dvojic není tabulka a tabulka není seznam dvojic.",
          "Velká čísla, na která se má obrazovka dívat. To jsou IngotMetrics — dlaždice s číslem a popiskem, ne řádek faktů."
        ],
        "en": [
          "It can be edited. A label with a value being typed into is IngotField — and if the label is to stand on the left, that is labelPlacement=\"side\".",
          "There is more than a handful and it is read row by row across records. That is IngotTable: a list of pairs is not a table and a table is not a list of pairs.",
          "Large figures the screen is meant to look at. Those are IngotMetrics — tiles with a number and a caption, not a row of facts."
        ]
      },
      "a11y": {
        "cs": [
          "Je to opravdový dl s dt a dd. Právě to říká, že tenhle popisek pojmenovává tuhle hodnotu; mřížka spanů vypadá stejně a odečítači neřekne nic — přečte osm textů za sebou a čtenář si má pořadí domyslet.",
          "Každá dvojice je vlastní řádek mřížky, takže dlouhá hodnota nevytáhne popisek souseda z linky. Vizuální zarovnání je tu součást čitelnosti, ne estetika."
        ],
        "en": [
          "It is a real dl with dt and dd. That is what says this label names this value; a grid of spans looks the same and tells a screen reader nothing — it reads eight texts in a row and leaves the reader to infer the pairing.",
          "Each pair is its own grid row, so a long value cannot drag its neighbour's label out of line. The alignment here is part of readability, not decoration."
        ]
      },
      "props": [
        {
          "name": "items",
          "type": "readonly IngotDescriptionItem[]",
          "required": true
        },
        {
          "name": "columns",
          "type": "1 | 2",
          "required": false
        },
        {
          "name": "className",
          "type": "string",
          "required": false
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "extraProps": [
        {
          "name": "IngotDescriptionItem",
          "props": [
            {
              "name": "label",
              "type": "ReactNode",
              "required": true
            },
            {
              "name": "value",
              "type": "ReactNode",
              "required": true
            },
            {
              "name": "mono",
              "type": "boolean",
              "required": false
            }
          ]
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/description-list",
        "en": "https://ingot.forgmatic.com/en/komponenty/description-list"
      }
    },
    {
      "name": "IngotDisclosure",
      "display": "Disclosure",
      "tag": ".disclosure",
      "status": "beta",
      "version": "1.2",
      "tokens": [
        "--border",
        "--surface-2",
        "--ink-2",
        "--ink-3",
        "--ink-4",
        "--font-mono"
      ],
      "classNameNote": {
        "cs": "Bere `className`, ale jen na rozvržení — šířku, mezery, umístění v mřížce. Vzhled drží primitivum.",
        "en": "Takes `className`, but for layout only — width, spacing, placement in a grid. The look stays with the primitive."
      },
      "summary": {
        "cs": "Sbalitelná sekce postranního panelu — popisek, počet a obsah, který se schová. Stav drží prohlížeč, ne React.",
        "en": "A collapsible section in a side panel — a label, a count and a body that hides. The browser holds the state, not React."
      },
      "avoidWhen": {
        "cs": [
          "Je to sekce obrazovky s nadpisem. Na to je IngotSection: sází <h2>/<h3> a drží osnovu stránky. Popisek téhle sekce nadpis NENÍ a nesmí se za něj vydávat.",
          "Obsah je důležitý a sbalením se schová. Sbalená sekce znamená „tohle nepotřebuješ vidět hned“ — u varování nebo chyby je to špatná zpráva o obsahu.",
          "Přepínají se pohledy na totéž místo. To jsou záložky IngotTabs: záložky mají vždy jeden vybraný pohled, sbalitelné sekce můžou být otevřené všechny nebo žádná."
        ],
        "en": [
          "It is a screen section with a heading. That is IngotSection: it sets an <h2>/<h3> and holds the page outline. This section's label is NOT a heading and must not pass for one.",
          "The content matters and collapsing hides it. A collapsed section says “you do not need to see this yet” — for a warning or an error that is the wrong thing to say.",
          "Views of the same place are being switched. Those are tabs, IngotTabs: tabs always have exactly one view selected, collapsible sections can be all open or all closed."
        ]
      },
      "a11y": {
        "cs": [
          "Sekce stojí na <details> a <summary>. Odečítač sám hlásí „sbaleno“ a „rozbaleno“ a Enter i mezerník přepínají bez naší pomoci — žádné aria-expanded, které by se dalo zapomenout přepnout.",
          "Popisek NENÍ nadpis. Sekce se schválně nezapisuje do osnovy stránky — nadpis, který nic nestrukturuje, o osnově lže, a osnova je jedna z mála věcí, které se čtou naslepo.",
          "Chevron je dekorace. Stav nese sám prvek, ne ikona, takže šipka odečítači nic neříká a otáčí se jen CSS.",
          "Sbalený obsah není zahozený: hledání na stránce ho v moderních prohlížečích najde a sekci samo rozbalí, a tisk stránky ho nevynechá. Vlastní stav v Reactu by obojí ztratil."
        ],
        "en": [
          "The section is built on <details> and <summary>. A screen reader announces “collapsed” and “expanded” on its own and Enter or Space toggles it without our help — no aria-expanded anyone could forget to flip.",
          "The label is NOT a heading. The section deliberately stays out of the page outline — a heading that structures nothing lies about the outline, and the outline is one of the few things read blind.",
          "The chevron is decoration. The element itself carries the state, not the icon, so the arrow says nothing to a screen reader and turns in CSS alone.",
          "Collapsed content is not thrown away: find-on-page reaches it in modern browsers and expands the section itself, and printing does not skip it. React-held state would lose both."
        ]
      },
      "props": [
        {
          "name": "title",
          "type": "ReactNode",
          "required": true
        },
        {
          "name": "count",
          "type": "number",
          "required": false
        },
        {
          "name": "defaultOpen",
          "type": "boolean",
          "required": false
        },
        {
          "name": "children",
          "type": "ReactNode",
          "required": true
        },
        {
          "name": "className",
          "type": "string",
          "required": false
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "extraProps": [
        {
          "name": "IngotDisclosureGroup",
          "props": [
            {
              "name": "children",
              "type": "ReactNode",
              "required": true
            },
            {
              "name": "testId",
              "type": "string",
              "required": false
            }
          ]
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/disclosure",
        "en": "https://ingot.forgmatic.com/en/komponenty/disclosure"
      }
    },
    {
      "name": "IngotDrawer",
      "display": "Drawer",
      "tag": ".drawer",
      "status": "beta",
      "version": "1.7",
      "tokens": [
        "--surface",
        "--surface-2",
        "--border",
        "--ink",
        "--ink-3",
        "--accent-bg",
        "--r-sm",
        "--shadow-lg"
      ],
      "classNameNote": {
        "cs": "`className` nebere. Šířku řídí `width` s tvrdým stropem ze specifikace, hranu `side`.",
        "en": "Does not take `className`. `width` drives the width with the spec's hard cap, `side` the edge."
      },
      "summary": {
        "cs": "Boční panel pro editaci se stejnou a11y laťkou jako dialog: focus trap, ESC, scroll lock a návrat fokusu na spouštěč.",
        "en": "A side panel for editing with the same accessibility floor as the dialog: focus trap, ESC, scroll lock and focus returned to the trigger."
      },
      "avoidWhen": {
        "cs": [
          "Obsah je delší než zhruba dvě obrazovky. Pak je to samostatná stránka, ne boční panel.",
          "Jde o rozhodnutí, které musí padnout hned („potvrď / zruš“) — na to je IngotModal, případně IngotConfirm.",
          "Nad otevřeným drawerem nikdy neotvírej další překryv. Jeden překryv v jednu chvíli; výsledek akce ohlas toastem."
        ],
        "en": [
          "The content runs longer than roughly two screens. Then it is a page of its own, not a side panel.",
          "The user must decide right now (\"confirm / cancel\") — that is IngotModal, or IngotConfirm.",
          "Never open another overlay on top of an open drawer. One overlay at a time; announce the result of an action with a toast."
        ]
      },
      "a11y": {
        "cs": [
          "Stejná laťka jako u dialogu: role=\"dialog\", aria-modal=\"true\" a aria-labelledby mířící na titulek.",
          "Focus trap na Tab i Shift+Tab, ESC zavírá vždy (i při dismissable={false}) a po zavření se fokus vrací na spouštěč.",
          "Scroll lock pozadí sdílí čítač s dialogem: drawer otevřený nad dialogem (nebo obráceně) ho při zavření neuvolní předčasně.",
          "Patka s akcemi je vždy viditelná — „Uložit“ se nikdy neschová pod scroll těla."
        ],
        "en": [
          "The same floor as the dialog: role=\"dialog\", aria-modal=\"true\" and aria-labelledby pointing at the title.",
          "A focus trap on both Tab and Shift+Tab, ESC always closes (even with dismissable={false}) and on close the focus returns to the trigger.",
          "The background scroll lock shares its counter with the dialog: a drawer open above a dialog (or the other way round) does not release it early on close.",
          "The action footer is always visible — \"Save\" never hides below the body's scroll."
        ]
      },
      "props": [
        {
          "name": "title",
          "type": "ReactNode",
          "required": true
        },
        {
          "name": "onClose",
          "type": "() => void",
          "required": true
        },
        {
          "name": "children",
          "type": "ReactNode",
          "required": true
        },
        {
          "name": "closeLabel",
          "type": "string",
          "required": true
        },
        {
          "name": "subtitle",
          "type": "ReactNode",
          "required": false
        },
        {
          "name": "footer",
          "type": "ReactNode",
          "required": false
        },
        {
          "name": "side",
          "type": "\"right\" | \"left\"",
          "required": false,
          "values": [
            "right",
            "left"
          ]
        },
        {
          "name": "width",
          "type": "number",
          "required": false
        },
        {
          "name": "dismissable",
          "type": "boolean",
          "required": false
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/drawer",
        "en": "https://ingot.forgmatic.com/en/komponenty/drawer"
      }
    },
    {
      "name": "IngotEmptyState",
      "display": "EmptyState",
      "tag": ".empty",
      "status": "stable",
      "version": "1.0",
      "tokens": [
        "--ink-2",
        "--ink-3"
      ],
      "classNameNote": {
        "cs": "`className` nebere. Vypadá stejně na každé obrazovce; rozvržení patří obalu kolem něj.",
        "en": "Does not take `className`. It looks the same on every screen; layout belongs to the wrapper around it."
      },
      "summary": {
        "cs": "Prázdný stav: jedna věta, co tu není, volitelně proč a volitelně afordance, jak to změnit.",
        "en": "Empty state: one sentence saying what is missing, optionally why, and optionally the affordance that changes it."
      },
      "avoidWhen": {
        "cs": [
          "Data se ještě načítají. Na to má IngotTable vlastní loading s aria-busy; prázdný stav by tvrdil „nic tu není“, zatímco odpověď je na cestě.",
          "Dotaz selhal. Chyba a prázdno jsou dvě různá sdělení a čtenář podle nich dělá dvě různé věci — u chyby chce vědět, co se stalo, ne že tu nic není.",
          "Prázdno je důsledek filtru. Věta pak má mluvit o filtru („nic neodpovídá výběru“), ne o tom, že je zdroj prázdný."
        ],
        "en": [
          "The data is still loading. IngotTable has its own loading with aria-busy for that; an empty state would claim \"there is nothing here\" while the answer is still on its way.",
          "The request failed. An error and an empty result are two different messages and the reader acts on them differently — on an error they want to know what happened, not that there is nothing.",
          "The emptiness is the result of a filter. Then the sentence should talk about the filter (\"nothing matches your selection\"), not about the source being empty."
        ]
      },
      "a11y": {
        "cs": [
          "Je to obyčejný text uvnitř toku stránky, ne role=\"status\". Prázdno není událost, kterou by měl odečítač hlásit — čte se v pořadí, v jakém na něj přijde.",
          "Uvnitř IngotTable sedí v buňce, jejíž colSpan se počítá ze sloupců. Ruční tabulky mají tohle číslo natvrdo a přidání sloupce jim ho tiše rozejde.",
          "action je slot, ne popisek: dostane celou afordanci i s jejím vlastním fokusem a popiskem."
        ],
        "en": [
          "It is ordinary text in the page flow, not role=\"status\". Emptiness is not an event a screen reader should announce — it is read in the order it is reached.",
          "Inside IngotTable it sits in a cell whose colSpan is computed from the columns. Hand-rolled tables hard-code that number, and adding a column breaks it silently.",
          "action is a slot, not a label: it receives the whole affordance together with its own focus handling and label."
        ]
      },
      "props": [
        {
          "name": "title",
          "type": "ReactNode",
          "required": true
        },
        {
          "name": "description",
          "type": "ReactNode",
          "required": false
        },
        {
          "name": "action",
          "type": "ReactNode",
          "required": false
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/empty-state",
        "en": "https://ingot.forgmatic.com/en/komponenty/empty-state"
      }
    },
    {
      "name": "IngotEyebrow",
      "display": "Eyebrow",
      "tag": ".eyebrow",
      "status": "beta",
      "version": "1.2",
      "tokens": [
        "--font-mono",
        "--ink-3",
        "--ink-4",
        "--accent-ink",
        "--ok"
      ],
      "classNameNote": {
        "cs": "Bere `className` na rozvržení — odsazení a flex. Barvu a písmo určují `size` a `tone`.",
        "en": "Takes `className` for layout — margins and flex. `size` and `tone` decide colour and type."
      },
      "summary": {
        "cs": "Malý mono popisek verzálkami nad věcí, kterou pojmenovává — skupina v menu, metrika, krok. Jeden zápis pro idiom, který se dřív kreslil deseti způsoby.",
        "en": "The small uppercase mono caption above the thing it names — a nav group, a metric, a step. One drawing for an idiom that used to be hand-drawn ten ways."
      },
      "avoidWhen": {
        "cs": [
          "Text pojmenovává STAV („ve výrobě“, „hotovo“) — to je IngotBadge: má rámeček, tón a pravidla pro kontrast, které holý popisek nemá.",
          "Text je nadpis, který má stát v osnově stránky — to je IngotSection. Eyebrow žádnou roli nadpisu nenese schválně.",
          "Popisek pole formuláře. Ten je label a patří k IngotField, aby ho odečítač spojil se vstupem."
        ],
        "en": [
          "The text names a STATE (“in production”, “done”) — that is IngotBadge: it has a border, a tone and contrast rules a bare caption does not.",
          "The text is a heading that belongs in the page outline — that is IngotSection. An eyebrow deliberately carries no heading role.",
          "A form field's label. That is a label and belongs to IngotField, so a screen reader ties it to the input."
        ]
      },
      "a11y": {
        "cs": [
          "Není to nadpis: žádná role, žádná úroveň. Odečítač ho čte jako text před hodnotou, ke které patří — proto stojí v DOM přímo před ní.",
          "Verzálky dělá text-transform, ne přepsaný řetězec, takže odečítač čte slova, ne písmena."
        ],
        "en": [
          "Not a heading: no role, no level. A screen reader reads it as the text before the value it belongs to — which is why it sits directly before it in the DOM.",
          "Uppercase comes from text-transform, not a rewritten string, so a screen reader reads words, not letters."
        ]
      },
      "props": [
        {
          "name": "children",
          "type": "ReactNode",
          "required": true
        },
        {
          "name": "as",
          "type": "\"p\" | \"span\" | \"div\"",
          "required": false,
          "values": [
            "p",
            "span",
            "div"
          ]
        },
        {
          "name": "size",
          "type": "\"sm\" | \"md\"",
          "required": false,
          "values": [
            "sm",
            "md"
          ]
        },
        {
          "name": "tone",
          "type": "\"neutral\" | \"muted\" | \"accent\" | \"ok\" | \"inherit\"",
          "required": false,
          "values": [
            "neutral",
            "muted",
            "accent",
            "ok",
            "inherit"
          ]
        },
        {
          "name": "className",
          "type": "string",
          "required": false
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/eyebrow",
        "en": "https://ingot.forgmatic.com/en/komponenty/eyebrow"
      }
    },
    {
      "name": "IngotField",
      "display": "Field",
      "tag": ".field",
      "status": "stable",
      "version": "1.6",
      "tokens": [
        "--surface",
        "--surface-2",
        "--border-strong",
        "--ink",
        "--ink-2",
        "--ink-3",
        "--ink-4",
        "--accent",
        "--accent-bg",
        "--danger",
        "--font-mono",
        "--r-md",
        "--shadow-sm"
      ],
      "classNameNote": {
        "cs": "`className` nebere. Vypadá stejně na každé obrazovce; rozvržení patří obalu kolem něj.",
        "en": "Does not take `className`. It looks the same on every screen; layout belongs to the wrapper around it."
      },
      "summary": {
        "cs": "Popsané textové pole: popisek, nápověda, chyba, jednotka. Pro ruční formuláře, které pole nemají odkud odvodit.",
        "en": "A labelled text field: label, hint, error, unit. For hand-written forms whose fields cannot be derived from anything."
      },
      "avoidWhen": {
        "cs": [
          "Pole vznikají ze schématu (konfigurace integrace, per-uzlové nastavení operace). Tam patří IngotForm nad IngotFieldSpec[]: pole, která nikdo neopsal rukou, se nemůžou se schématem rozejít. Tahle komponenta je opačná větev — rukou psaný formulář, kde žádné schéma není.",
          "Potřebuješ jen samotný vstup bez popisku, protože si popisek stránka skládá sama. To je IngotFieldInput.",
          "Hodnota se jen ukazuje a nejde měnit. Zamčený vstup tvrdí „tohle je pole“ a uživatel do něj bude klikat; použij popis a hodnotu (<dl> / <dt> / <dd>), ne disabled. To je pro pole, které se ODEMKNE — dočasně, ne navždy.",
          "Vstup není text: zaškrtávátko, výběr z množiny, datum. První verze je schválně jen textová — primitivum bez konzumenta je nezapojený slib."
        ],
        "en": [
          "The fields come from a schema (integration config, per-node operation settings). That is IngotForm over IngotFieldSpec[]: fields nobody typed out by hand cannot drift from the schema. This component is the opposite branch — a hand-written form where there is no schema.",
          "You only need the input itself without a label, because the page composes the label on its own. That is IngotFieldInput.",
          "The value is only displayed and cannot be changed. A locked input claims \"this is a field\" and users will keep clicking it; use a term and its value (<dl> / <dt> / <dd>), not disabled. That one is for a field that will UNLOCK — temporarily, not forever.",
          "The input is not text: a checkbox, a choice from a set, a date. The first version is deliberately text-only — a primitive without a consumer is an unwired promise."
        ]
      },
      "a11y": {
        "cs": [
          "label for ↔ input id drží komponenta přes useId. Vazba proto nemůže vzniknout špatně ani u dvou stejných polí na jedné stránce a volající žádné id nevymýšlí.",
          "Placeholder popisek NENÍ. Zmizí, jakmile uživatel začne psát, a odečítač ho nečte jako jméno pole.",
          "Chyba se hlásí textem a aria-invalid, ne jen červenou barvou. hint, affix i error jsou navázané přes aria-describedby v pořadí, v jakém se čtou.",
          "Fokus je vidět na celém rámečku (focus-within): border accent a ring 3px accent-bg. Kdyby seděl jen na <input>, přípona by z něj vypadla.",
          "Počet znaků je ozdoba pro oko a odečítač ho nečte: číslo, které se mění při každém stisku, by mluvilo přes psaní. Limit patří do hint jako věta — ta se nehýbe a jde přes aria-describedby. Pole s counterMax bez takové věty limit neřekne nikomu, kdo na něj nevidí."
        ],
        "en": [
          "label for ↔ input id is held by the component via useId. The binding therefore cannot be made wrong, not even with two identical fields on one page, and the caller invents no id.",
          "The placeholder is NOT a label. It disappears as soon as the user starts typing, and screen readers do not read it as the field's name.",
          "An error is announced by text and aria-invalid, not by red alone. hint, affix and error are all wired through aria-describedby in reading order.",
          "Focus is visible on the whole frame (focus-within): an accent border and a 3px accent-bg ring. On the <input> alone the affix would fall outside it.",
          "The character count is decoration for the eye and a screen reader does not read it: a number that changes on every keystroke would talk over the typing. The limit belongs in hint as a sentence — that one does not move and goes through aria-describedby. A field with counterMax and no such sentence states its limit to nobody who cannot see it."
        ]
      },
      "props": [
        {
          "name": "type",
          "type": "\"text\" | \"number\" | \"password\" | \"email\" | \"url\" | \"tel\" | \"textarea\"",
          "required": false,
          "values": [
            "text",
            "number",
            "password",
            "email",
            "url",
            "tel",
            "textarea"
          ]
        },
        {
          "name": "rows",
          "type": "number",
          "required": false
        },
        {
          "name": "counterMax",
          "type": "number",
          "required": false
        },
        {
          "name": "label",
          "type": "ReactNode",
          "required": true
        },
        {
          "name": "value",
          "type": "string",
          "required": true
        },
        {
          "name": "onChange",
          "type": "(next: string) => void",
          "required": true
        },
        {
          "name": "hint",
          "type": "ReactNode",
          "required": false
        },
        {
          "name": "error",
          "type": "ReactNode",
          "required": false
        },
        {
          "name": "affix",
          "type": "ReactNode",
          "required": false
        },
        {
          "name": "mono",
          "type": "boolean",
          "required": false
        },
        {
          "name": "optionalLabel",
          "type": "ReactNode",
          "required": false
        },
        {
          "name": "placeholder",
          "type": "string",
          "required": false
        },
        {
          "name": "required",
          "type": "boolean",
          "required": false
        },
        {
          "name": "disabled",
          "type": "boolean",
          "required": false
        },
        {
          "name": "labelPlacement",
          "type": "\"stacked\" | \"side\"",
          "required": false,
          "values": [
            "stacked",
            "side"
          ]
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/field",
        "en": "https://ingot.forgmatic.com/en/komponenty/field"
      }
    },
    {
      "name": "IngotFieldInput",
      "display": "FieldInput",
      "tag": ".input",
      "status": "stable",
      "version": "2.3",
      "tokens": [
        "--surface",
        "--surface-2",
        "--border-strong",
        "--ink",
        "--ink-4",
        "--accent",
        "--accent-bg",
        "--r-md",
        "--shadow-sm"
      ],
      "classNameNote": {
        "cs": "Bere `className`, ale jen na rozvržení — šířku, mezery, umístění v mřížce. Vzhled drží primitivum.",
        "en": "Takes `className`, but for layout only — width, spacing, placement in a grid. The look stays with the primitive."
      },
      "summary": {
        "cs": "Jedno pole podle svého kind. Nezná žádnou doménu — množinu voleb i překlady dodává volající.",
        "en": "A single field driven by its kind. It knows no domain — the option set and the translations both come from the caller."
      },
      "avoidWhen": {
        "cs": [
          "Stavíš obyčejný svislý formulář z více polí. IngotForm k tomu přidá popisky i nápovědy a ušetří ti je opisovat.",
          "Chceš pole, které Ingot nezná (datum, soubor, barva). IngotFieldSpec má pevný výčet kind; nový se přidává k němu, ne obcházením primitiva.",
          "Formulář se píše rukou a pole nemají odkud vzniknout — jsou tři a jsou dané. Tam patří IngotField, které si popisek, nápovědu i chybu nese s sebou. Tohle primitivum je schválně bez popisku, protože ho nad ním skládá IngotForm."
        ],
        "en": [
          "You are building an ordinary vertical form out of several fields. IngotForm adds the labels and hints and saves you copying them.",
          "You want a field the Ingot does not know (date, file, colour). IngotFieldSpec has a closed set of kind values; a new one is added there, not by working around the primitive.",
          "The form is written by hand and the fields have nowhere to come from — there are three of them and they are fixed. That is IngotField, which carries its own label, hint and error. This primitive is deliberately label-less, because IngotForm composes the label above it."
        ]
      },
      "a11y": {
        "cs": [
          "🚨 Samotné pole <label> nevykresluje. Popisek i jeho vazbu (htmlFor + id, nebo obalující <label>) dodává volající — jak to dělá ukázka výše.",
          "kind rozhoduje o typu vstupu: boolean → checkbox, secret → password, integer a number → number se step 1, resp. any.",
          "testId je povinný. Bez popisku uvnitř primitiva je data-testid jediný stabilní úchyt, který E2E má."
        ],
        "en": [
          "🚨 The field itself does not render a <label>. The label and its association (htmlFor + id, or a wrapping <label>) come from the caller — as the demo above does.",
          "kind decides the input type: boolean → checkbox, secret → password, integer and number → number with step 1 and any respectively.",
          "testId is required. With no label inside the primitive, data-testid is the only stable handle the end-to-end tests have."
        ]
      },
      "props": [
        {
          "name": "field",
          "type": "IngotFieldSpec",
          "required": true
        },
        {
          "name": "value",
          "type": "unknown",
          "required": true
        },
        {
          "name": "onChange",
          "type": "(next: unknown) => void",
          "required": true
        },
        {
          "name": "testId",
          "type": "string",
          "required": true
        },
        {
          "name": "disabled",
          "type": "boolean",
          "required": false
        },
        {
          "name": "className",
          "type": "string",
          "required": false
        },
        {
          "name": "renderOptions",
          "type": "(args) => ReactNode",
          "required": false
        },
        {
          "name": "secretPlaceholder",
          "type": "(field: IngotFieldSpec) => string",
          "required": false
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/field-input",
        "en": "https://ingot.forgmatic.com/en/komponenty/field-input"
      }
    },
    {
      "name": "IngotForm",
      "display": "Form",
      "tag": ".form",
      "status": "stable",
      "version": "2.2",
      "tokens": [
        "--ink-2",
        "--ink-3",
        "--ink-4",
        "--accent"
      ],
      "classNameNote": {
        "cs": "Bere `className`, ale jen na rozvržení — šířku, mezery, umístění v mřížce. Vzhled drží primitivum.",
        "en": "Takes `className`, but for layout only — width, spacing, placement in a grid. The look stays with the primitive."
      },
      "summary": {
        "cs": "Deklarativní formulář: dostane pole a hodnoty, vrací změny přes onChange. Tvar formuláře jsou data, ne JSX.",
        "en": "Declarative form: it takes fields and values and reports changes through onChange. The shape of the form is data, not JSX."
      },
      "avoidWhen": {
        "cs": [
          "Rozvržení není svislý seznam — dvousloupcové sekce, pole vedle sebe, taby. Formulář umí jen space-y; poskládej si IngotFieldInput sám.",
          "Potřebuješ validaci a chybové hlášky u jednotlivých polí. První verze je nemá — hlášku vykresluje volající nad formulářem. Rukou psaný formulář, který chybu u pole potřebuje, si ho složí z IngotField.",
          "Je to jediné pole. Pak je IngotFieldInput přímo levnější a nemusíš vymýšlet testIdPrefix."
        ],
        "en": [
          "The layout is not a vertical list — two-column sections, fields side by side, tabs. The form only does space-y; compose IngotFieldInput yourself.",
          "You need per-field validation and error messages. The first version has none — the caller renders the message above the form. A hand-written form that needs a per-field error composes itself out of IngotField.",
          "It is a single field. Then IngotFieldInput is cheaper outright and you do not have to invent a testIdPrefix."
        ]
      },
      "a11y": {
        "cs": [
          "Popisek je <label> obalující vstup, takže vazba drží bez htmlFor a id a klik na text zaostří pole.",
          "Zaškrtávátko (kind: \"boolean\") má popisek vpravo od sebe. Je to jediná odchylka a drží ji formulář, aby ji konzumenti neopisovali.",
          "field.description se vypisuje pod vstupem uvnitř téhož <label>, takže se čte spolu s popiskem.",
          "testIdPrefix je povinný: data-testid vstupu se skládá z něj a z field.key, a E2E na tom visí."
        ],
        "en": [
          "The label is a <label> wrapping the input, so the association holds without htmlFor and id, and clicking the text focuses the field.",
          "A checkbox (kind: \"boolean\") carries its label to its right. It is the only deviation, and the form owns it so consumers do not copy it.",
          "field.description is rendered below the input inside the same <label>, so it is read together with the label.",
          "testIdPrefix is required: an input's data-testid is built from it and field.key, and the end-to-end tests hang off that."
        ]
      },
      "props": [
        {
          "name": "fields",
          "type": "readonly IngotFieldSpec[]",
          "required": true
        },
        {
          "name": "values",
          "type": "Record<string, unknown>",
          "required": true
        },
        {
          "name": "onChange",
          "type": "(key: string, next: unknown) => void",
          "required": true
        },
        {
          "name": "testIdPrefix",
          "type": "string",
          "required": true
        },
        {
          "name": "renderOptions",
          "type": "IngotFieldInput['renderOptions']",
          "required": false
        },
        {
          "name": "secretPlaceholder",
          "type": "(field: IngotFieldSpec) => string",
          "required": false
        },
        {
          "name": "className",
          "type": "string",
          "required": false
        },
        {
          "name": "inputClassName",
          "type": "string",
          "required": false
        },
        {
          "name": "labelClassName",
          "type": "string",
          "required": false
        }
      ],
      "extraProps": [
        {
          "name": "useIngotForm(fields, initial, resetKey)",
          "props": [
            {
              "name": "fields",
              "type": "readonly IngotFieldSpec[]",
              "required": true
            },
            {
              "name": "initial",
              "type": "Record<string, unknown> | null",
              "required": true
            },
            {
              "name": "resetKey",
              "type": "string | number",
              "required": false
            }
          ]
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/form",
        "en": "https://ingot.forgmatic.com/en/komponenty/form"
      }
    },
    {
      "name": "IngotIcon",
      "display": "Icon",
      "tag": "[data-icon]",
      "status": "stable",
      "version": "1.4",
      "tokens": [
        "currentColor"
      ],
      "classNameNote": {
        "cs": "Bere `className` na umístění a barvu — glyf kreslí `currentColor`, velikost je `size`.",
        "en": "Takes `className` for placement and colour — the glyph draws in `currentColor`, the size is `size`."
      },
      "summary": {
        "cs": "Ikony rozhraní — jedna sada, jedna technika. Kreslí se čárou v mřížce 24×24, barví se textem rodiče a škáluje se jedním číslem.",
        "en": "Interface icons — one set, one technique. Line art on a 24×24 grid, inked by the parent's text colour, scaled by a single number."
      },
      "avoidWhen": {
        "cs": [
          "Je to ikona výrobní operace. Ta má vlastní sadu i vlastní pravidla — IngotOpIcon. Klíč operace ukládá backend a ikona bez názvu je hádanka.",
          "Ikona má nést význam, který nikde není napsaný. Symbol sám o sobě se nedá spolehlivě uhodnout; napiš popisek a ikonu nech vedle něj jako dekoraci.",
          "Potřebuješ obrázek, ne symbol — logo, ilustraci, fotku. Sada je jednobarevná čára, ne obrazová knihovna."
        ],
        "en": [
          "It is a manufacturing operation icon. That set has its own rules — IngotOpIcon. The key is stored by the backend and the icon alone is a riddle.",
          "The icon would have to carry meaning that is written nowhere else. A symbol on its own cannot be guessed reliably; write the label and keep the icon beside it as decoration.",
          "You need a picture, not a symbol — a logo, an illustration, a photo. This set is single-colour line art, not an image library."
        ]
      },
      "a11y": {
        "cs": [
          "Výchozí stav je dekorativní — aria-hidden. Drtivá většina ikon stojí vedle svého popisku a odečítač je má přeskočit.",
          "S title se ikona stane obrázkem s názvem (role=\"img\"). To je jediný povolený tvar ikony bez viditelného popisku.",
          "Barva jde z currentColor. Kontrast tedy neřeší ikona, ale text kolem ní — a mění se s motivem spolu s ním.",
          "Stav nesmí stát jen na barvě. Přepnutý stav si vezme druhý tvar (star → star-filled), protože barvoslepý uživatel rozdíl žluté a šedé nevidí a aria-pressed neuslyší — ten je pro odečítač. Obojí je potřeba, ne jedno z toho."
        ],
        "en": [
          "The default is decorative — aria-hidden. Most icons sit next to their own label and a screen reader should skip them.",
          "With title the icon becomes a named image (role=\"img\"). That is the only allowed shape for an icon without a visible label.",
          "The colour comes from currentColor. Contrast is therefore decided by the surrounding text, and follows the theme with it.",
          "State must not rest on colour alone. A toggled state takes the second shape (star → star-filled), because a colour-blind reader cannot see yellow against grey and will never hear aria-pressed — that one is for the screen reader. Both are needed, not either."
        ]
      },
      "props": [
        {
          "name": "name",
          "type": "IngotIconName",
          "required": true
        },
        {
          "name": "size",
          "type": "number",
          "required": false
        },
        {
          "name": "title",
          "type": "string",
          "required": false
        },
        {
          "name": "className",
          "type": "string",
          "required": false
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/icon",
        "en": "https://ingot.forgmatic.com/en/komponenty/icon"
      }
    },
    {
      "name": "IngotList",
      "display": "List",
      "tag": ".list",
      "status": "stable",
      "version": "1.1",
      "tokens": [
        "--ink-2"
      ],
      "classNameNote": {
        "cs": "`className` nebere. Vypadá stejně na každé obrazovce; rozvržení patří obalu kolem něj.",
        "en": "Does not take `className`. It looks the same on every screen; layout belongs to the wrapper around it."
      },
      "summary": {
        "cs": "Výčet: odrážky, čísla, nebo holý seznam bez značek. Značka i odsazení patří k sobě a rozhoduje se o nich na jednom místě.",
        "en": "A list: bullets, numbers, or no markers at all. The marker and the indent belong together, and one place decides both."
      },
      "avoidWhen": {
        "cs": [
          "Položky mají víc než jeden rozměr (jméno + stav + datum). To je tabulka — IngotTable.",
          "Je to jedna položka. Seznam o jednom prvku odečítač ohlásí jako seznam, což čtenáře upozorní na strukturu, která tam není.",
          "Dvojice „popisek — hodnota\" u jednoho záznamu. To je definiční seznam, ne výčet; význam je jiný a odečítač ho čte jinak."
        ],
        "en": [
          "The items have more than one dimension (name + state + date). That is a table — IngotTable.",
          "There is one item. A one-item list is still announced as a list, which points the reader at a structure that is not there.",
          "Label–value pairs for a single record. That is a description list, not a bullet list; the meaning differs and a screen reader reads it differently."
        ]
      },
      "a11y": {
        "cs": [
          "Vždycky vzniká skutečný <ul> nebo <ol> s <li>. To je celý důvod, proč tohle není <div> s odrážkami nakreslenými v CSS: počet položek a jejich hranice zná odečítač jen ze značek.",
          "variant=\"plain\" schovává jen značku, ne strukturu. Seznam odkazů proto zůstane seznamem, i když puntíky nevidíš.",
          "ordered nechává čísla na prohlížeči. Ručně napsané „1.\", „2.\" odečítač přečte jako text a při vložení kroku doprostřed se rozejdou."
        ],
        "en": [
          "It always produces a real <ul> or <ol> with <li> children. That is the whole reason this is not a <div> with bullets drawn in CSS: item count and item boundaries are known to a screen reader only from the markup.",
          "variant=\"plain\" hides the marker, not the structure. A list of links stays a list even when you see no bullets.",
          "ordered leaves the numbering to the browser. Hand-written \"1.\", \"2.\" is read as text and drifts the moment a step is inserted in the middle."
        ]
      },
      "props": [
        {
          "name": "items",
          "type": "readonly ReactNode[]",
          "required": true
        },
        {
          "name": "variant",
          "type": "\"bullet\" | \"ordered\" | \"plain\"",
          "required": false,
          "values": [
            "bullet",
            "ordered",
            "plain"
          ]
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/list",
        "en": "https://ingot.forgmatic.com/en/komponenty/list"
      }
    },
    {
      "name": "IngotMarketingComparison",
      "display": "MarketingComparison",
      "tag": ".cmp",
      "status": "beta",
      "version": "1.1",
      "tokens": [
        "--border",
        "--surface",
        "--surface-2",
        "--accent-bg",
        "--accent-ink",
        "--ink",
        "--ink-3"
      ],
      "classNameNote": {
        "cs": "`className` nebere. Vypadá stejně na každé obrazovce; rozvržení patří obalu kolem něj.",
        "en": "Does not take `className`. It looks the same on every screen; layout belongs to the wrapper around it."
      },
      "summary": {
        "cs": "Řádkové srovnání „dnes / s platformou“. Dvojice patří k jednomu úkolu, takže ji nejde napsat rozpojenou.",
        "en": "A row-wise “today / with the platform” comparison. The pair belongs to one task, so it cannot be written apart."
      },
      "avoidWhen": {
        "cs": [
          "Jsou to data administrace — tři sloupce, řazení, stránkování. Na to je IngotTable; tohle srovnání je sazba veřejné stránky bez chování tabulky.",
          "Srovnávají se plány a u každého je cena a akce. Na to je IngotMarketingPricing."
        ],
        "en": [
          "It is admin data — three columns, sorting, pagination. That is IngotTable; this comparison is public-page typography without table behaviour.",
          "Plans are being compared and each has a price and an action. That is IngotMarketingPricing."
        ]
      },
      "a11y": {
        "cs": [
          "Rozdíl mezi sloupci nenese jen barva: zvýrazněný sloupec má vlastní záhlaví a ikony, takže srovnání dává smysl i bez vnímání barvy.",
          "Na úzkém viewportu se mřížka roluje vodorovně a neskládá se do sloupce. Složené srovnání přestane srovnávat — dvojice se rozpadne na dva samostatné odstavce."
        ],
        "en": [
          "Colour is not the only carrier of the difference: the highlighted column has its own header and icons, so the comparison holds without colour perception.",
          "On a narrow viewport the grid scrolls horizontally instead of folding into a column. A folded comparison stops comparing — the pair falls apart into two separate paragraphs."
        ]
      },
      "props": [
        {
          "name": "headers",
          "type": "IngotMarketingComparisonHeaders",
          "required": true
        },
        {
          "name": "rows",
          "type": "readonly IngotMarketingComparisonRow[]",
          "required": true
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "extraProps": [
        {
          "name": "IngotMarketingComparisonRow",
          "props": [
            {
              "name": "id",
              "type": "string",
              "required": true
            },
            {
              "name": "task",
              "type": "string",
              "required": true
            },
            {
              "name": "before",
              "type": "IngotMarketingComparisonCell",
              "required": true
            },
            {
              "name": "after",
              "type": "IngotMarketingComparisonCell",
              "required": true
            }
          ]
        },
        {
          "name": "IngotMarketingComparisonCell",
          "props": [
            {
              "name": "icon",
              "type": "IngotIconName",
              "required": false
            },
            {
              "name": "text",
              "type": "string",
              "required": true
            }
          ]
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/marketing-comparison",
        "en": "https://ingot.forgmatic.com/en/komponenty/marketing-comparison"
      }
    },
    {
      "name": "IngotMarketingCta",
      "display": "MarketingCta",
      "tag": ".cta",
      "status": "beta",
      "version": "1.0",
      "tokens": [
        "--ink",
        "--bg",
        "--accent",
        "--accent-ink"
      ],
      "classNameNote": {
        "cs": "`className` nebere. Vypadá stejně na každé obrazovce; rozvržení patří obalu kolem něj.",
        "en": "Does not take `className`. It looks the same on every screen; layout belongs to the wrapper around it."
      },
      "summary": {
        "cs": "Závěrečná výzva — tmavý blok se dvěma akcemi. Hlavní akce je akcentová a je jediným barevným prvkem bloku.",
        "en": "The closing call to action — a dark block with two actions. The primary one is accented and the block's only colour."
      },
      "avoidWhen": {
        "cs": [
          "Akce něco spouští v aplikaci. Pak je to tlačítko — Button; tenhle blok naviguje odkazy.",
          "Na stránce už jsou dva tmavé bloky. Handoff jich připouští nejvýš dva (tohle CTA a patičku) a třetí z tmavé plochy udělá vzor, ne důraz."
        ],
        "en": [
          "The action starts something in the app. Then it is a button — Button; this block navigates with links.",
          "The page already has two dark blocks. The handoff allows at most two (this CTA and the footer) and a third turns a dark field into a pattern rather than emphasis."
        ]
      },
      "a11y": {
        "cs": [
          "Akce jsou odkazy, ne tlačítka. Odečítač je hlásí jako odkazy, dají se otevřít v novém panelu a fungují bez JavaScriptu — tlačítko by o všech třech věcech lhalo.",
          "Hlavní akce je akcentová schválně. Na tmavé ploše je světlé neutrální tlačítko k nerozeznání od vedlejšího a závěrečná výzva pak nemá kam poslat oko.",
          "Obě akce jsou Button s as=\"a\", ne ručně psané odkazy. Rozhodnutí o kontrastu akcentu v tmavém motivu tak zůstává na jednom místě — opsané třídy by ho zdvojily a druhá kopie by zestárla potichu."
        ],
        "en": [
          "The actions are links, not buttons. A screen reader announces them as links, they open in a new tab and they work without JavaScript — a button would lie about all three.",
          "The primary action is accented on purpose. On a dark field a light neutral button is indistinguishable from the secondary one and the closing call then has nowhere to send the eye.",
          "Both actions are Button with as=\"a\", not hand-written links. The dark-mode accent contrast decision therefore stays in one place — copied classes would duplicate it, and the second copy would go stale quietly."
        ]
      },
      "props": [
        {
          "name": "title",
          "type": "string",
          "required": true
        },
        {
          "name": "text",
          "type": "string",
          "required": false
        },
        {
          "name": "primary",
          "type": "IngotMarketingCtaAction",
          "required": true
        },
        {
          "name": "secondary",
          "type": "IngotMarketingCtaAction",
          "required": false
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "extraProps": [
        {
          "name": "IngotMarketingCtaAction",
          "props": [
            {
              "name": "label",
              "type": "string",
              "required": true
            },
            {
              "name": "href",
              "type": "string",
              "required": true
            }
          ]
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/marketing-cta",
        "en": "https://ingot.forgmatic.com/en/komponenty/marketing-cta"
      }
    },
    {
      "name": "IngotMarketingFaq",
      "display": "MarketingFaq",
      "tag": ".faq-item",
      "status": "beta",
      "version": "1.1",
      "tokens": [
        "--border",
        "--surface",
        "--surface-2",
        "--ink",
        "--ink-2",
        "--ink-3"
      ],
      "classNameNote": {
        "cs": "`className` nebere. Vypadá stejně na každé obrazovce; rozvržení patří obalu kolem něj.",
        "en": "Does not take `className`. It looks the same on every screen; layout belongs to the wrapper around it."
      },
      "summary": {
        "cs": "Časté dotazy — otázka je ovládací prvek hlásící svůj stav, odpověď pojmenovaná oblast. Odpověď je povinná.",
        "en": "A FAQ — the question is a control announcing its state, the answer a named region. The answer is required."
      },
      "avoidWhen": {
        "cs": [
          "Je to sbalitelný blok postranního panelu v aplikaci. Na to je IngotDisclosure: mono uppercase popisek, stav drží prohlížeč přes details.",
          "Odpověď je důležitá natolik, že ji čtenář nesmí minout. Sbalený obsah říká „tohle nepotřebuješ hned“ — u varování je to špatná zpráva o obsahu."
        ],
        "en": [
          "It is a collapsible block in an app side panel. That is IngotDisclosure: a mono uppercase label, with the browser holding state through details.",
          "The answer matters enough that the reader must not miss it. Collapsed content says “you do not need this yet” — for a warning that is the wrong thing to say."
        ]
      },
      "a11y": {
        "cs": [
          "Otázka nese aria-expanded i aria-controls a drží je komponenta, ne volající — atribut, který si musí přepnout někdo jiný, se dřív nebo později přepínat přestane.",
          "Rozbalená odpověď je pojmenovaná oblast, takže se v odečítači dá přeskočit jako celek. Klávesnice funguje bez naší pomoci: otázka je nativní ovládací prvek, Enter i mezerník na ní přepínají."
        ],
        "en": [
          "The question carries both aria-expanded and aria-controls, and the component holds them, not the caller — an attribute somebody else has to flip eventually stops being flipped.",
          "The expanded answer is a named region, so a screen reader can skip it as a whole. The keyboard works without our help: the question is a native control, Enter and Space toggle it."
        ]
      },
      "props": [
        {
          "name": "items",
          "type": "readonly IngotMarketingFaqItem[]",
          "required": true
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "extraProps": [
        {
          "name": "IngotMarketingFaqItem",
          "props": [
            {
              "name": "id",
              "type": "string",
              "required": true
            },
            {
              "name": "question",
              "type": "string",
              "required": true
            },
            {
              "name": "answer",
              "type": "ReactNode",
              "required": true
            }
          ]
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/marketing-faq",
        "en": "https://ingot.forgmatic.com/en/komponenty/marketing-faq"
      }
    },
    {
      "name": "IngotMarketingPricing",
      "display": "MarketingPricing",
      "tag": ".pricecard",
      "status": "beta",
      "version": "1.0",
      "tokens": [
        "--border",
        "--surface",
        "--ink",
        "--ink-2",
        "--ink-3",
        "--ok"
      ],
      "classNameNote": {
        "cs": "`className` nebere. Vypadá stejně na každé obrazovce; rozvržení patří obalu kolem něj.",
        "en": "Does not take `className`. It looks the same on every screen; layout belongs to the wrapper around it."
      },
      "summary": {
        "cs": "Karty plánů — cena, výčet vlastností a akce na patě. Zvýrazněná karta má obrys a odznak, ne akcentový rámeček.",
        "en": "Plan cards — a price, a feature list and an action at the foot. The featured card gets an outline and a badge, not an accent frame."
      },
      "avoidWhen": {
        "cs": [
          "Uživatel si vybírá z variant uvnitř aplikace. Na to je IngotOptionCard: ta je vybíratelná a nese stav volby, tahle karta jen nabízí a odkazuje pryč.",
          "Plány se mají porovnat vlastnost po vlastnosti. Na to je IngotMarketingComparison — tři karty vedle sebe se párovat nedají."
        ],
        "en": [
          "The user picks between options inside the app. That is IngotOptionCard: it is selectable and holds the choice; this card only offers and links away.",
          "The plans are to be compared feature by feature. That is IngotMarketingComparison — three cards side by side cannot be paired up."
        ]
      },
      "a11y": {
        "cs": [
          "Zvýraznění nenese jen barva: karta má obrys, stín a odznak se slovem, takže „nejčastější“ se dá přečíst, ne jen uvidět.",
          "Akce sedí na patě všech karet ve stejné výšce, takže se dá procházet tabulátorem shora dolů bez skákání po stránce."
        ],
        "en": [
          "Colour is not the only carrier of the highlight: the card has an outline, a shadow and a badge with a word in it, so “most popular” can be read, not merely seen.",
          "The action sits at the foot of every card at the same height, so tabbing runs top to bottom without jumping around the page."
        ]
      },
      "props": [
        {
          "name": "plans",
          "type": "readonly IngotMarketingPlan[]",
          "required": true
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "extraProps": [
        {
          "name": "IngotMarketingPlan",
          "props": [
            {
              "name": "id",
              "type": "string",
              "required": true
            },
            {
              "name": "name",
              "type": "string",
              "required": true
            },
            {
              "name": "price",
              "type": "string",
              "required": true
            },
            {
              "name": "period",
              "type": "string",
              "required": false
            },
            {
              "name": "description",
              "type": "string",
              "required": false
            },
            {
              "name": "features",
              "type": "readonly string[]",
              "required": true
            },
            {
              "name": "featured",
              "type": "boolean",
              "required": false
            },
            {
              "name": "badge",
              "type": "string",
              "required": false
            },
            {
              "name": "action",
              "type": "ReactNode",
              "required": true
            }
          ]
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/marketing-pricing",
        "en": "https://ingot.forgmatic.com/en/komponenty/marketing-pricing"
      }
    },
    {
      "name": "IngotMarketingSectionHead",
      "display": "MarketingSectionHead",
      "tag": ".section-head",
      "status": "beta",
      "version": "1.1",
      "tokens": [
        "--accent",
        "--ink",
        "--ink-3"
      ],
      "classNameNote": {
        "cs": "`className` nebere. Vypadá stejně na každé obrazovce; rozvržení patří obalu kolem něj.",
        "en": "Does not take `className`. It looks the same on every screen; layout belongs to the wrapper around it."
      },
      "summary": {
        "cs": "Hlavička marketingové sekce — nadpis vlevo, uvozující odstavec vpravo. Akcent nese jediný prvek: eyebrow.",
        "en": "A marketing section head — the heading on the left, the lede on the right. One element carries the accent: the eyebrow."
      },
      "avoidWhen": {
        "cs": [
          "Je to sekce admin obrazovky. Na to je IngotSection: menší rozestupy, jeden sloupec a nadpis, který se zapíše do osnovy obrazovky.",
          "Je to nadpis celé stránky. Ten sází IngotPageHeader a je h1; tahle hlavička je h2 a předpokládá, že nad ní nějaký h1 stojí."
        ],
        "en": [
          "It is a section of an admin screen. That is IngotSection: tighter spacing, one column and a heading that enters the screen outline.",
          "It is the heading of the whole page. That is IngotPageHeader and it is an h1; this head is an h2 and assumes an h1 stands above it."
        ]
      },
      "a11y": {
        "cs": [
          "Nadpis je h2, ne odstavec obarvený na velikost nadpisu. Osnova stránky je jedna z mála věcí, které se čtou naslepo.",
          "eyebrow je odstavec, ne nadpis vyšší úrovně. Kdyby byl nadpisem, měla by sekce dva nadpisy nad sebou a osnova by tvrdila, že jde o dvě sekce."
        ],
        "en": [
          "The heading is an h2, not a paragraph coloured to heading size. The page outline is one of the few things read blind.",
          "eyebrow is a paragraph, not a higher-level heading. Were it a heading, the section would have two stacked headings and the outline would claim two sections."
        ]
      },
      "props": [
        {
          "name": "eyebrow",
          "type": "string",
          "required": false
        },
        {
          "name": "title",
          "type": "string",
          "required": true
        },
        {
          "name": "lede",
          "type": "string",
          "required": false
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/marketing-section-head",
        "en": "https://ingot.forgmatic.com/en/komponenty/marketing-section-head"
      }
    },
    {
      "name": "IngotMarketingSegments",
      "display": "MarketingSegments",
      "tag": ".seg-card",
      "status": "beta",
      "version": "1.0",
      "tokens": [
        "--border",
        "--surface",
        "--surface-2",
        "--ink",
        "--ink-3"
      ],
      "classNameNote": {
        "cs": "`className` nebere. Vypadá stejně na každé obrazovce; rozvržení patří obalu kolem něj.",
        "en": "Does not take `className`. It looks the same on every screen; layout belongs to the wrapper around it."
      },
      "summary": {
        "cs": "Karty „pro koho“ — titulek, věta a štítky provozu. Žádný štítek nenese akcent; ten patří hlavičce sekce.",
        "en": "The “who it is for” cards — a title, a sentence and shop-floor tags. No tag carries the accent; that belongs to the section head."
      },
      "avoidWhen": {
        "cs": [
          "Štítek má hlásit stav entity (aktivní, čeká, zrušeno). Na to je IngotBadge — kulatá pilulka s verzálkami. Tenhle štítek je technický údaj, ne stav.",
          "Segmenty se mají porovnat mezi sebou položku po položce. Na to je IngotMarketingComparison: karty vedle sebe se párovat nedají."
        ],
        "en": [
          "The tag should announce entity state (active, pending, cancelled). That is IngotBadge — a round pill in capitals. This tag is a technical fact, not a state.",
          "The segments are to be compared item by item. That is IngotMarketingComparison: cards side by side cannot be paired up."
        ]
      },
      "a11y": {
        "cs": [
          "Titulky jsou h3, takže se segmenty dají procházet po nadpisech stejně jako zbytek stránky.",
          "Štítky jsou text, ne ikony ani barevné plochy — čtou se nahlas i tehdy, když se rozdíl mezi kartami nedá vidět."
        ],
        "en": [
          "The titles are h3, so the segments can be walked by heading like the rest of the page.",
          "The tags are text, not icons or colour fields — they are read aloud even when the difference between cards cannot be seen."
        ]
      },
      "props": [
        {
          "name": "items",
          "type": "readonly IngotMarketingSegmentItem[]",
          "required": true
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "extraProps": [
        {
          "name": "IngotMarketingSegmentItem",
          "props": [
            {
              "name": "title",
              "type": "string",
              "required": true
            },
            {
              "name": "text",
              "type": "string",
              "required": true
            },
            {
              "name": "tags",
              "type": "readonly string[]",
              "required": true
            }
          ]
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/marketing-segments",
        "en": "https://ingot.forgmatic.com/en/komponenty/marketing-segments"
      }
    },
    {
      "name": "IngotMarketingSteps",
      "display": "MarketingSteps",
      "tag": ".step",
      "status": "beta",
      "version": "1.0",
      "tokens": [
        "--border",
        "--surface",
        "--accent-bg",
        "--accent-ink",
        "--ink",
        "--ink-3",
        "--ink-4"
      ],
      "classNameNote": {
        "cs": "`className` nebere. Vypadá stejně na každé obrazovce; rozvržení patří obalu kolem něj.",
        "en": "Does not take `className`. It looks the same on every screen; layout belongs to the wrapper around it."
      },
      "summary": {
        "cs": "Kroky „jak to funguje“ — karty s pořadovým číslem a šipkou k dalšímu kroku. Číslo se počítá z pořadí, nepíše se.",
        "en": "The “how it works” steps — cards with an ordinal and an arrow to the next one. The number comes from the order, it is not typed."
      },
      "avoidWhen": {
        "cs": [
          "Položky jsou rovnocenné a pořadí je nahodilé. Pak je to výčet featur — IngotMarketingTri.",
          "Je to průvodce v aplikaci, kterým uživatel prochází. Na to je IngotStepCard: ta drží stav kroku (hotovo, běží) a sbalí se; tyhle karty jsou statický popis."
        ],
        "en": [
          "The items are equal and the order is arbitrary. Then it is a feature list — IngotMarketingTri.",
          "It is an in-app wizard the user walks through. That is IngotStepCard: it holds step state (done, in progress) and collapses; these cards are a static description."
        ]
      },
      "a11y": {
        "cs": [
          "Kroky jsou ol, takže odečítač hlásí pořadí i počet — pořadí je tu význam, ne vzhled.",
          "Šipka k dalšímu kroku je dekorace a poslední karta ji nemá: pořadí se čte ze seznamu, ne z ikony."
        ],
        "en": [
          "The steps are an ol, so a screen reader announces position and count — the order is meaning here, not decoration.",
          "The arrow to the next step is decoration and the last card has none: the order is read from the list, not from an icon."
        ]
      },
      "props": [
        {
          "name": "items",
          "type": "readonly IngotMarketingStepItem[]",
          "required": true
        },
        {
          "name": "columns",
          "type": "3 | 4",
          "required": false
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "extraProps": [
        {
          "name": "IngotMarketingStepItem",
          "props": [
            {
              "name": "title",
              "type": "string",
              "required": true
            },
            {
              "name": "text",
              "type": "string",
              "required": true
            }
          ]
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/marketing-steps",
        "en": "https://ingot.forgmatic.com/en/komponenty/marketing-steps"
      }
    },
    {
      "name": "IngotMarketingTri",
      "display": "MarketingTri",
      "tag": ".tri",
      "status": "beta",
      "version": "1.0",
      "tokens": [
        "--border",
        "--surface",
        "--accent",
        "--accent-bg",
        "--accent-border",
        "--ink",
        "--ink-3"
      ],
      "classNameNote": {
        "cs": "`className` nebere. Vypadá stejně na každé obrazovce; rozvržení patří obalu kolem něj.",
        "en": "Does not take `className`. It looks the same on every screen; layout belongs to the wrapper around it."
      },
      "summary": {
        "cs": "Trojice featur pod hlavičkou sekce — ikona v akcentovém rámečku, titulek a věta. Jeden rám, panely oddělené vlasovou linkou.",
        "en": "Three features under a section head — an icon in an accent frame, a title and a sentence. One frame, panels split by a hairline."
      },
      "avoidWhen": {
        "cs": [
          "Položky mají pořadí a jedna vede k druhé. To jsou kroky — IngotMarketingSteps číslují a kreslí šipku k dalšímu kroku.",
          "Je to dlaždice v adminu. Na to je Card: trojice má rozestupy veřejné stránky a v administraci působí rozvolněně."
        ],
        "en": [
          "The items have an order and one leads to the next. Those are steps — IngotMarketingSteps numbers them and draws an arrow to the next one.",
          "It is a tile in the admin. That is Card: the trio carries public-page spacing and reads as loose inside the admin."
        ]
      },
      "a11y": {
        "cs": [
          "Titulky jsou h3 pod h2 hlavičky sekce, takže osnova sedí i tehdy, když čtenář stránku prochází po nadpisech.",
          "Ikona je dekorace — nese ji IngotIcon s aria-hidden, protože význam už stojí v titulku vedle ní."
        ],
        "en": [
          "The titles are h3 under the section head's h2, so the outline holds even when a reader walks the page by headings.",
          "The icon is decoration — IngotIcon marks it aria-hidden, because the meaning already stands in the title beside it."
        ]
      },
      "props": [
        {
          "name": "items",
          "type": "readonly IngotMarketingTriItem[]",
          "required": true
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "extraProps": [
        {
          "name": "IngotMarketingTriItem",
          "props": [
            {
              "name": "icon",
              "type": "IngotIconName",
              "required": true
            },
            {
              "name": "title",
              "type": "string",
              "required": true
            },
            {
              "name": "text",
              "type": "string",
              "required": true
            }
          ]
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/marketing-tri",
        "en": "https://ingot.forgmatic.com/en/komponenty/marketing-tri"
      }
    },
    {
      "name": "IngotMegaMenu",
      "display": "MegaMenu",
      "tag": ".megamenu",
      "status": "beta",
      "version": "2.8",
      "tokens": [
        "--surface",
        "--surface-2",
        "--surface-3",
        "--border",
        "--ink",
        "--ink-3",
        "--ink-4",
        "--accent",
        "--accent-ink",
        "--font-mono",
        "--r-sm",
        "--r-lg",
        "--shadow-lg"
      ],
      "classNameNote": {
        "cs": "`className` nebere. Vypadá stejně na každé obrazovce; rozvržení patří obalu kolem něj.",
        "en": "Does not take `className`. It looks the same on every screen; layout belongs to the wrapper around it."
      },
      "summary": {
        "cs": "Rozbalené menu sekce z horní lišty — skupiny odkazů v jednom nebo dvou sloupcích a náhledový sloupec, který popisuje položku pod kurzorem i pod fokusem.",
        "en": "The opened section menu from the top bar — groups of links in one or two columns, plus a preview column describing the item under the cursor and under focus alike."
      },
      "avoidWhen": {
        "cs": [
          "Sekce, která má jedinou obrazovku. Menu s jednou položkou je krok navíc — z takové sekce udělej rovnou odkaz v liště.",
          "Sekce s víc než dvěma tucty odkazů. Menu si sloupce rozdělí samo, ale seznam všeho, co kdy vzniklo, nezachrání — taková sekce je ve skutečnosti dvě a patří do lišty zvlášť.",
          "Nabídka akcí nad vybraným záznamem — smazat, duplikovat, exportovat. Tohle menu obsahuje odkazy na obrazovky, ne příkazy.",
          "Rejstřík stránek uvnitř obsahu, třeba v dlouhém nastavení. Na to je IngotSideNav — je to obsah stránky, ne rám aplikace."
        ],
        "en": [
          "A section with a single screen. A menu holding one item is a step too many — make that section a plain link in the bar.",
          "A section with more than two dozen links. The menu splits its columns by itself, but it cannot save a list of everything that ever existed — such a section is really two, and they belong in the bar separately.",
          "A list of actions on a selected record — delete, duplicate, export. This menu holds links to screens, not commands.",
          "An index of pages inside the content, for example in a long settings flow. That is IngotSideNav — it is page content, not the application frame."
        ]
      },
      "a11y": {
        "cs": [
          "Náhled sleduje položku pod kurzorem i pod fokusem — procházení Tabem přepíná popis stejně jako myš, klávesnice není druhá kategorie. Dokud čtenář na žádné položce nestojí, náhled popisuje první.",
          "Popis položky čte odečítač z odkazu samotného (aria-describedby); náhledový sloupec je jeho vizuální kopie a je aria-hidden, aby nic neznělo dvakrát.",
          "Otevřená sekce se v liště značí plochou --surface-3, ne akcentem. Akcent v téhle aplikaci znamená akci, a rozbalené menu žádná akce není.",
          "Menu je nav s vlastním jménem z label. Odečítač tak umí seznam navigací rozlišit od horní lišty.",
          "Otevřenou položku značí aria-current, ne pouhá barva. Kdo barvu nevidí, se jinak nedozví, kde v aplikaci právě je."
        ],
        "en": [
          "The preview follows the item under the cursor and under focus — tabbing switches the description just like the mouse does; the keyboard is not a second-class citizen. Until the reader is on an item, the preview describes the first one.",
          "A screen reader hears an item's description from the link itself (aria-describedby); the preview column is its visual copy and is aria-hidden, so nothing sounds twice.",
          "An open section is marked in the bar with the --surface-3 surface, not with the accent. The accent means an action in this application, and an open menu is not an action.",
          "The menu is a nav with its own name from label. That lets a screen reader tell it apart from the top bar in the list of navigations.",
          "The open item is marked with aria-current, not by colour alone. Someone who does not see the colour would otherwise never learn where in the application they are."
        ]
      },
      "props": [
        {
          "name": "groups",
          "type": "readonly IngotMegaMenuGroup[]",
          "required": true
        },
        {
          "name": "art",
          "type": "ReactNode",
          "required": false
        },
        {
          "name": "label",
          "type": "string",
          "required": true
        },
        {
          "name": "onLockedItemClick",
          "type": "(item) => void",
          "required": false
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "extraProps": [
        {
          "name": "IngotMegaMenuGroup",
          "props": [
            {
              "name": "title",
              "type": "string",
              "required": false
            },
            {
              "name": "items",
              "type": "readonly IngotMegaMenuItem[]",
              "required": true
            }
          ]
        },
        {
          "name": "IngotMegaMenuItem",
          "props": [
            {
              "name": "href",
              "type": "string",
              "required": true
            },
            {
              "name": "label",
              "type": "string",
              "required": true
            },
            {
              "name": "description",
              "type": "string",
              "required": false
            },
            {
              "name": "icon",
              "type": "ReactNode",
              "required": false
            },
            {
              "name": "count",
              "type": "number",
              "required": false
            },
            {
              "name": "current",
              "type": "boolean",
              "required": false
            },
            {
              "name": "onClick",
              "type": "(event) => void",
              "required": false
            },
            {
              "name": "locked",
              "type": "boolean",
              "required": false
            },
            {
              "name": "muted",
              "type": "boolean",
              "required": false
            },
            {
              "name": "marker",
              "type": "ReactNode",
              "required": false
            },
            {
              "name": "testId",
              "type": "string",
              "required": false
            }
          ]
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/mega-menu",
        "en": "https://ingot.forgmatic.com/en/komponenty/mega-menu"
      }
    },
    {
      "name": "IngotMenu",
      "display": "Menu",
      "tag": ".menu",
      "status": "beta",
      "version": "2.0",
      "tokens": [
        "--surface",
        "--surface-2",
        "--border",
        "--ink",
        "--ink-3",
        "--ink-4",
        "--danger",
        "--danger-bg",
        "--r-lg"
      ],
      "classNameNote": {
        "cs": "Bere `className`, ale jen na rozvržení — typicky šířku menu. Řádky, tóny a mezery drží primitivum.",
        "en": "Takes `className`, but for layout only — typically the menu's width. The rows, tones and spacing stay with the primitive."
      },
      "summary": {
        "cs": "Seznam akcí v popoveru: role menu, šipky, Home/End, psaní podle prvních písmen, oddělovače a tón danger.",
        "en": "A list of actions in a popover: the menu roles, arrows, Home/End, type-ahead, separators and the danger tone."
      },
      "avoidWhen": {
        "cs": [
          "Vybírá se HODNOTA, ne akce — to je IngotSelect nebo IngotSegmented. Menu spouští, nevybírá.",
          "Navigace mezi sekcemi aplikace — to je IngotMegaMenu v horní liště.",
          "Dvě tři akce, které se vejdou vedle sebe. Menu, které se musí otevřít, aby ukázalo dvě položky, je krok navíc."
        ],
        "en": [
          "A VALUE is being chosen, not an action — that is IngotSelect or IngotSegmented. A menu triggers, it does not select.",
          "Navigation between the application's sections — that is IngotMegaMenu in the top bar.",
          "Two or three actions that fit side by side. A menu that has to be opened to show two items is one step too many."
        ]
      },
      "a11y": {
        "cs": [
          "role=\"menu\" a role=\"menuitem\"; tlačítko, které menu otevírá, nese aria-haspopup a aria-expanded.",
          "Fokus vstoupí do menu po otevření. Šipky procházejí položky, Home a End skáčou na kraje, psaní hledá podle prvních písmen.",
          "Tab menu opouští, neroluje v něm. Menu je jedna zastávka na cestě stránkou — tak to říká ARIA a jen tak z něj uživatel klávesnice odejde bez triku.",
          "Vypnutá položka zůstává vidět a hlásí se jako nedostupná. Zmizet by znamenalo přerovnat menu pod rukama."
        ],
        "en": [
          "role=\"menu\" and role=\"menuitem\"; the button that opens it carries aria-haspopup and aria-expanded.",
          "Focus enters the menu on open. Arrows walk the items, Home and End jump to the ends, typing finds by the first letters.",
          "Tab leaves the menu, it does not cycle inside. A menu is one stop on the way through the page — that is what ARIA says, and the only version a keyboard user escapes without a trick.",
          "A disabled item stays visible and announces itself as unavailable. Disappearing would reshuffle the menu under the reader's hands."
        ]
      },
      "props": [
        {
          "name": "open",
          "type": "boolean",
          "required": true
        },
        {
          "name": "anchorRef",
          "type": "RefObject<HTMLElement | null>",
          "required": true
        },
        {
          "name": "onClose",
          "type": "() => void",
          "required": true
        },
        {
          "name": "items",
          "type": "readonly IngotMenuItem[]",
          "required": true
        },
        {
          "name": "label",
          "type": "string",
          "required": true
        },
        {
          "name": "placement",
          "type": "IngotPlacement",
          "required": false
        },
        {
          "name": "className",
          "type": "string",
          "required": false
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "extraProps": [
        {
          "name": "IngotMenuItem",
          "props": [
            {
              "name": "label",
              "type": "string",
              "required": true
            },
            {
              "name": "onSelect",
              "type": "() => void",
              "required": true
            },
            {
              "name": "icon",
              "type": "IngotIconName",
              "required": false
            },
            {
              "name": "disabled",
              "type": "boolean",
              "required": false
            },
            {
              "name": "tone",
              "type": "Extract<IngotTone, \"neutral\" | \"danger\">",
              "required": false
            },
            {
              "name": "separatorBefore",
              "type": "boolean",
              "required": false
            }
          ]
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/menu",
        "en": "https://ingot.forgmatic.com/en/komponenty/menu"
      }
    },
    {
      "name": "IngotMetrics",
      "display": "Metrics",
      "tag": ".metricstrip",
      "status": "beta",
      "version": "1.5",
      "tokens": [
        "--surface",
        "--border",
        "--ink",
        "--ink-3",
        "--ink-4",
        "--warn",
        "--danger",
        "--accent",
        "--font-mono",
        "--r-md"
      ],
      "classNameNote": {
        "cs": "`className` nebere. Vypadá stejně na každé obrazovce; rozvržení patří obalu kolem něj.",
        "en": "Does not take `className`. It looks the same on every screen; layout belongs to the wrapper around it."
      },
      "summary": {
        "cs": "Čísla, podle kterých se obrazovka čte na první pohled. Dvě hustoty jedné komponenty: pruh pod hlavičkou a kompaktní shluk do hlavičky.",
        "en": "The numbers by which a screen is read at a glance. Two densities of one component: a strip below the header and a compact cluster inside it."
      },
      "avoidWhen": {
        "cs": [
          "Graf nebo trend. Buňka ukazuje jedno číslo teď, ne jeho vývoj — vývoj se do dvaadvaceti pixelů výšky vykreslit nedá.",
          "Stav jedné položky. „Čeká na potvrzení“ u konkrétní objednávky je odznak, tedy IngotBadge; metriky mluví o množině.",
          "Zvýraznění toho nejdůležitějšího čísla barvou. Tón je informace, ne důraz: danger znamená, že to číslo je problém, ne že je nejdůležitější. Obarvená polovina pruhu nesděluje nic."
        ],
        "en": [
          "A chart or a trend. A cell shows one number now, not its development — development does not fit into twenty-two pixels of height.",
          "The state of a single item. “Awaiting confirmation” on one specific order is a badge, that is IngotBadge; metrics talk about a set.",
          "Highlighting the most important number with colour. The tone is information, not emphasis: danger means that number is a problem, not that it matters most. Half a strip in colour says nothing."
        ]
      },
      "a11y": {
        "cs": [
          "Pruh je role=\"group\" s aria-label, takže se čísla ohlásí jako jeden celek, ne jako pět nesouvisejících odstavců uprostřed stránky.",
          "Tón je jediné, co barva sděluje — a sděluje to i jinak: hodnota má vedle sebe popisek a ve variantě strip i poznámku. Kdo červenou nerozezná, přečte si totéž slovy.",
          "Hodnota je mono s tabular-nums, popisek ne. Čísla se v pruhu čtou pod sebou a proporcionální číslice je rozhodí; je to součást specifikace, ne detail. Popisek je věta, ne údaj."
        ],
        "en": [
          "The strip is a role=\"group\" with an aria-label, so the numbers are announced as one whole, not as five unrelated paragraphs in the middle of the page.",
          "The tone is the only thing colour conveys — and it conveys it in other ways too: every value has a label beside it and, in the strip variant, a note as well. Anyone who cannot tell the red apart reads the same thing in words.",
          "The value is monospaced with tabular-nums, the label is not. Numbers in a strip are read one under another and proportional digits throw that off; it is part of the specification, not a detail. A label is a sentence, not a figure."
        ]
      },
      "props": [
        {
          "name": "items",
          "type": "readonly IngotMetric[]",
          "required": true
        },
        {
          "name": "variant",
          "type": "\"strip\" | \"inline\"",
          "required": false,
          "values": [
            "strip",
            "inline"
          ]
        },
        {
          "name": "label",
          "type": "string",
          "required": true
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "extraProps": [
        {
          "name": "IngotMetric",
          "props": [
            {
              "name": "label",
              "type": "string",
              "required": true
            },
            {
              "name": "value",
              "type": "ReactNode",
              "required": true
            },
            {
              "name": "note",
              "type": "ReactNode",
              "required": false
            },
            {
              "name": "tone",
              "type": "\"neutral\" | \"warn\" | \"danger\"",
              "required": false,
              "values": [
                "neutral",
                "warn",
                "danger"
              ]
            },
            {
              "name": "testId",
              "type": "string",
              "required": false
            },
            {
              "name": "trend",
              "type": "readonly number[]",
              "required": false
            }
          ]
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/metrics",
        "en": "https://ingot.forgmatic.com/en/komponenty/metrics"
      }
    },
    {
      "name": "IngotModal",
      "display": "Modal",
      "tag": ".modal",
      "status": "stable",
      "version": "1.7",
      "tokens": [
        "--surface",
        "--surface-2",
        "--border",
        "--ink",
        "--ink-3",
        "--accent-bg",
        "--r-sm",
        "--r-lg",
        "--shadow-lg"
      ],
      "classNameNote": {
        "cs": "`className` nebere. Geometrie překryvu je součástí přístupnostní laťky (past fokusu, zámek rolování) — šířku řídí `size`.",
        "en": "Does not take `className`. The overlay's geometry is part of the accessibility bar (focus trap, scroll lock) — `size` drives the width."
      },
      "summary": {
        "cs": "Skořápka dialogu s a11y laťkou: focus trap, ESC, scroll lock, aria-modal a návrat fokusu na spouštěč.",
        "en": "Dialog shell with the accessibility floor built in: focus trap, ESC, scroll lock, aria-modal and focus returned to the trigger."
      },
      "avoidWhen": {
        "cs": [
          "Jde o potvrzení destruktivního kroku. Na to je IngotConfirm, který nad tímhle primitivem stojí a přidává tlačítka i veto.",
          "Sdělení nikoho neblokuje („uloženo“, „nepovedlo se“). Dialog je nejtěžší afordance, kterou máš; hlášení v místě děje stačí.",
          "Obsah je tak dlouhý, že se v panelu jenom posouvá. Pak je to obrazovka, ne dialog."
        ],
        "en": [
          "You are confirming a destructive step. That is IngotConfirm, which is built on top of this primitive and adds the buttons and the veto.",
          "The message blocks nobody (\"saved\", \"that did not work\"). A dialog is the heaviest affordance you have; a message where the action happened is enough.",
          "The content is long enough that it only scrolls inside the panel. Then it is a screen, not a dialog."
        ]
      },
      "a11y": {
        "cs": [
          "Laťku drží primitivum samo: role=\"dialog\", aria-modal=\"true\" a aria-labelledby mířící na <h2> s titulkem.",
          "Focus trap na Tab i Shift+Tab, ESC zavírá a po zavření se fokus vrací na prvek, který dialog otevřel.",
          "Scroll lock pozadí se počítá podle hloubky: původní overflow se obnoví až se zavřením posledního dialogu, takže vnořený dialog ho neuvolní předčasně.",
          "Ikona křížku je aria-hidden; popisek nese aria-label z closeLabel."
        ],
        "en": [
          "The primitive holds the floor itself: role=\"dialog\", aria-modal=\"true\" and aria-labelledby pointing at the <h2> that carries the title.",
          "A focus trap on both Tab and Shift+Tab, ESC closes, and on close the focus returns to the element that opened the dialog.",
          "The background scroll lock is depth-counted: the original overflow is restored only when the last dialog closes, so a nested dialog does not release it early.",
          "The close icon is aria-hidden; the label comes from closeLabel via aria-label."
        ]
      },
      "props": [
        {
          "name": "title",
          "type": "ReactNode",
          "required": true
        },
        {
          "name": "onClose",
          "type": "() => void",
          "required": true
        },
        {
          "name": "children",
          "type": "ReactNode",
          "required": true
        },
        {
          "name": "closeLabel",
          "type": "string",
          "required": true
        },
        {
          "name": "width",
          "type": "number",
          "required": false
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/modal",
        "en": "https://ingot.forgmatic.com/en/komponenty/modal"
      }
    },
    {
      "name": "IngotOpIcon",
      "display": "OpIcon",
      "tag": "[data-op-icon]",
      "status": "stable",
      "version": "1.2",
      "tokens": [
        "currentColor"
      ],
      "classNameNote": {
        "cs": "Bere `className` na umístění; barvu určuje kategorie procesu, velikost `size`.",
        "en": "Takes `className` for placement; the process category decides the colour and `size` the size."
      },
      "summary": {
        "cs": "Ikona výrobní operace. Kresbu bere z knihovny operací, barvu z kategorie procesu a klíč z databáze — sama si nevymýšlí nic.",
        "en": "A manufacturing-operation icon. It takes the drawing from the operation library, the ink from the process category and the key from the database — it invents nothing."
      },
      "avoidWhen": {
        "cs": [
          "Je to ikona rozhraní — nahrání, hledání, koš. IngotIcon.",
          "Vedle už svítí opdot, barevná tečka kategorie. Tečka a ikona říkají totéž a vedle sebe si protiřečí — vyber jedno.",
          "Klíč bys skládal z názvu operace nebo ze slugu. Ikonu vybírá admin ručně; žádné párování slug→ikona v repu není a dohadovat ho znamená ukázat cizí technologii.",
          "Chybí ti technologie. Přidej ikonu do knihovny operací — nikdy emoji."
        ],
        "en": [
          "It is an interface icon — upload, search, trash. IngotIcon.",
          "An opdot, the category colour dot, is already showing next to it. The dot and the icon say the same thing and contradict each other side by side — pick one.",
          "You would build the key from the operation name or slug. The admin picks the icon by hand; there is no slug→icon matching in this repo and guessing one shows the wrong technology.",
          "Your technology is missing. Add an icon to the operation library — never an emoji."
        ]
      },
      "a11y": {
        "cs": [
          "Výchozí stav je dekorativní. Vedle názvu operace je to správně — název už ten význam nese.",
          "Bez viditelného názvu MUSÍ přijít title; komponenta z něj udělá role=\"img\" a aria-label. Ikona operace bez jména je jinak nečitelná i pro vidícího.",
          "Varianta :white je bílá čára — potřebuje pod sebou tmavou plochu od volajícího, jinak zmizí. Kontrast tady kit neuhlídá, protože pozadí nezná."
        ],
        "en": [
          "The default is decorative. Next to the operation name that is correct — the name already carries the meaning.",
          "Without a visible name a title is REQUIRED; the component turns it into role=\"img\" and aria-label. An unnamed operation icon is unreadable even to a sighted user.",
          "The :white variant is white line art — it needs a dark surface from the caller or it disappears. The kit cannot police that contrast because it does not know the background."
        ]
      },
      "props": [
        {
          "name": "token",
          "type": "string | null | undefined",
          "required": true
        },
        {
          "name": "size",
          "type": "number",
          "required": false
        },
        {
          "name": "categoryColor",
          "type": "string | null",
          "required": false
        },
        {
          "name": "title",
          "type": "string",
          "required": false
        },
        {
          "name": "className",
          "type": "string",
          "required": false
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/op-icon",
        "en": "https://ingot.forgmatic.com/en/komponenty/op-icon"
      }
    },
    {
      "name": "IngotOptionCard",
      "display": "OptionCard",
      "tag": ".optioncard",
      "status": "beta",
      "version": "1.2",
      "tokens": [
        "--surface",
        "--border-strong",
        "--ink",
        "--ink-3",
        "--ink-4",
        "--accent",
        "--r-md"
      ],
      "classNameNote": {
        "cs": "`className` nebere. Vypadá stejně na každé obrazovce; rozvržení patří obalu kolem něj.",
        "en": "Does not take `className`. It looks the same on every screen; layout belongs to the wrapper around it."
      },
      "summary": {
        "cs": "Výběr jedné varianty, kde volba potřebuje vysvětlení. Klikatelná je celá karta, vybraná varianta je poznat obrysem v akcentu.",
        "en": "Picking one option where the choice needs an explanation. The whole card is clickable, and the selected option is marked by an accent outline."
      },
      "avoidWhen": {
        "cs": [
          "Zapnout/vypnout jednu vlastnost. To je přepínač, ne dvě karty „Zapnuto“ a „Vypnuto“ — kartami se z jednoho stavu stanou dva.",
          "Dlouhý výčet rovnocenných hodnot bez vysvětlení — země, měna, materiál. Bez popisné věty karta nepřidá nic, co by nezvládl rozbalovací seznam, a zabere několikanásobek místa.",
          "Výběr více možností najednou. Uvnitř je přepínač skupiny, takže volby se vylučují; na vícenásobný výběr patří zaškrtávací pole.",
          "Volba, která spouští akci. Karta jen mění hodnotu — tlačítko, po kterém se něco stane, vypadá jinak a patří jinam."
        ],
        "en": [
          "Turning a single feature on or off. That is a switch, not two cards reading “On” and “Off” — cards turn one state into two.",
          "A long list of equivalent values with nothing to explain — country, currency, material. Without a descriptive sentence the card adds nothing a dropdown could not do, and takes several times the space.",
          "Selecting more than one option. Inside is a grouped radio input, so the options exclude each other; multi-select calls for checkboxes.",
          "A choice that triggers an action. The card only changes a value — a button that makes something happen looks different and belongs elsewhere."
        ]
      },
      "a11y": {
        "cs": [
          "Klikatelná je celá karta, ne jen kolečko. Karta s textem, kde reaguje jen puntík o průměru 16 px, je past, obzvlášť na dotyku — proto je popiskem label kolem celého obsahu.",
          "Uvnitř je skutečný přepínač, takže šipky přepínají mezi variantami a fokus se chová jako u nativní skupiny. To platí jen tehdy, když varianty jedné volby sdílejí stejné name.",
          "Vybraná varianta je poznat obrysem v akcentu, ne výplní. Vyplněná karta by soupeřila s obsahem, který popisuje; obrys drží kontrast i v tmavém motivu.",
          "disabled zamkne přepínač, ne jen vzhled karty. Nedostupná varianta tak vypadne z pořadí tabulátoru místo toho, aby ho brala bez efektu."
        ],
        "en": [
          "The whole card is clickable, not just the circle. A card full of text where only a 16 px dot responds is a trap, especially on touch — hence a label wrapped around the entire content.",
          "Inside is a real radio input, so arrow keys move between options and focus behaves like a native group. That only holds when the options of one choice share the same name.",
          "The selected option is marked by an accent outline, not a fill. A filled card would compete with the content it describes; the outline keeps contrast in the dark theme too.",
          "disabled locks the input, not just the card's looks. An unavailable option drops out of the tab order instead of taking a stop with no effect."
        ]
      },
      "props": [
        {
          "name": "name",
          "type": "string",
          "required": true
        },
        {
          "name": "value",
          "type": "string",
          "required": true
        },
        {
          "name": "checked",
          "type": "boolean",
          "required": true
        },
        {
          "name": "onChange",
          "type": "(value: string) => void",
          "required": true
        },
        {
          "name": "title",
          "type": "ReactNode",
          "required": true
        },
        {
          "name": "description",
          "type": "ReactNode",
          "required": false
        },
        {
          "name": "disabled",
          "type": "boolean",
          "required": false
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/option-card",
        "en": "https://ingot.forgmatic.com/en/komponenty/option-card"
      }
    },
    {
      "name": "IngotPageHeader",
      "display": "PageHeader",
      "tag": ".pagehead",
      "status": "stable",
      "version": "1.0",
      "tokens": [
        "--ink",
        "--ink-3"
      ],
      "classNameNote": {
        "cs": "`className` nebere. Sazba nadpisu stránky patří systému, ne obrazovce — mezery pod ním řeší rám stránky.",
        "en": "Does not take `className`. The page heading's typesetting belongs to the system, not to a screen — the page frame holds the spacing below it."
      },
      "summary": {
        "cs": "Hlavička obrazovky: nadpis, věta pod ním, akce vpravo. Typografický spec má jedno místo a nenese s sebou router.",
        "en": "The screen header: a title, a sentence under it, actions on the right. One home for the type spec, and it drags no router along."
      },
      "avoidWhen": {
        "cs": [
          "Je to nadpis sekce uvnitř obrazovky, ne nadpis obrazovky. Na to je IngotSection; dvě <h1> na stránce rozbijí osnovu, kterou odečítač čte.",
          "Potřebuješ drobečkovou navigaci. Tu záměrně neumí: drobečky znají routu, a tenhle modul zůstává bez závislosti na routeru. Skládá se o patro výš — drobečky nad hlavičkou, hlavička z tohohle primitiva."
        ],
        "en": [
          "It is a section heading inside a screen, not the screen's title. Use IngotSection; two <h1> elements break the outline a screen reader reads.",
          "You need breadcrumbs. It deliberately cannot do them: breadcrumbs know about routing, and this module stays free of a router dependency. Compose them one level up — the trail above the header, the header from this primitive."
        ]
      },
      "a11y": {
        "cs": [
          "Nadpis je <h1> a na obrazovce má být jediný — je to kořen osnovy, podle které se odečítač orientuje.",
          "description je odstavec pod nadpisem, ne aria-describedby. Čte ho každý, ne jen odečítač.",
          "Modul nemá žádnou závislost, a to je jeho smysl. Typografický spec je tu vyvedený zvlášť (INGOT_PAGE_TITLE_CLASS), takže se dá použít i tam, kde by se router načítat neměl — třeba na stránce, která žádné routy nemá."
        ],
        "en": [
          "The title is an <h1> and there should be exactly one per screen — it is the root of the outline a screen reader navigates by.",
          "description is a paragraph under the title, not aria-describedby. Everyone reads it, not only a screen reader.",
          "The module has no dependencies, and that is the point. The type spec is exported separately (INGOT_PAGE_TITLE_CLASS), so it can be used where a router has no business being loaded — on a page that has no routes at all, say."
        ]
      },
      "props": [
        {
          "name": "title",
          "type": "ReactNode",
          "required": true
        },
        {
          "name": "description",
          "type": "ReactNode",
          "required": false
        },
        {
          "name": "actions",
          "type": "ReactNode",
          "required": false
        },
        {
          "name": "titleAdornment",
          "type": "ReactNode",
          "required": false
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/page-header",
        "en": "https://ingot.forgmatic.com/en/komponenty/page-header"
      }
    },
    {
      "name": "IngotPageHint",
      "display": "PageHint",
      "tag": ".pagehint",
      "status": "beta",
      "version": "1.4",
      "tokens": [
        "--ink",
        "--ink-2",
        "--ink-3",
        "--surface-2",
        "--accent",
        "--accent-ink",
        "--accent-bg",
        "--accent-border",
        "--r-sm",
        "--r-lg"
      ],
      "classNameNote": {
        "cs": "`className` nebere. Vypadá stejně na každé obrazovce; rozvržení patří obalu kolem něj.",
        "en": "Does not take `className`. It looks the same on every screen; layout belongs to the wrapper around it."
      },
      "summary": {
        "cs": "Nápověda stránky se žárovkou: klik jednorázově zvýrazní prvky, kterých se nápověda týká.",
        "en": "A page hint with a bulb: a click highlights the elements the hint talks about, once."
      },
      "avoidWhen": {
        "cs": [
          "Potvrzení výsledku operace („Uloženo“) — to je toast, ne nápověda.",
          "Varování nebo chyba, na kterou musí uživatel reagovat — nápověda je volitelné čtení, které jde vypnout.",
          "Vícekrokové provedení celým procesem — pruh popisuje jednu obrazovku; průvodce je jiný vzor."
        ],
        "en": [
          "Confirming the result of an operation (“Saved”) — that is a toast, not a hint.",
          "A warning or an error the user must react to — a hint is optional reading that can be switched off.",
          "A multi-step walkthrough of a whole process — the strip describes one screen; a guided tour is a different pattern."
        ]
      },
      "a11y": {
        "cs": [
          "Žárovka je obyčejné tlačítko s popisným aria-label — jednorázová akce, ne přepínač, takže žádné aria-pressed.",
          "Pruh umístěte v pořadí čtení PŘED obsah, o kterém mluví — odečítač ho má potkat dřív než obrazovku samotnou.",
          "Vypnutá nápověda nekreslí nic, takže nemění rozvržení ani pořadí fokusu. Při omezení pohybu se probliknutí vypne a po dobu zvýraznění zůstane statický rámeček."
        ],
        "en": [
          "The bulb is a plain button with a descriptive aria-label — a one-shot action, not a toggle, so no aria-pressed.",
          "Place the strip BEFORE the content it talks about in reading order — a screen reader should meet it before the screen itself.",
          "A switched-off hint renders nothing, so it changes neither layout nor focus order. Under reduced motion the flash is disabled and a static outline stays for the duration of the highlight."
        ]
      },
      "props": [
        {
          "name": "title",
          "type": "string",
          "required": true
        },
        {
          "name": "children",
          "type": "ReactNode",
          "required": true
        },
        {
          "name": "targets",
          "type": "string[]",
          "required": false
        },
        {
          "name": "level",
          "type": "\"simple\" | \"expert\" | \"both\"",
          "required": false,
          "values": [
            "simple",
            "expert",
            "both"
          ]
        },
        {
          "name": "dismissible",
          "type": "boolean",
          "required": false
        },
        {
          "name": "onDismiss",
          "type": "() => void",
          "required": false
        },
        {
          "name": "visible",
          "type": "boolean",
          "required": false
        },
        {
          "name": "bulbLabel",
          "type": "string",
          "required": false
        },
        {
          "name": "dismissLabel",
          "type": "string",
          "required": false
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/page-hint",
        "en": "https://ingot.forgmatic.com/en/komponenty/page-hint"
      }
    },
    {
      "name": "IngotPageLayout",
      "display": "PageLayout",
      "tag": ".page",
      "status": "beta",
      "version": "2.1",
      "tokens": [
        "--s-5",
        "--s-6",
        "--aside",
        "--page-card",
        "--page-reading",
        "--page-wide"
      ],
      "classNameNote": {
        "cs": "`className` nebere. Rozvržení JE jeho obsahem — sloupce a jejich prahy drží primitivum.",
        "en": "Does not take `className`. Layout IS its content — the primitive holds the columns and their thresholds."
      },
      "summary": {
        "cs": "Rytmus obsahu jedné stránky — mezera mezi bloky, šířka čtení a volitelný postranní rejstřík. Co si dřív každá obrazovka skládala sama.",
        "en": "The rhythm of a page's content — the gap between blocks, a reading width, and an optional side index. What every screen used to assemble by hand."
      },
      "avoidWhen": {
        "cs": [
          "Vnější rám aplikace — šířku 1440 px a odsazení od okrajů drží shell pod horní lištou, ne stránka.",
          "Mřížka karet nebo dvousloupcový blok uvnitř obsahu. To je vnitřek bloku (grid na místě), ne rám stránky — rám nemá vědět, co v něm je.",
          "Boční navigace aplikace. Administrace ji nemá; aside je rejstřík obsahu jedné stránky."
        ],
        "en": [
          "The application's outer frame — the 1440 px width and the edge padding are held by the shell under the top bar, not by the page.",
          "A card grid or a two-column block inside the content. That is the inside of a block (a grid written in place), not the page frame — the frame must not know what is in it.",
          "Application-level side navigation. The admin has none; aside is the index of one page's content."
        ]
      },
      "a11y": {
        "cs": [
          "Layout nekreslí žádné role — orientační body dodávají bloky uvnitř (hlavička, sekce, rejstřík). Rám, který by je zdvojoval, by je znehodnotil.",
          "Rejstřík v aside stojí PŘED obsahem i v DOM, takže klávesnice ho potká první — stejně jako oko.",
          "Na úzké obrazovce je rejstřík v DOM právě jednou. Vykreslit obě podoby a jednu schovat breakpointem by znamenalo každý odkaz v dokumentu dvakrát: odečítač přečte oba a každé id uvnitř se srazí samo se sebou."
        ],
        "en": [
          "The layout draws no roles — the landmarks come from the blocks inside (header, sections, index). A frame duplicating them would devalue them.",
          "The index in aside comes before the content in the DOM too, so the keyboard meets it first — same as the eye.",
          "On a narrow screen the index is in the DOM exactly once. Rendering both shapes and hiding one with a breakpoint would put every link in the document twice: a screen reader reads both, and every id inside them collides with itself."
        ]
      },
      "props": [
        {
          "name": "width",
          "type": "\"full\" | \"wide\" | \"reading\" | \"card\"",
          "required": false,
          "values": [
            "full",
            "wide",
            "reading",
            "card"
          ]
        },
        {
          "name": "aside",
          "type": "ReactNode",
          "required": false
        },
        {
          "name": "asideLabel",
          "type": "string",
          "required": false
        },
        {
          "name": "children",
          "type": "ReactNode",
          "required": true
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/page-layout",
        "en": "https://ingot.forgmatic.com/en/komponenty/page-layout"
      }
    },
    {
      "name": "IngotPagination",
      "display": "Pagination",
      "tag": ".pager",
      "status": "beta",
      "version": "1.0",
      "tokens": [
        "--ink-3"
      ],
      "classNameNote": {
        "cs": "Bere `className`, ale jen na rozvržení — šířku, mezery, umístění v mřížce. Vzhled drží primitivum.",
        "en": "Takes `className`, but for layout only — width, spacing, placement in a grid. The look stays with the primitive."
      },
      "summary": {
        "cs": "Stránkování pod tabulkou. Řízené volajícím — s tabulkou se nepře o to, kdo drží stav stránky.",
        "en": "Pagination under a table. Controlled by the caller — it never fights the table over who owns the page state."
      },
      "avoidWhen": {
        "cs": [
          "Řádků je pár desítek a vejdou se do jednoho scrollboxu — sticky hlavička tabulky je pak jednodušší než stav stránky navíc.",
          "Potřebuješ číslované stránky nebo „načíst další\". Tenhle tvar je záměrně jen předchozí / další; jiný tvar znamená rozšířit primitivum, ne ho obcházet."
        ],
        "en": [
          "There are only a few dozen rows and they fit one scroll box — the table's sticky header is then simpler than extra page state.",
          "You need numbered pages or \"load more\". This shape is deliberately previous / next only; a different shape means extending the primitive, not working around it."
        ]
      },
      "a11y": {
        "cs": [
          "Pager je <nav> — orientační bod, na který odečítač umí skočit. S label se dva pagery na obrazovce nespletou.",
          "Krajní tlačítka se vypínají: na první straně nejde „předchozí“, na poslední „další“. onPageChange se tak nikdy nezavolá mimo rozsah.",
          "Stav se neohlašuje živě: mění se jen po kliknutí a fokus zůstává na tlačítku — hlášení navíc by rušilo."
        ],
        "en": [
          "The pager is a <nav> — a landmark a screen reader can jump to. With label, two pagers on one screen cannot be confused.",
          "The edge buttons disable themselves: no \"previous\" on the first page, no \"next\" on the last. onPageChange is therefore never called out of range.",
          "The status is not announced live: it only changes after a click and focus stays on the button — an extra announcement would be noise."
        ]
      },
      "props": [
        {
          "name": "page",
          "type": "number",
          "required": true
        },
        {
          "name": "pageCount",
          "type": "number",
          "required": true
        },
        {
          "name": "onPageChange",
          "type": "(page: number) => void",
          "required": true
        },
        {
          "name": "prevLabel",
          "type": "string",
          "required": true
        },
        {
          "name": "nextLabel",
          "type": "string",
          "required": true
        },
        {
          "name": "status",
          "type": "ReactNode",
          "required": false
        },
        {
          "name": "label",
          "type": "string",
          "required": false
        },
        {
          "name": "className",
          "type": "string",
          "required": false
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/pagination",
        "en": "https://ingot.forgmatic.com/en/komponenty/pagination"
      }
    },
    {
      "name": "IngotPopover",
      "display": "Popover",
      "tag": ".popover",
      "status": "beta",
      "version": "1.2",
      "tokens": [
        "--surface",
        "--border",
        "--r-lg",
        "--shadow-lg"
      ],
      "classNameNote": {
        "cs": "Bere `className`, ale jen na rozvržení panelu — šířku a vnitřní odsazení. Rám, stín a vrstvu drží primitivum.",
        "en": "Takes `className`, but for the panel's layout only — width and inner padding. The frame, the shadow and the layer stay with the primitive."
      },
      "summary": {
        "cs": "Panel ukotvený k prvku, který ho otevřel: pozice, klik mimo, Escape a návrat fokusu na jednom místě.",
        "en": "A panel anchored to what opened it: position, click outside, Escape and focus return, all in one place."
      },
      "avoidWhen": {
        "cs": [
          "Obsah vyžaduje soustředění na jednu věc a potvrzení — to je IngotModal. Popover fokus schválně nezavírá dovnitř, takže se z něj Tabem odejde.",
          "Editace, u které se pracuje se seznamem za ní — to je IngotDrawer.",
          "Krátký popisek ovládacího prvku. Na to je IngotTooltip; popover je pro obsah, se kterým se pracuje."
        ],
        "en": [
          "The content needs focus on one thing and a confirmation — that is IngotModal. A popover deliberately does not trap focus, so Tab leaves it.",
          "An edit made while working with the list behind it — that is IngotDrawer.",
          "A short description of a control. That is IngotTooltip; a popover is for content you work with."
        ]
      },
      "a11y": {
        "cs": [
          "Panel je pojmenovaná skupina (role=\"group\" + aria-label). Kotva má nést aria-expanded — ta patří jí, ne panelu.",
          "Fokus se schválně nezavírá dovnitř. Popover není dialog: Tab z něj má vést dál po stránce. Past fokusu v panelu s filtry je nejrychlejší způsob, jak uživatele klávesnice uvěznit.",
          "Escape zavře a vrátí fokus na kotvu. Bez návratu se čtenář probere na začátku stránky."
        ],
        "en": [
          "The panel is a named group (role=\"group\" + aria-label). The anchor carries aria-expanded — that belongs to it, not to the panel.",
          "Focus is deliberately not trapped. A popover is not a dialog: Tab is meant to leave it and carry on through the page. Trapping focus in a filter panel is the fastest way to strand a keyboard user.",
          "Escape closes it and returns focus to the anchor. Without the return the reader wakes up at the top of the page."
        ]
      },
      "props": [
        {
          "name": "open",
          "type": "boolean",
          "required": true
        },
        {
          "name": "anchorRef",
          "type": "RefObject<HTMLElement | null>",
          "required": true
        },
        {
          "name": "onClose",
          "type": "() => void",
          "required": true
        },
        {
          "name": "placement",
          "type": "\"bottom-start\" | \"bottom-end\" | \"top-start\" | \"top-end\"",
          "required": false,
          "values": [
            "bottom-start",
            "bottom-end",
            "top-start",
            "top-end"
          ]
        },
        {
          "name": "label",
          "type": "string",
          "required": true
        },
        {
          "name": "className",
          "type": "string",
          "required": false
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/popover",
        "en": "https://ingot.forgmatic.com/en/komponenty/popover"
      }
    },
    {
      "name": "IngotProvider",
      "display": "Provider",
      "tag": ".provider",
      "status": "beta",
      "version": "1.1",
      "tokens": [],
      "classNameNote": {
        "cs": "`className` nebere — nic nevykresluje, jen nese slovník popisků.",
        "en": "Does not take `className` — it renders nothing, it only carries the label dictionary."
      },
      "summary": {
        "cs": "Slovník několika popisků, které kit říká sám — zpět, žárovka nápovědy, tajné pole. Bez providera anglicky; lang=\"cs\" přepne celou sadu.",
        "en": "The dictionary of the few labels the kit says itself — undo, the hint bulb, the secret field. English without a provider; lang=\"cs\" switches the whole set."
      },
      "avoidWhen": {
        "cs": [
          "Text stránky, tlačítek, nadpisů — to je obsah, který dodává volající přeložený. Provider nese jen popisky, které by jinak řekl kit.",
          "Jedna komponenta má mluvit jinak než zbytek stránky: použij její vlastní prop (undoLabel, bulbLabel, secretPlaceholder) — ten má před providerem vždy přednost."
        ],
        "en": [
          "Page text, buttons, headings — that is content the caller supplies translated. The provider carries only the labels the kit would otherwise say itself.",
          "One component should speak differently from the rest of the page: use its own prop (undoLabel, bulbLabel, secretPlaceholder) — it always wins over the provider."
        ]
      },
      "a11y": {
        "cs": [
          "Většina popisků ve slovníku jsou aria-label ikonových tlačítek — text, který vidí jen odečítač. Právě proto nesmí zůstat v cizím jazyce: nikdo si toho na obrazovce nevšimne.",
          "Provider nic nevykresluje a nemění strom — žádná role, žádný obal."
        ],
        "en": [
          "Most labels in the dictionary are the aria-label of icon buttons — text only a screen reader sees. That is exactly why they must not stay in a foreign language: nobody notices on screen.",
          "The provider renders nothing and changes no tree — no role, no wrapper."
        ]
      },
      "props": [
        {
          "name": "lang",
          "type": "\"cs\" | \"en\"",
          "required": false,
          "values": [
            "cs",
            "en"
          ]
        },
        {
          "name": "labels",
          "type": "Partial<IngotLabels>",
          "required": false
        },
        {
          "name": "children",
          "type": "ReactNode",
          "required": true
        }
      ],
      "extraProps": [
        {
          "name": "IngotLabels",
          "props": [
            {
              "name": "toastUndo",
              "type": "string",
              "required": true
            },
            {
              "name": "toastClose",
              "type": "string",
              "required": true
            },
            {
              "name": "pageHintBulb",
              "type": "string",
              "required": true
            },
            {
              "name": "pageHintDismiss",
              "type": "string",
              "required": true
            },
            {
              "name": "secretSet",
              "type": "string",
              "required": true
            },
            {
              "name": "secretUnset",
              "type": "string",
              "required": true
            }
          ]
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/provider",
        "en": "https://ingot.forgmatic.com/en/komponenty/provider"
      }
    },
    {
      "name": "IngotRadioGroup",
      "display": "RadioGroup",
      "tag": ".radiogroup",
      "status": "beta",
      "version": "1.1",
      "tokens": [
        "--accent",
        "--ink",
        "--ink-2",
        "--ink-3",
        "--ink-4",
        "--danger"
      ],
      "classNameNote": {
        "cs": "Bere `className`, ale jen na rozvržení — šířku a odsazení skupiny. Podobu voleb drží primitivum.",
        "en": "Takes `className`, but for layout only — the group's width and margins. The options' shape stays with the primitive."
      },
      "summary": {
        "cs": "Jedna volba z několika, všechny vidět naráz. Nativní skupina, takže šipky i odesílání formuláře jedou samy.",
        "en": "One choice out of several, all visible at once. A native group, so the arrows and form submission work by themselves."
      },
      "avoidWhen": {
        "cs": [
          "Voleb je hodně nebo se mění za běhu — to je IngotSelect; rozbalený seznam dvaceti přepínačů je stěna.",
          "Volby jsou dvě tři krátká slova a mají stát v liště — to je IngotSegmented.",
          "Každá volba potřebuje odstavec, obrázek nebo cenu — to je IngotOptionCard."
        ],
        "en": [
          "There are many options or they change at runtime — that is IngotSelect; twenty radios laid out is a wall.",
          "The options are two or three short words meant to sit in a bar — that is IngotSegmented.",
          "Each option needs a paragraph, an image or a price — that is IngotOptionCard."
        ]
      },
      "a11y": {
        "cs": [
          "Skupina je fieldset s legendou, takže její jméno patří ke skupině i ve stromu přístupnosti, ne jen opticky nad ní.",
          "Nativní radia: šipky se pohybují a rovnou vybírají, skupina je jedna zastávka Tabu.",
          "Nápověda volby se váže přes aria-describedby, takže se čte s ní, ne někde vedle."
        ],
        "en": [
          "The group is a fieldset with a legend, so its name belongs to the group in the accessibility tree, not merely above it on screen.",
          "Native radios: arrows move and select as they go, and the group is one tab stop.",
          "An option's hint is bound with aria-describedby, so it is read with the option rather than somewhere beside it."
        ]
      },
      "props": [
        {
          "name": "value",
          "type": "string",
          "required": true
        },
        {
          "name": "onChange",
          "type": "(next: string) => void",
          "required": true
        },
        {
          "name": "options",
          "type": "readonly IngotRadioOption[]",
          "required": true
        },
        {
          "name": "label",
          "type": "ReactNode",
          "required": true
        },
        {
          "name": "hint",
          "type": "ReactNode",
          "required": false
        },
        {
          "name": "error",
          "type": "ReactNode",
          "required": false
        },
        {
          "name": "disabled",
          "type": "boolean",
          "required": false
        },
        {
          "name": "className",
          "type": "string",
          "required": false
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "extraProps": [
        {
          "name": "IngotRadioOption",
          "props": [
            {
              "name": "value",
              "type": "string",
              "required": true
            },
            {
              "name": "label",
              "type": "ReactNode",
              "required": true
            },
            {
              "name": "hint",
              "type": "ReactNode",
              "required": false
            },
            {
              "name": "disabled",
              "type": "boolean",
              "required": false
            },
            {
              "name": "testId",
              "type": "string",
              "required": false
            }
          ]
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/radio-group",
        "en": "https://ingot.forgmatic.com/en/komponenty/radio-group"
      }
    },
    {
      "name": "IngotRowActions",
      "display": "RowActions",
      "tag": ".rowactions",
      "status": "stable",
      "version": "1.4",
      "tokens": [
        "--surface-2",
        "--ink",
        "--ink-3",
        "--danger",
        "--danger-bg",
        "--accent-bg",
        "--r-sm"
      ],
      "classNameNote": {
        "cs": "`className` nebere. Vypadá stejně na každé obrazovce; rozvržení patří obalu kolem něj.",
        "en": "Does not take `className`. It looks the same on every screen; layout belongs to the wrapper around it."
      },
      "summary": {
        "cs": "Akce jednoho řádku tabulky — ikonová tlačítka 28×28 px na konci řádku, bez rámečku a bez viditelného popisku.",
        "en": "The actions of a single table row — 28×28 px icon buttons at the end of the row, with no frame and no visible label."
      },
      "avoidWhen": {
        "cs": [
          "Hlavní akce obrazovky. „Nová objednávka“ v hlavičce je IngotButton s popiskem — tenhle rozpočet platí jen v řádku, kde se opakuje dvacetkrát.",
          "Čtyři a víc akcí. Řada ikon bez popisků se přestane číst a jediné rozlišení je tvar piktogramu; přesuň je do menu.",
          "Samotné mazání. Nevratná akce má tón danger, ale sama nemaže — potvrzení řeší IngotConfirm u volajícího. Tón mění jen to, jak tlačítko vypadá při najetí."
        ],
        "en": [
          "The primary action of a screen. “New order” in a header is an IngotButton with a label — this budget only applies in a row, where it repeats twenty times.",
          "Four or more actions. A row of unlabelled icons stops being read and the only distinction left is the shape of the glyph; move them into a menu.",
          "Deleting on its own. An irreversible action carries the danger tone, but it does not delete by itself — confirmation is handled by IngotConfirm at the caller. The tone only changes how the button looks on hover."
        ]
      },
      "a11y": {
        "cs": [
          "Popisek je povinný a začíná slovesem. Bez něj odečítač přečte jen „tlačítko“, dvacetkrát pod sebou — proto „Smazat objednávku“, ne „Koš“.",
          "Týž popisek jde do aria-label i do title, takže se stejná věta ukáže v bublině i ohlásí odečítači. Jedna pravda, dvě cesty k ní.",
          "Pevné pořadí je přístupnost, ne estetika. Přeházené pořadí je nejrychlejší cesta ke smazání špatného řádku — pro myš i pro klávesnici.",
          "Zakázané tlačítko zůstává v pořadí i ve výpisu odečítače, jen ztlumené a nepoužitelné; kurzor nad ním hlásí, že akci tenhle řádek nedovoluje."
        ],
        "en": [
          "The label is required and starts with a verb. Without it a screen reader announces just “button”, twenty times in a row — so “Delete order”, not “Trash”.",
          "The same label goes into aria-label and title, so the same sentence appears in the tooltip and is announced to a screen reader. One truth, two routes to it.",
          "The fixed order is accessibility, not aesthetics. A reshuffled order is the fastest route to deleting the wrong row — for the mouse and for the keyboard alike.",
          "A disabled button keeps its place in the order and in the screen reader's listing, only dimmed and inert; the cursor over it says this row does not allow the action."
        ]
      },
      "props": [
        {
          "name": "actions",
          "type": "readonly IngotRowAction[]",
          "required": true
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "extraProps": [
        {
          "name": "IngotRowAction",
          "props": [
            {
              "name": "icon",
              "type": "IngotIconName",
              "required": true
            },
            {
              "name": "label",
              "type": "string",
              "required": true
            },
            {
              "name": "onClick",
              "type": "() => void",
              "required": true
            },
            {
              "name": "tone",
              "type": "Extract<IngotTone, \"neutral\" | \"danger\">",
              "required": false
            },
            {
              "name": "disabled",
              "type": "boolean",
              "required": false
            },
            {
              "name": "testId",
              "type": "string",
              "required": false
            }
          ]
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/row-actions",
        "en": "https://ingot.forgmatic.com/en/komponenty/row-actions"
      }
    },
    {
      "name": "IngotSearchInput",
      "display": "SearchInput",
      "tag": ".search",
      "status": "beta",
      "version": "1.6",
      "tokens": [
        "--surface",
        "--surface-2",
        "--border-strong",
        "--ink",
        "--ink-4",
        "--accent",
        "--accent-bg",
        "--r-md",
        "--shadow-sm"
      ],
      "classNameNote": {
        "cs": "Bere `className`, ale jen na rozvržení — šířku, mezery, umístění v mřížce. Vzhled drží primitivum.",
        "en": "Takes `className`, but for layout only — width, spacing, placement in a grid. The look stays with the primitive."
      },
      "summary": {
        "cs": "Hledací pole nad seznamem — první prvek filtr baru. Filtruje, nevyhledává: zužuje seznam každým úhozem, žádné tlačítko Hledat.",
        "en": "A search field above a list — the first element of the filter bar. It filters, it does not search: it narrows the list on every keystroke, no Search button."
      },
      "avoidWhen": {
        "cs": [
          "Hledání přes celý produkt z horní lišty. Takové v produktu není — a kdyby vzniklo, je to vlastní primitivum s výsledky, ne pole nad jedním seznamem.",
          "Pole formuláře, do kterého se zapisuje hodnota. Tohle pole nic neukládá — jen zužuje, co je vidět."
        ],
        "en": [
          "Product-wide search from the top bar. The product has none — and if one appears, it is its own primitive with results, not a field above a single list.",
          "A form field that stores a value. This field saves nothing — it only narrows what is visible."
        ]
      },
      "a11y": {
        "cs": [
          "Lupa je dekorace (aria-hidden); jméno pole nese label. Ikona beze jména by odečítači řekla „obrázek“, ne „hledání“.",
          "type=\"search\" dává prohlížečový křížek na vymazání a odečítači roli hledacího pole zadarmo.",
          "Klávesová zkratka, která do hledání skáče, si pole vezme přes ref — ne hledáním <input> uvnitř obalu. Vnitřek primitiva se smí přejmenovat, vlastnost ne.",
          "Pole, které řídí vlastní seznam výsledků, dostane combobox. Šipky v takovém hledání hýbou zvýrazněním, ne fokusem — kurzor musí zůstat v poli, aby další písmeno šlo tam, kam čtenář čeká. Odečítač o tom ale neví, dokud se to neřekne nahlas: aria-activedescendant jde za zvýrazněním a položky nesou role=\"option\" uvnitř role=\"listbox\"."
        ],
        "en": [
          "The magnifier is decoration (aria-hidden); the field's name is carried by label. A nameless icon tells a screen reader “image”, not “search”.",
          "type=\"search\" gives the browser's clear cross and the search-field role for free.",
          "A keyboard shortcut that jumps into search takes the field through ref — not by looking for the <input> inside the wrapper. The inside of a primitive may be renamed; a prop may not.",
          "A field that drives its own result list takes combobox. The arrows in such a search move a highlight, not the focus — the caret has to stay in the field so the next letter goes where the reader expects. A screen reader cannot see that unless it is said out loud: aria-activedescendant follows the highlight and the items carry role=\"option\" inside a role=\"listbox\"."
        ]
      },
      "props": [
        {
          "name": "value",
          "type": "string",
          "required": true
        },
        {
          "name": "onChange",
          "type": "(next: string) => void",
          "required": true
        },
        {
          "name": "label",
          "type": "string",
          "required": true
        },
        {
          "name": "placeholder",
          "type": "string",
          "required": false
        },
        {
          "name": "disabled",
          "type": "boolean",
          "required": false
        },
        {
          "name": "ref",
          "type": "React.Ref<HTMLInputElement>",
          "required": false
        },
        {
          "name": "combobox",
          "type": "{ controls: string; expanded: boolean; activeOption?: string | null }",
          "required": false
        },
        {
          "name": "className",
          "type": "string",
          "required": false
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/search-input",
        "en": "https://ingot.forgmatic.com/en/komponenty/search-input"
      }
    },
    {
      "name": "IngotSection",
      "display": "Section",
      "tag": ".section",
      "status": "stable",
      "version": "1.0",
      "tokens": [
        "--ink"
      ],
      "classNameNote": {
        "cs": "`className` nebere. Sekce drží osnovu stránky; odsazení mezi sekcemi patří rámu stránky.",
        "en": "Does not take `className`. The section holds the page outline; spacing between sections belongs to the page frame."
      },
      "summary": {
        "cs": "Sekce obrazovky: nadpis správné úrovně a kotva, která sedí na sekci, ne na nadpisu.",
        "en": "A screen section: a heading at the right level and an anchor that sits on the section, not on the heading."
      },
      "avoidWhen": {
        "cs": [
          "Chceš jen tučný řádek nad odstavcem. Nadpis, který nic nestrukturuje, odečítači lže o osnově stránky — a osnova je jedna z mála věcí, které se čtou naslepo.",
          "Je to nadpis celé obrazovky. Na to je IngotPageHeader s <h1>."
        ],
        "en": [
          "You only want a bold line above a paragraph. A heading that structures nothing lies to a screen reader about the page outline — and the outline is one of the few things read blind.",
          "It is the title of the whole screen. That is IngotPageHeader, with an <h1>."
        ]
      },
      "a11y": {
        "cs": [
          "level je o osnově, ne o vzhledu. Přeskočená úroveň (z h1 rovnou na h3) rozbije seznam nadpisů, podle kterého se po stránce pohybuje kdokoli, kdo ji nevidí.",
          "Vykresluje se <section>, takže je to skutečná část dokumentu — ne <div>, který jen vypadá odděleně.",
          "id patří na sekci. Kotva na nadpisu odroluje tak, že nadpis zmizí nad okrajem okna a čtenář začne uprostřed odstavce."
        ],
        "en": [
          "level is about the outline, not the look. A skipped level (h1 straight to h3) breaks the heading list anyone who cannot see the page moves through it by.",
          "It renders a <section>, so it really is a part of the document — not a <div> that merely looks separate.",
          "id belongs on the section. An anchor on the heading scrolls so that the heading disappears past the top of the window and the reader starts mid-paragraph."
        ]
      },
      "props": [
        {
          "name": "title",
          "type": "ReactNode",
          "required": true
        },
        {
          "name": "children",
          "type": "ReactNode",
          "required": true
        },
        {
          "name": "id",
          "type": "string",
          "required": false
        },
        {
          "name": "level",
          "type": "2 | 3",
          "required": false
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/section",
        "en": "https://ingot.forgmatic.com/en/komponenty/section"
      }
    },
    {
      "name": "IngotSegmented",
      "display": "Segmented",
      "tag": ".seg",
      "status": "beta",
      "version": "1.2",
      "tokens": [
        "--border",
        "--surface",
        "--surface-2",
        "--ink",
        "--ink-3"
      ],
      "classNameNote": {
        "cs": "`className` nebere. Vypadá stejně na každé obrazovce; rozvržení patří obalu kolem něj.",
        "en": "Does not take `className`. It looks the same on every screen; layout belongs to the wrapper around it."
      },
      "summary": {
        "cs": "Přepínač lišty — dvě až tři krátké volby vedle sebe, vybraná vystoupí na plochu. Volba se projeví hned.",
        "en": "A top-bar switch — two or three short choices side by side, the selected one lifted onto the surface. The choice applies at once."
      },
      "avoidWhen": {
        "cs": [
          "Přepínají se pohledy na obsah stránky. To jsou záložky — IngotTabs; ty patří k obsahu pod nimi, tenhle přepínač k liště nad ním.",
          "Voleb je víc než tři nebo jsou dlouhé. Pak je to IngotSelect — přepínač se jinak roztáhne přes půl lišty.",
          "Volí se barva. Na to je IngotAccentSwatches — název rodiny neřekne, jak bude obrazovka vypadat."
        ],
        "en": [
          "Views of the page content are being switched. Those are tabs — IngotTabs; they belong to the content below them, this switch to the bar above it.",
          "There are more than three choices, or they are long. Then it is IngotSelect — otherwise the switch spreads across half the bar.",
          "A colour is being chosen. That is IngotAccentSwatches — a family name does not say how the screen will look."
        ]
      },
      "a11y": {
        "cs": [
          "Volby jsou přepínače v pojmenované skupině, ne tlačítka: odečítač hlásí „vybráno“ i to, kolikátá volba z kolika to je.",
          "Skupina je jedna zastávka tabulátoru — vstoupí se do ní na vybranou volbu a šipky, Home a End mezi volbami přepínají. Tabulátor tak lištu přeskočí jedním stiskem, ne třemi.",
          "Vybraná volba se nepozná jen barvou textu — vystoupí na plochu se stínem, takže rozdíl drží i bez vnímání barvy."
        ],
        "en": [
          "The choices are radios in a named group, not buttons: a screen reader announces “selected”, and which choice of how many this is.",
          "The group is a single tab stop — it is entered at the selected choice, and arrows, Home and End switch between the choices. One key press walks past the bar, not three.",
          "The selected choice is not marked by text colour alone — it lifts onto the surface with a shadow, so the difference holds without colour perception."
        ]
      },
      "props": [
        {
          "name": "options",
          "type": "readonly IngotSegmentedOption[]",
          "required": true
        },
        {
          "name": "value",
          "type": "string",
          "required": true
        },
        {
          "name": "onChange",
          "type": "(value: string) => void",
          "required": true
        },
        {
          "name": "label",
          "type": "string",
          "required": true
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "extraProps": [
        {
          "name": "IngotSegmentedOption",
          "props": [
            {
              "name": "value",
              "type": "string",
              "required": true
            },
            {
              "name": "label",
              "type": "string",
              "required": true
            }
          ]
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/segmented",
        "en": "https://ingot.forgmatic.com/en/komponenty/segmented"
      }
    },
    {
      "name": "IngotSelect",
      "display": "Select",
      "tag": ".select",
      "status": "beta",
      "version": "1.4",
      "tokens": [
        "--surface",
        "--surface-2",
        "--border-strong",
        "--ink",
        "--ink-4",
        "--accent",
        "--accent-bg",
        "--r-md",
        "--shadow-sm"
      ],
      "classNameNote": {
        "cs": "Bere `className`, ale jen na rozvržení — šířku, mezery, umístění v mřížce. Vzhled drží primitivum.",
        "en": "Takes `className`, but for layout only — width, spacing, placement in a grid. The look stays with the primitive."
      },
      "summary": {
        "cs": "Výběr jedné hodnoty z krátké množiny — filtr nad seznamem, přepínač varianty v nastavení. Nativní select: klávesnice, odečítač i mobil zadarmo.",
        "en": "Picking one value from a short set — a filter above a list, a variant switch in settings. A native select: keyboard, screen reader and mobile behaviour for free."
      },
      "avoidWhen": {
        "cs": [
          "Množina, ve které se hledá — desítky materiálů, stovky kontaktů. Nativní select se v ní nedá prohledávat; takové pole si řekne o vlastní primitivum s hledáním, až bude mít žadatele.",
          "Výběr více hodnot najednou. Na sadu nezávislých přepínačů je IngotCheckbox.",
          "Přepnutí mezi dvěma až třemi pohledy na tutéž obrazovku — to jsou IngotTabs, ne formulářový prvek."
        ],
        "en": [
          "A set you search in — dozens of materials, hundreds of contacts. A native select cannot be searched; such a field asks for its own primitive with search, once it has a requester.",
          "Picking several values at once. A row of independent toggles is IngotCheckbox.",
          "Switching between two or three views of the same screen — that is IngotTabs, not a form control."
        ]
      },
      "a11y": {
        "cs": [
          "Je to nativní select: šipky, psaní počátečních písmen, odečítač i mobilní kolečko fungují bez jediného řádku navíc — a proto se nenahrazuje vlastním rozbalovacím seznamem.",
          "Jméno nese label (aria-label), ne první volba. „Všechny stavy“ říká, jaká je hodnota, ne čeho je to filtr."
        ],
        "en": [
          "It is a native select: arrows, type-ahead, screen readers and the mobile wheel work without a single extra line — which is exactly why it is not replaced by a custom dropdown.",
          "The name is carried by label (aria-label), not by the first option. “All statuses” says what the value is, not what the filter is of."
        ]
      },
      "props": [
        {
          "name": "value",
          "type": "string",
          "required": true
        },
        {
          "name": "onChange",
          "type": "(next: string) => void",
          "required": true
        },
        {
          "name": "options",
          "type": "readonly IngotSelectOption[]",
          "required": true
        },
        {
          "name": "label",
          "type": "string",
          "required": true
        },
        {
          "name": "disabled",
          "type": "boolean",
          "required": false
        },
        {
          "name": "id",
          "type": "string",
          "required": false
        },
        {
          "name": "className",
          "type": "string",
          "required": false
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "extraProps": [
        {
          "name": "IngotSelectOption",
          "props": [
            {
              "name": "value",
              "type": "string",
              "required": true
            },
            {
              "name": "label",
              "type": "string",
              "required": true
            }
          ]
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/select",
        "en": "https://ingot.forgmatic.com/en/komponenty/select"
      }
    },
    {
      "name": "IngotSideNav",
      "display": "SideNav",
      "tag": ".sidenav",
      "status": "stable",
      "version": "1.4",
      "tokens": [
        "--surface",
        "--surface-2",
        "--border",
        "--ink",
        "--ink-2",
        "--ink-3",
        "--ink-4",
        "--font-mono",
        "--r-sm",
        "--shadow-sm"
      ],
      "classNameNote": {
        "cs": "`className` nebere. Šířku a přilnutí menu drží sloupec kolem něj, ne položky uvnitř.",
        "en": "Does not take `className`. The column around it holds the menu's width and stickiness, not the items inside."
      },
      "summary": {
        "cs": "Pojmenovaná skupina odkazů, jeden z nich aktivní. Popisek navigace a aria-current drží primitivum, ne domluva.",
        "en": "A named group of links with one of them active. The primitive owns the navigation label and aria-current — not an agreement to remember them."
      },
      "avoidWhen": {
        "cs": [
          "Položky nic nepřepínají, jen se vypisují. Pak je to seznam — IngotList s variant=\"plain\".",
          "Menu má vlastní chování navíc: skládání skupin, ikony, přetečení do „další\". Tohle primitivum je záměrně holé; složitější menu ať si drží obrazovka, dokud se pro ně nenajde druhý žadatel.",
          "Položka spouští akci, ne navigaci. Odkaz, který nikam nevede, je tlačítko — Button."
        ],
        "en": [
          "The items switch nothing and are only listed. Then it is a list — IngotList with variant=\"plain\".",
          "The menu needs extra behaviour: collapsible groups, icons, overflow into a \"more\" control. This primitive is deliberately bare; a richer menu should stay with the screen until a second caller asks for it.",
          "The item triggers an action, not navigation. A link that goes nowhere is a button — Button."
        ]
      },
      "a11y": {
        "cs": [
          "label je povinný schválně. Na stránce s víc navigacemi je aria-label to jediné, čím je odečítač rozezná — jinak uživatel slyší „navigace\" dvakrát a neví, která je která.",
          "Aktivní položka nese aria-current=\"page\", ne jen jinou barvu. Zvýraznění barvou je informace, kterou odečítač nevidí.",
          "Odkaz je <a href>, ne <div onClick>. Klávesnice, prostřední tlačítko myši i „otevřít v novém panelu\" pak fungují samy a nikdo je nemusí dodělávat."
        ],
        "en": [
          "label is required on purpose. On a page with several navigations, aria-label is the only thing that tells them apart — otherwise the user hears \"navigation\" twice and cannot tell which is which.",
          "The active item carries aria-current=\"page\", not just a different colour. Colour is information a screen reader cannot see.",
          "A link is an <a href>, not a <div onClick>. Keyboard, middle-click and \"open in a new tab\" then work by themselves and nobody has to add them."
        ]
      },
      "props": [
        {
          "name": "label",
          "type": "string",
          "required": true
        },
        {
          "name": "items",
          "type": "readonly IngotNavItem[]",
          "required": true
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "extraProps": [
        {
          "name": "IngotNavItem",
          "props": [
            {
              "name": "href",
              "type": "string",
              "required": true
            },
            {
              "name": "label",
              "type": "ReactNode",
              "required": true
            },
            {
              "name": "current",
              "type": "boolean",
              "required": false
            },
            {
              "name": "testId",
              "type": "string",
              "required": false
            }
          ]
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/side-nav",
        "en": "https://ingot.forgmatic.com/en/komponenty/side-nav"
      }
    },
    {
      "name": "IngotSkeleton",
      "display": "Skeleton",
      "tag": ".skeleton",
      "status": "beta",
      "version": "1.0",
      "tokens": [
        "--surface",
        "--surface-2",
        "--surface-3",
        "--border",
        "--r-sm",
        "--r-md"
      ],
      "classNameNote": {
        "cs": "Bere `className`, ale jen na umístění a šířku bloku. Barvy ani rytmus pulzu ne — kostra, která na každé obrazovce bliká jinak, přestane být poznávacím znamením načítání.",
        "en": "Takes `className`, but only for the block's placement and width. Not the colours or the rhythm of the pulse — a skeleton that blinks differently on every screen stops being recognisable as loading."
      },
      "summary": {
        "cs": "Tvar toho, co se načítá, dokud se to načítá. Drží místo, takže po dojití dat nic neposkočí.",
        "en": "The shape of what is coming, while it is still coming. It holds the room, so nothing jumps when the data lands."
      },
      "avoidWhen": {
        "cs": [
          "Tvar výsledku neznáš. Pak je to IngotSpinner — kostra, která drží místo pro něco jiného, než co přijde, poskočí stejně jako prázdno.",
          "Načtení je hotové v jednom snímku. Kostra, která blikne a zmizí, čte se jako závada.",
          "Nic nedorazí, protože tam nic není. To je IngotEmptyState: prázdný stav je odpověď, ne čekání."
        ],
        "en": [
          "You do not know the shape of the answer. Then it is IngotSpinner — a skeleton holding room for something other than what arrives jumps exactly as the blank did.",
          "The load is done within a frame. A skeleton that flashes and vanishes reads as a fault.",
          "Nothing is coming, because there is nothing there. That is IngotEmptyState: an empty state is an answer, not a wait."
        ]
      },
      "a11y": {
        "cs": [
          "Blok je role=\"status\" s aria-busy a se jménem od volajícího. Proužky jsou aria-hidden: bez toho by odečítač prošel třicet šedých obdélníků a třicetkrát neřekl nic.",
          "Pulz se pod prefers-reduced-motion zastaví. Kostra zůstane — je to informace o tvaru, ne animace; vypnutý pohyb neznamená vypnutou zprávu.",
          "Kostra nenese kontrast textu, protože žádný text nemá. --surface-3 je záměrně tichý: je to podklad, který za chvíli zmizí, ne obsah, který se má číst."
        ],
        "en": [
          "The block is a role=\"status\" with aria-busy and the caller's name on it. The bars are aria-hidden: without that a screen reader would walk thirty grey rectangles and announce nothing thirty times.",
          "The pulse stops under prefers-reduced-motion. The skeleton stays — it is information about shape, not an animation; movement switched off does not mean the message switched off.",
          "A skeleton carries no text contrast, because it has no text. --surface-3 is deliberately quiet: it is a placeholder about to disappear, not content to be read."
        ]
      },
      "props": [
        {
          "name": "label",
          "type": "string",
          "required": true
        },
        {
          "name": "shape",
          "type": "\"text\" | \"card\" | \"table\" | \"metrics\"",
          "required": false,
          "values": [
            "text",
            "card",
            "table",
            "metrics"
          ]
        },
        {
          "name": "rows",
          "type": "number",
          "required": false
        },
        {
          "name": "className",
          "type": "string",
          "required": false
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/skeleton",
        "en": "https://ingot.forgmatic.com/en/komponenty/skeleton"
      }
    },
    {
      "name": "IngotSpinner",
      "display": "Spinner",
      "tag": ".spinner",
      "status": "beta",
      "version": "1.1",
      "tokens": [
        "--accent",
        "--dur",
        "--ease"
      ],
      "classNameNote": {
        "cs": "Bere `className`, ale jen na umístění — okraje, zarovnání. Velikost je `size`, barva je akcent: prstenec v barvě obrazovky přestane být tím, co člověk pozná jako „pracuje se“.",
        "en": "Takes `className`, but only for placement — margins, alignment. The size is `size` and the colour is the accent: a ring in a screen's own colour stops being the thing people recognise as work in progress."
      },
      "summary": {
        "cs": "Něco se děje a není co držet za tvar. Druhá volba po kostře — a jméno je povinné.",
        "en": "Something is happening and there is no shape to hold. The second choice after a skeleton — and the name is required."
      },
      "avoidWhen": {
        "cs": [
          "Tvar toho, co přijde, je známý. Pak IngotSkeleton říká víc a hlavně drží místo, takže po dojití dat nic neposkočí.",
          "Čekání je delší než pár vteřin. Točící se kolečko po půl minutě neříká „pracuje se“, ale „zaseklo se“ — takové čekání potřebuje průběh nebo větu.",
          "Načtení celé stránky. Prázdná stránka s kolečkem uprostřed je horší než stránka, která ukáže svůj tvar a doplní se."
        ],
        "en": [
          "The shape of what is coming is known. Then IngotSkeleton says more and, above all, holds the room — so nothing jumps when the data lands.",
          "The wait is longer than a few seconds. After half a minute a spinning circle no longer says \"working\" but \"stuck\" — a wait like that needs progress or a sentence.",
          "A whole page loading. A blank page with a circle in the middle is worse than a page that shows its shape and fills in."
        ]
      },
      "a11y": {
        "cs": [
          "role=\"status\" a aria-live=\"polite\": odečítač se o tom dozví, až domluví, ne uprostřed věty. Kolečko samo je aria-hidden — jméno nese obal.",
          "Pod prefers-reduced-motion se točení zastaví. Prstenec zůstane a jméno taky: kdo si vypnul pohyb, nevypnul si tím informaci.",
          "label je povinný v typu, ne jen v dokumentaci. Volitelné jméno je jméno, které v polovině případů chybí — a chybí právě tam, kde nikdo netestoval s odečítačem."
        ],
        "en": [
          "role=\"status\" and aria-live=\"polite\": a screen reader hears about it when it has finished speaking, not mid-sentence. The circle itself is aria-hidden — the wrapper carries the name.",
          "Under prefers-reduced-motion the spinning stops. The ring stays and so does the name: switching off movement did not switch off the information.",
          "label is required in the type, not only in the documentation. An optional name is a name that is missing half the time — and missing exactly where nobody tested with a screen reader."
        ]
      },
      "props": [
        {
          "name": "label",
          "type": "string",
          "required": true
        },
        {
          "name": "size",
          "type": "\"sm\" | \"md\"",
          "required": false,
          "values": [
            "sm",
            "md"
          ]
        },
        {
          "name": "block",
          "type": "boolean",
          "required": false
        },
        {
          "name": "className",
          "type": "string",
          "required": false
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/spinner",
        "en": "https://ingot.forgmatic.com/en/komponenty/spinner"
      }
    },
    {
      "name": "IngotStepCard",
      "display": "StepCard",
      "tag": ".stepcard",
      "status": "beta",
      "version": "1.6",
      "tokens": [
        "--surface",
        "--surface-2",
        "--surface-3",
        "--border",
        "--border-strong",
        "--ink",
        "--ink-3",
        "--ok",
        "--ok-bg",
        "--ok-border",
        "--font-mono",
        "--r-md"
      ],
      "classNameNote": {
        "cs": "`className` nebere. Vypadá stejně na každé obrazovce; rozvržení patří obalu kolem něj.",
        "en": "Does not take `className`. It looks the same on every screen; layout belongs to the wrapper around it."
      },
      "summary": {
        "cs": "Karta jednoho kroku vícekrokového nastavení. Nese svůj stav natrvalo — hotový krok zůstane hotový a je vidět i po návratu na obrazovku.",
        "en": "A card for one step of a multi-step setup. It carries its state permanently — a finished step stays finished and is still visible when you come back to the screen."
      },
      "avoidWhen": {
        "cs": [
          "Krok, který se potvrzuje tlačítkem. Krok je hotový tehdy, když má, co potřebuje — tlačítko „Hotovo“ v patičce by zavedlo druhý stav, který s obsahem karty nesouvisí.",
          "Průvodce, který ukazuje jen jednu obrazovku po druhé a předchozí krok zahodí. Tahle karta stojí na tom, že všechny kroky zůstávají vidět vedle sebe.",
          "Obyčejné seskupení obsahu bez pořadí a bez stavu. Očíslované záhlaví a fajfka tam slibují postup, který ve skutečnosti neexistuje.",
          "Zvýraznění chyby nebo varování. Karta zná jediný stav — hotovo, nebo ne; červené a oranžové hlášky patří dovnitř obsahu."
        ],
        "en": [
          "A step that gets confirmed with a button. A step is done when it has what it needs — a “Done” button in the footer would introduce a second state unrelated to the card's content.",
          "A wizard that shows one screen at a time and discards the previous step. This card is built on all the steps staying visible side by side.",
          "Plain grouping of content with no ordering and no state. A numbered header and a check mark promise progress that does not actually exist there.",
          "Highlighting an error or a warning. The card knows a single state — done or not; red and amber messages belong inside the content."
        ]
      },
      "a11y": {
        "cs": [
          "Hotový krok je poznat tvarem, ne jen barvou: zelené záhlaví doprovází fajfka místo čísla. Kdo barvu nerozliší, čte tvar — a tenhle stav je jediná informace, kvůli které se člověk na obrazovku vrací.",
          "Fajfka nese popisek z doneLabel. Vynechat ho znamená, že odečítač ohlásí kartu bez toho jediného, co ji odlišuje od rozdělané.",
          "Kicker je mono verzálkami jen opticky — text předávej normálně psaný. Verzálky napsané ručně čte odečítač po písmenech.",
          "Patička obsahuje jednu akci. Víc tlačítek v ní udělá z jednoznačného „přidej další položku“ hádanku, kterou u kroku bez potvrzení nemá co rozhodovat.",
          "Sbalovací tlačítko hlásí stav přes aria-expanded a přes aria-controls ukazuje na tělo kroku, takže odečítač nabídne skok přesně na to, co tlačítko odkrylo. Popisek dodává toggleLabel — chevron sám je němý.",
          "Sbalené tělo zůstává v dokumentu a schová ho hidden. Odstranit ho z DOM by rozbilo aria-controls, které by pak mířilo do prázdna — a hledání na stránce by sbalený krok přestalo najít."
        ],
        "en": [
          "A finished step is recognizable by shape, not color alone: the green header comes with a check mark instead of the number. Anyone who cannot tell the color apart reads the shape — and this state is the one piece of information people come back to the screen for.",
          "The check mark carries the label from doneLabel. Leaving it out means a screen reader announces the card without the single thing that separates it from an unfinished one.",
          "The kicker is mono uppercase only visually — pass the text written normally. Uppercase typed by hand gets read out letter by letter.",
          "The footer holds one action. More buttons turn an unambiguous “add another item” into a puzzle, which a step with no confirmation has no business posing.",
          "The collapse button reports its state through aria-expanded and points at the step body through aria-controls, so a screen reader can offer a jump to exactly what the button revealed. The label comes from toggleLabel — the chevron alone is silent.",
          "A collapsed body stays in the document and is hidden by hidden. Removing it from the DOM would break aria-controls, which would then point at nothing — and find-on-page would stop finding a collapsed step."
        ]
      },
      "props": [
        {
          "name": "step",
          "type": "string",
          "required": true
        },
        {
          "name": "kicker",
          "type": "string",
          "required": true
        },
        {
          "name": "title",
          "type": "ReactNode",
          "required": true
        },
        {
          "name": "meta",
          "type": "ReactNode",
          "required": false
        },
        {
          "name": "done",
          "type": "boolean",
          "required": false
        },
        {
          "name": "doneLabel",
          "type": "string",
          "required": false
        },
        {
          "name": "collapsible",
          "type": "boolean",
          "required": false
        },
        {
          "name": "toggleLabel",
          "type": "string",
          "required": false
        },
        {
          "name": "children",
          "type": "ReactNode",
          "required": true
        },
        {
          "name": "footer",
          "type": "ReactNode",
          "required": false
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/step-card",
        "en": "https://ingot.forgmatic.com/en/komponenty/step-card"
      }
    },
    {
      "name": "IngotSwitch",
      "display": "Switch",
      "tag": ".switch",
      "status": "beta",
      "version": "1.2",
      "tokens": [
        "--accent",
        "--border-strong",
        "--surface",
        "--ink-2",
        "--ink-3",
        "--ink-4",
        "--accent-bg"
      ],
      "classNameNote": {
        "cs": "Bere `className`, ale jen na rozvržení řádku — odsazení a zarovnání. Podobu přepínače drží primitivum.",
        "en": "Takes `className`, but for the row's layout only — margins and alignment. The switch's own shape stays with the primitive."
      },
      "summary": {
        "cs": "Nastavení, které platí hned po přepnutí. Ne zaškrtávátko: to čeká na uložení formuláře.",
        "en": "A setting that takes effect the moment it is flipped. Not a checkbox: that one waits for the form to be saved."
      },
      "avoidWhen": {
        "cs": [
          "Volba se projeví až po uložení formuláře. To je IngotCheckbox — přepínač by sliboval okamžitý účinek, který nenastal.",
          "Vybírá se jedna možnost z několika. To je IngotRadioGroup nebo IngotSegmented.",
          "Volba je nevratná nebo drahá. Přepínač na ni nepatří: patří tam tlačítko s potvrzením."
        ],
        "en": [
          "The choice takes effect when the form is saved. That is IngotCheckbox — a switch would promise an immediate effect that did not happen.",
          "One option out of several is being chosen. That is IngotRadioGroup or IngotSegmented.",
          "The change is irreversible or expensive. A switch does not carry that; a button with a confirmation does."
        ]
      },
      "a11y": {
        "cs": [
          "role=\"switch\" a aria-checked, takže odečítač hlásí zapnuto/vypnuto, ne zaškrtnuto.",
          "Popisek obaluje ovládací prvek, takže klik na text přepíná a jméno jde zadarmo.",
          "Stav není jen barva: knoflík se posune, což se pozná i v šedotónu."
        ],
        "en": [
          "role=\"switch\" and aria-checked, so a screen reader says on or off rather than checked.",
          "The label wraps the control, so clicking the text flips it and the name comes for free.",
          "The state is not only a colour: the knob moves, which reads in greyscale too."
        ]
      },
      "props": [
        {
          "name": "checked",
          "type": "boolean",
          "required": true
        },
        {
          "name": "onChange",
          "type": "(next: boolean) => void",
          "required": true
        },
        {
          "name": "label",
          "type": "ReactNode",
          "required": true
        },
        {
          "name": "hint",
          "type": "ReactNode",
          "required": false
        },
        {
          "name": "disabled",
          "type": "boolean",
          "required": false
        },
        {
          "name": "className",
          "type": "string",
          "required": false
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/switch",
        "en": "https://ingot.forgmatic.com/en/komponenty/switch"
      }
    },
    {
      "name": "IngotTable",
      "display": "Table",
      "tag": ".table-wrap",
      "status": "stable",
      "version": "1.3",
      "tokens": [
        "--surface-2",
        "--border",
        "--ink",
        "--ink-3",
        "--ink-4",
        "--accent",
        "--accent-bg",
        "--accent-border",
        "--r-md"
      ],
      "classNameNote": {
        "cs": "Bere `className` na rozvržení tabulky — typicky `min-w-[40rem]` pod vodorovným posuvníkem.",
        "en": "Takes `className` for the table's layout — typically `min-w-[40rem]` under a horizontal scroller."
      },
      "summary": {
        "cs": "Tabulka se sloupci jako daty. Drží scope na záhlaví, jeden zdroj pravdy pro colSpan a řádkové akce neschovává za hover.",
        "en": "A table whose columns are data. It keeps scope on the headers, one source of truth for colSpan, and never hides row actions behind hover."
      },
      "avoidWhen": {
        "cs": [
          "Data nejsou mřížka. Dvojice „popisek — hodnota“ u jednoho záznamu je definiční seznam, ne tabulka; odečítač by ohlašoval souřadnice, které nikam nevedou.",
          "Rozvržení je mřížka jen vizuálně (dlaždice, karty vedle sebe). Na to je CSS grid — <table> tomu dává význam, který nemá.",
          "Potřebuješ něco z výčtu „co první verze neumí“ níž a nemůžeš na to počkat. Konverze, která by byla UX regrese, je důvod tabulku nepoužít — ne důvod si ji obejít."
        ],
        "en": [
          "The data is not a grid. Label–value pairs for a single record are a description list, not a table; a screen reader would announce coordinates that lead nowhere.",
          "The layout is a grid only visually (tiles, cards side by side). That is CSS grid — a <table> gives it meaning it does not have.",
          "You need something from \"what the first version cannot do\" below and cannot wait for it. A conversion that would be a UX regression is a reason not to use the table — not a reason to work around it."
        ]
      },
      "a11y": {
        "cs": [
          "Tabulka sedí ve schránce, která se posouvá do stran, a ta schránka je tabIndex=0 — obsah, který jde odrolovat jen taháním myší, je obsah, ke kterému se klávesnice nedostane. Když má tabulka caption, je schránka pojmenovaný region; bez něj zůstane nepojmenovaná, protože přistát na „regionu“, který o sobě nic neříká, je horší než přistát v obyčejném posuvném boxu.",
          "Záhlaví staví primitivum: každá hlavička je <th scope=\"col\">. Ruční <thead> ten scope většinou nemá, a bez něj odečítač neví, ke kterému sloupci buňka patří.",
          "colSpan prázdného a načítacího řádku se počítá ze sloupců a případného sloupce akcí. Ruční tabulky ho mají natvrdo a přidání sloupce jim ho tiše rozejde.",
          "Řádkové akce se neschovávají za hover. Vzor opacity-0 group-hover:… je pro klávesnici i dotyk past; primitivum ho nedělá.",
          "loading nasadí na tabulku aria-busy a jeden role=\"status\" řádek, takže se ohlásí jednou — ne jednou za buňku.",
          "caption se vykresluje mimo obraz: čte ho odečítač, aniž by nad tabulkou přibyl nadpis, který návrh nepočítal.",
          "Seřazený sloupec nese aria-sort a řaditelná hlavička je tlačítko — klávesnice řadí stejně jako myš. Šipka v hlavičce je jen dekor; stav čte odečítač z atributu.",
          "Vybraný řádek nese aria-selected a checkbox v hlavičce umí i částečný stav (indeterminate), když je vybraná jen část řádků."
        ],
        "en": [
          "The table sits in a box that scrolls sideways, and that box is tabIndex=0 — content that can only be scrolled by dragging with a mouse is content a keyboard cannot reach. With a caption the box is a named region; without one it stays unnamed, because landing on a \"region\" that says nothing about itself is worse than landing on a plain scrollable box.",
          "The primitive builds the header: every heading is a <th scope=\"col\">. A hand-rolled <thead> usually lacks that scope, and without it a screen reader cannot tell which column a cell belongs to.",
          "The colSpan of the empty and loading rows is computed from the columns plus the actions column, if any. Hand-rolled tables hard-code it, and adding a column breaks it silently.",
          "Row actions are not hidden behind hover. The opacity-0 group-hover:… pattern is a trap for keyboard and touch alike; the primitive does not do it.",
          "loading puts aria-busy on the table and a single role=\"status\" row, so it is announced once — not once per cell.",
          "caption is rendered off-screen: a screen reader reads it without adding a heading above the table that the design did not plan for.",
          "The sorted column carries aria-sort and a sortable header is a button — the keyboard sorts the same way the mouse does. The arrow in the header is decoration; a screen reader reads the state from the attribute.",
          "A selected row carries aria-selected and the header checkbox knows the partial (indeterminate) state for when only some rows are selected."
        ]
      },
      "props": [
        {
          "name": "columns",
          "type": "readonly IngotColumn<Row>[]",
          "required": true
        },
        {
          "name": "rows",
          "type": "readonly Row[]",
          "required": true
        },
        {
          "name": "rowKey",
          "type": "(row: Row) => string",
          "required": true
        },
        {
          "name": "empty",
          "type": "ReactNode",
          "required": false
        },
        {
          "name": "loading",
          "type": "boolean",
          "required": false
        },
        {
          "name": "loadingLabel",
          "type": "string",
          "required": false
        },
        {
          "name": "actions",
          "type": "(row: Row) => ReactNode",
          "required": false
        },
        {
          "name": "actionsLabel",
          "type": "string",
          "required": false
        },
        {
          "name": "caption",
          "type": "string",
          "required": false
        },
        {
          "name": "stickyHeader",
          "type": "boolean",
          "required": false
        },
        {
          "name": "density",
          "type": "\"default\" | \"compact\"",
          "required": false,
          "values": [
            "default",
            "compact"
          ]
        },
        {
          "name": "sort",
          "type": "IngotSort",
          "required": false
        },
        {
          "name": "onSortChange",
          "type": "(sort: IngotSort) => void",
          "required": false
        },
        {
          "name": "selectedKeys",
          "type": "ReadonlySet<string>",
          "required": false
        },
        {
          "name": "onSelectedKeysChange",
          "type": "(keys: ReadonlySet<string>) => void",
          "required": false
        },
        {
          "name": "selectAllLabel",
          "type": "string",
          "required": false
        },
        {
          "name": "selectRowLabel",
          "type": "(row: Row) => string",
          "required": false
        },
        {
          "name": "bulkbar",
          "type": "ReactNode",
          "required": false
        },
        {
          "name": "rowClassName",
          "type": "(row: Row) => string | undefined",
          "required": false
        },
        {
          "name": "className",
          "type": "string",
          "required": false
        },
        {
          "name": "rowTestId",
          "type": "(row: Row) => string",
          "required": false
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "extraProps": [
        {
          "name": "IngotColumn<Row>",
          "props": [
            {
              "name": "key",
              "type": "string",
              "required": true
            },
            {
              "name": "header",
              "type": "ReactNode",
              "required": true
            },
            {
              "name": "cell",
              "type": "(row: Row, index: number) => ReactNode",
              "required": true
            },
            {
              "name": "align",
              "type": "\"start\" | \"end\"",
              "required": false,
              "values": [
                "start",
                "end"
              ]
            },
            {
              "name": "cellClassName",
              "type": "string",
              "required": false
            },
            {
              "name": "sortable",
              "type": "boolean",
              "required": false
            }
          ]
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/table",
        "en": "https://ingot.forgmatic.com/en/komponenty/table"
      }
    },
    {
      "name": "IngotTabs",
      "display": "Tabs",
      "tag": ".tabs",
      "status": "beta",
      "version": "1.2",
      "tokens": [
        "--border",
        "--ink",
        "--ink-3",
        "--surface-2",
        "--font-mono"
      ],
      "classNameNote": {
        "cs": "`className` nebere. Vypadá stejně na každé obrazovce; rozvržení patří obalu kolem něj.",
        "en": "Does not take `className`. It looks the same on every screen; layout belongs to the wrapper around it."
      },
      "summary": {
        "cs": "Přepínání pohledů na tentýž záznam: řízené value/onChange, role tablist a šipky mezi taby.",
        "en": "Switching views of the same record: controlled value/onChange, the tablist role and arrow keys between tabs."
      },
      "avoidWhen": {
        "cs": [
          "Kroky procesu, které mají pořadí a dokončení — to je steps pattern, ne taby.",
          "Filtrování téhož seznamu („Vše / Aktivní / Archiv“) — to jsou chipy; tab slibuje jiný pohled, ne jiný výřez.",
          "Navigace mezi různými záznamy nebo stránkami — na to je menu, ne taby."
        ],
        "en": [
          "Process steps with an order and completion — that is a steps pattern, not tabs.",
          "Filtering the same list (\"All / Active / Archived\") — those are chips; a tab promises a different view, not a different slice.",
          "Navigation between different records or pages — that is a menu, not tabs."
        ]
      },
      "a11y": {
        "cs": [
          "Role drží komponenta sama: role=\"tablist\", role=\"tab\" s aria-selected a role=\"tabpanel\" svázaný s aktivním tabem.",
          "Roving tabindex: Tab zastaví jen na aktivním tabu, šipky (a Home/End) přepínají mezi pohledy — fokus se přesouvá bez posunu stránky.",
          "Aktivní tab je poznat i bez barvy: podtržení a tučnost."
        ],
        "en": [
          "The component holds the roles itself: role=\"tablist\", role=\"tab\" with aria-selected and role=\"tabpanel\" tied to the active tab.",
          "A roving tabindex: Tab stops only on the active tab, arrow keys (and Home/End) switch views — focus moves without scrolling the page.",
          "The active tab is recognizable without color: underline and bold."
        ]
      },
      "props": [
        {
          "name": "items",
          "type": "IngotTabItem[]",
          "required": true
        },
        {
          "name": "value",
          "type": "string",
          "required": true
        },
        {
          "name": "onChange",
          "type": "(key: string) => void",
          "required": true
        },
        {
          "name": "children",
          "type": "ReactNode",
          "required": false
        },
        {
          "name": "label",
          "type": "string",
          "required": false
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "extraProps": [
        {
          "name": "IngotTabItem",
          "props": [
            {
              "name": "key",
              "type": "string",
              "required": true
            },
            {
              "name": "label",
              "type": "string",
              "required": true
            },
            {
              "name": "count",
              "type": "number",
              "required": false
            }
          ]
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/tabs",
        "en": "https://ingot.forgmatic.com/en/komponenty/tabs"
      }
    },
    {
      "name": "IngotToast",
      "display": "Toast",
      "tag": ".toast",
      "status": "beta",
      "version": "2.1",
      "tokens": [
        "--bg",
        "--border-strong",
        "--ink",
        "--danger",
        "--r-lg",
        "--shadow-lg"
      ],
      "classNameNote": {
        "cs": "`className` nebere. Místo i vrstva jsou pevné, aby výsledek akce nikdy nezakryl hlavní akci stránky.",
        "en": "Does not take `className`. Position and layer are fixed so an action's result never covers the page's primary action."
      },
      "summary": {
        "cs": "Imperativní toast pro výsledek akce: toast({ text, undo }) ohlásí, co se stalo, a nezastaví práci.",
        "en": "An imperative toast for the result of an action: toast({ text, undo }) announces what happened without stopping the work."
      },
      "avoidWhen": {
        "cs": [
          "Validace formuláře. Chyba pole patří k poli (IngotField má error), ne do toastu, který za 4 sekundy zmizí.",
          "Sdělení, které vyžaduje rozhodnutí. Toast nejde „potvrdit“ — na to je IngotModal nebo IngotConfirm.",
          "Trvalý stav („3 položky čekají na schválení“). Toast je pomíjivý; trvalé hlášení patří do stránky."
        ],
        "en": [
          "Form validation. A field error belongs to the field (IngotField has error), not to a toast that disappears in 4 seconds.",
          "A message that demands a decision. A toast cannot be \"confirmed\" — that is IngotModal or IngotConfirm.",
          "A persistent state (\"3 items awaiting approval\"). A toast is ephemeral; a persistent message belongs in the page."
        ]
      },
      "a11y": {
        "cs": [
          "Region je aria-live=\"polite\" — odečítač toast přečte, až domluví. Chyba operace (tone=\"danger\") se hlásí assertive.",
          "Toast stojí vlevo dole, aby nepřekryl primární akci stránky.",
          "Toast se zpětnou akcí žije 8 sekund místo 4 — operátor musí stihnout přečíst, pochopit a kliknout.",
          "V tmavém motivu dostává toast border, aby se inverzní plocha neztratila na tmavém pozadí."
        ],
        "en": [
          "The region is aria-live=\"polite\" — a screen reader announces the toast once it finishes speaking. An operation error (tone=\"danger\") is announced assertive.",
          "The toast sits bottom-left so it never covers the page's primary action.",
          "A toast with an undo lives 8 seconds instead of 4 — the operator has to read, understand and click in time.",
          "In the dark theme the toast gets a border so its inverted surface does not sink into the dark background."
        ]
      },
      "props": [
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "extraProps": [
        {
          "name": "toast(options)",
          "props": [
            {
              "name": "text",
              "type": "string",
              "required": true
            },
            {
              "name": "tone",
              "type": "Extract<IngotTone, \"neutral\" | \"danger\">",
              "required": false
            },
            {
              "name": "undo",
              "type": "() => void",
              "required": false
            },
            {
              "name": "undoLabel",
              "type": "string",
              "required": false
            },
            {
              "name": "duration",
              "type": "number",
              "required": false
            }
          ]
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/toast",
        "en": "https://ingot.forgmatic.com/en/komponenty/toast"
      }
    },
    {
      "name": "IngotToolbar",
      "display": "Toolbar",
      "tag": ".toolbar",
      "status": "beta",
      "version": "1.0",
      "tokens": [
        "--ink-2",
        "--ink-3"
      ],
      "classNameNote": {
        "cs": "Bere `className`, ale jen na rozvržení — šířku, mezery, umístění v mřížce. Vzhled drží primitivum.",
        "en": "Takes `className`, but for layout only — width, spacing, placement in a grid. The look stays with the primitive."
      },
      "summary": {
        "cs": "Filtr bar nad seznamem. Drží mezery, zalamování a pravý konec; čím se filtruje, dodává volající.",
        "en": "A filter bar above a list. It owns spacing, wrapping and the right end; what filters, the caller supplies."
      },
      "avoidWhen": {
        "cs": [
          "Pruh hromadných akcí nad vybranými řádky — ten kreslí tabulka sama, protože visí na jejím výběru.",
          "Formulář s odesláním. Filtr bar je skupina prvků s okamžitým účinkem; co má tlačítko „Uložit“ a validaci, patří do formuláře."
        ],
        "en": [
          "The bulk-action bar above selected rows — the table draws that one itself, because it hangs off the table's selection.",
          "A form with a submit. A filter bar is a group of controls with immediate effect; anything with a \"Save\" button and validation belongs in a form."
        ]
      },
      "a11y": {
        "cs": [
          "Každý prvek uvnitř potřebuje vlastní popisek — aria-label nebo label. Bar je jen rozvržení a jména za volajícího nedoplní.",
          "Žádné role=\"toolbar\": ta role slibuje šipkovou navigaci, kterou by pak musel někdo doopravdy napsat. Filtr bar je obyčejná skupina formulářových prvků s tabováním.",
          "Bar se zalamuje (flex-wrap) — na úzkém okně ani při 200% zvětšení se filtry neoříznou, jen přeskládají."
        ],
        "en": [
          "Every control inside needs its own label — aria-label or a label. The bar is layout only and will not name things for the caller.",
          "No role=\"toolbar\": that role promises arrow-key navigation someone would then have to actually write. A filter bar is an ordinary group of form controls, tabbed through.",
          "The bar wraps (flex-wrap) — on a narrow window or at 200% zoom the filters reflow instead of clipping."
        ]
      },
      "props": [
        {
          "name": "children",
          "type": "ReactNode",
          "required": true
        },
        {
          "name": "end",
          "type": "ReactNode",
          "required": false
        },
        {
          "name": "className",
          "type": "string",
          "required": false
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/toolbar",
        "en": "https://ingot.forgmatic.com/en/komponenty/toolbar"
      }
    },
    {
      "name": "IngotTooltip",
      "display": "Tooltip",
      "tag": ".tooltip",
      "status": "beta",
      "version": "1.1",
      "tokens": [
        "--ink",
        "--bg",
        "--r-md",
        "--shadow-lg"
      ],
      "classNameNote": {
        "cs": "`className` nebere. Bublina má jednu podobu na celý produkt; umístění řídí `placement`.",
        "en": "Does not take `className`. The bubble has one shape across the product; `placement` drives where it lands."
      },
      "summary": {
        "cs": "Krátký popisek u prvku, na hover i na fokus. Popisuje, nepojmenovává — jméno prvku zůstává na něm.",
        "en": "A short description next to a control, on hover and on focus. It describes, it does not name — the control keeps its own name."
      },
      "avoidWhen": {
        "cs": [
          "Text je potřeba k tomu, aby se dalo jednat. Tooltip je nápověda pro myš a oko — co člověk potřebuje k rozhodnutí, patří do popisku nebo vedle prvku.",
          "Obsah se dá vybrat, prokliknout nebo s ním pracovat — to je IngotPopover.",
          "Nahrazuje se jím jméno prvku. Jméno nese aria-label tlačítka; tooltip ho jen popisuje."
        ],
        "en": [
          "The text is needed in order to act. A tooltip is a hint for the mouse and the eye — what a person needs to decide belongs in the label or next to the control.",
          "The content can be selected, clicked through or worked with — that is IngotPopover.",
          "It is used instead of the control's name. The name is the button's aria-label; the tooltip only describes it."
        ]
      },
      "a11y": {
        "cs": [
          "Bublina je role=\"tooltip\" a váže se přes aria-describedby. Odečítač tak přečte jméno prvku a pak popis — ne dvakrát totéž.",
          "Ukazuje se na hover i na fokus. Klávesnice není druhá kategorie: co se dozví myš, musí se dozvědět i ona.",
          "Escape bublinu skryje, i když kurzor zůstane — WCAG 1.4.13. Bublina nesmí zůstat přes obsah, který zakrývá.",
          "Bublina sama fokus nebere a je pointer-events: none: nesmí stát v cestě prvku, který popisuje."
        ],
        "en": [
          "The bubble is role=\"tooltip\" and is tied with aria-describedby. A screen reader then reads the control's name and the description — not the same words twice.",
          "It shows on hover and on focus. The keyboard is not a second class: what the mouse learns, it has to learn too.",
          "Escape hides the bubble even while the pointer stays — WCAG 1.4.13. A bubble must not sit over the content it covers.",
          "The bubble takes no focus and is pointer-events: none: it must not stand in the way of the control it describes."
        ]
      },
      "props": [
        {
          "name": "text",
          "type": "string",
          "required": true
        },
        {
          "name": "children",
          "type": "ReactElement",
          "required": true
        },
        {
          "name": "placement",
          "type": "IngotPlacement",
          "required": false
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/tooltip",
        "en": "https://ingot.forgmatic.com/en/komponenty/tooltip"
      }
    },
    {
      "name": "IngotTopNav",
      "display": "TopNav",
      "tag": ".topnav",
      "status": "beta",
      "version": "2.12",
      "tokens": [
        "--surface",
        "--surface-2",
        "--surface-3",
        "--border",
        "--ink",
        "--ink-2",
        "--r-sm"
      ],
      "classNameNote": {
        "cs": "`className` nebere. Vypadá stejně na každé obrazovce; rozvržení patří obalu kolem něj.",
        "en": "Does not take `className`. It looks the same on every screen; layout belongs to the wrapper around it."
      },
      "summary": {
        "cs": "Horní lišta aplikace — brand, sekce a účet v jednom řádku. Administrace nemá boční menu; obsah pod lištou jde na plnou šířku.",
        "en": "The application's top bar — brand, sections and account in one row. The admin has no side menu; content below the bar runs full width."
      },
      "avoidWhen": {
        "cs": [
          "Rejstřík stránek uvnitř obsahu — dokumentace, nápověda, dlouhé nastavení. Na to je IngotSideNav: je to obsah stránky, ne rám aplikace.",
          "Sekce, která má jedinou obrazovku. Tlačítko, které rozbalí menu s jednou položkou, je krok navíc — udělej z ní odkaz.",
          "Sada sekcí, která se i s popisky nevejde na 1280 px. Lišta se nezalamuje a co přeteče, zmizí za okrajem — měřítkem je nejužší podporovaná šířka, ne pevný počet. Když se lišta láme, zkracuj popisky nebo spoj dvě sekce."
        ],
        "en": [
          "An index of pages inside the content — documentation, help, a long settings flow. That is IngotSideNav: it is page content, not the application frame.",
          "A section with a single screen. A button that opens a menu with one item is a step too many — make it a link.",
          "A set of sections that will not fit at 1280 px with their labels. The bar does not wrap and whatever overflows falls off the edge — the measure is the narrowest supported width, not a fixed count. When the bar breaks, shorten labels or merge two sections."
        ]
      },
      "a11y": {
        "cs": [
          "Sekce je tlačítko, ne odkaz — sama nikam nevede, jen rozbaluje menu. Nese proto aria-expanded, ne aria-current; ta patří až odkazu uvnitř menu.",
          "Otevřenou sekci značí plocha --surface-3, ne akcent. Akcent v téhle aplikaci znamená akci, a rozbalené menu žádná akce není.",
          "Ikonové akce vpravo potřebují aria-label — lupa bez popisku je pro odečítač jen „tlačítko“.",
          "Esc zavírá menu a vrací fokus na tlačítko sekce. Zavírání drží volající, protože jen on ví, jestli se má menu zavřít i po prokliku. Návrat fokusu drží lišta — zmizelý panel by ho jinak zahodil na začátek stránky.",
          "ArrowDown a ArrowUp na tlačítku sekci otevřou a skočí na první, resp. poslední položku; uvnitř panelu procházejí položky dokola. Menu, které se otevírá najetím myší, je bez toho z klávesnice past: panel je vidět, ale fokus zůstal na tlačítku.",
          "Tab z otevřeného panelu nevypadne — obchází jeho položky. Odejít doprostřed lišty a nechat si panel viset za zády je stav, ze kterého se čtenář nedostane zpátky; ven vede Esc."
        ],
        "en": [
          "A section is a button, not a link — it goes nowhere on its own, it opens a menu. So it carries aria-expanded, not aria-current; that belongs to the link inside the menu.",
          "An open section is marked with the --surface-3 surface, not the accent. The accent means an action in this application, and an open menu is not an action.",
          "The icon actions on the right need an aria-label — a magnifier without one is just “button” to a screen reader.",
          "Esc closes the menu and returns focus to the section button. Closing is the caller's, because only they know whether the menu should also close after a click-through. The focus return is the bar's — a vanished panel would otherwise drop focus at the top of the page.",
          "ArrowDown and ArrowUp on the button open the section and jump to its first or last item; inside the panel they walk the items and wrap around. Without that, a menu that opens on hover is a keyboard trap: the panel is visible, but focus stayed on the button.",
          "Tab does not fall out of an open panel — it cycles through its items. Walking off into the middle of the bar with the panel left hanging behind you is a state there is no way back from; Esc is the way out."
        ]
      },
      "props": [
        {
          "name": "brand",
          "type": "ReactNode",
          "required": true
        },
        {
          "name": "sections",
          "type": "readonly IngotTopNavSection[]",
          "required": false
        },
        {
          "name": "openSection",
          "type": "string | null",
          "required": false
        },
        {
          "name": "onOpenSection",
          "type": "(key: string) => void",
          "required": false
        },
        {
          "name": "onCloseSection",
          "type": "() => void",
          "required": false
        },
        {
          "name": "renderMenu",
          "type": "(key: string) => ReactNode",
          "required": false
        },
        {
          "name": "menuButton",
          "type": "ReactNode",
          "required": false
        },
        {
          "name": "contentClassName",
          "type": "string",
          "required": false
        },
        {
          "name": "sectionsLabel",
          "type": "string",
          "required": false
        },
        {
          "name": "sectionsClassName",
          "type": "string",
          "required": false
        },
        {
          "name": "sectionsEnd",
          "type": "ReactNode",
          "required": false
        },
        {
          "name": "actions",
          "type": "ReactNode",
          "required": false
        },
        {
          "name": "account",
          "type": "ReactNode",
          "required": false
        },
        {
          "name": "children",
          "type": "ReactNode",
          "required": false
        }
      ],
      "extraProps": [
        {
          "name": "IngotTopNavAccount",
          "props": [
            {
              "name": "initials",
              "type": "string",
              "required": true
            },
            {
              "name": "label",
              "type": "string",
              "required": true
            },
            {
              "name": "expanded",
              "type": "boolean",
              "required": false
            }
          ]
        },
        {
          "name": "IngotTopNavSection",
          "props": [
            {
              "name": "key",
              "type": "string",
              "required": true
            },
            {
              "name": "label",
              "type": "string",
              "required": true
            },
            {
              "name": "href",
              "type": "string",
              "required": false
            },
            {
              "name": "onNavigate",
              "type": "(event) => void",
              "required": false
            },
            {
              "name": "current",
              "type": "boolean",
              "required": false
            },
            {
              "name": "locked",
              "type": "boolean",
              "required": false
            },
            {
              "name": "onLockedClick",
              "type": "() => void",
              "required": false
            },
            {
              "name": "badge",
              "type": "ReactNode",
              "required": false
            },
            {
              "name": "muted",
              "type": "boolean",
              "required": false
            },
            {
              "name": "testId",
              "type": "string",
              "required": false
            }
          ]
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/top-nav",
        "en": "https://ingot.forgmatic.com/en/komponenty/top-nav"
      }
    },
    {
      "name": "IngotUserMenu",
      "display": "UserMenu",
      "tag": ".usermenu",
      "status": "beta",
      "version": "1.0",
      "tokens": [
        "--surface",
        "--border",
        "--ink",
        "--ink-2",
        "--r-lg",
        "--shadow-lg"
      ],
      "classNameNote": {
        "cs": "`className` nebere. Vypadá stejně na každé obrazovce; rozvržení patří obalu kolem něj.",
        "en": "Does not take `className`. It looks the same on every screen; layout belongs to the wrapper around it."
      },
      "summary": {
        "cs": "Menu účtu — identita, organizace, předvolby, odhlášení. Primitivum drží strukturu, ne obsah: vrstvy oddělené linkou a řádek „popisek vlevo, ovládací prvek vpravo“.",
        "en": "The account menu — identity, organisation, preferences, sign-out. The primitive holds the structure, not the content: layers separated by a rule, and a row of “label on the left, control on the right”."
      },
      "avoidWhen": {
        "cs": [
          "Nastavení, které má víc než jeden ovládací prvek na volbu. Menu má šířku jednoho sloupce; delší formulář patří na vlastní obrazovku.",
          "Předvolby vázané na jednu obrazovku — filtry, řazení, hustota tabulky. Ty patří k té obrazovce, ne k účtu.",
          "Nabídka akcí nad záznamem. Tohle je menu člověka, ne kontextové menu obsahu.",
          "Snaha popsat, které předvolby má produkt mít. Kit zná tvar vrstev a řádků; co je uvnitř, ví aplikace."
        ],
        "en": [
          "Settings that need more than one control per choice. The menu is one column wide; a longer form belongs on a screen of its own.",
          "Preferences bound to a single screen — filters, sorting, table density. Those belong to that screen, not to the account.",
          "A list of actions on a record. This is a person's menu, not a context menu for content.",
          "Any attempt to prescribe which preferences the product should have. The kit knows the shape of the layers and rows; what goes inside them is the application's business."
        ]
      },
      "a11y": {
        "cs": [
          "Vypnutá nápověda na stránkách nesmí změnit rozvržení stránky. Skrývá se viditelnost, ne prostor — jinak se obsah přeskládá a uživatel přijde o místo, kam se právě díval.",
          "Popisek řádku je label jen tehdy, když prvek vpravo dostane controlId. Kliknutí na popisek pak ovládá prvek a odečítač je přečte spolu.",
          "Menu je skupina s vlastním jménem z label. Odečítač tak řekne, do čeho uživatel vstoupil, ještě než začne číst položky.",
          "Odznak s aktuální hodnotou předvolby musí nést text, ne jen barvu. Zapnutá a vypnutá nápověda se od sebe nesmí lišit jen odstínem."
        ],
        "en": [
          "Turning in-page help off must not change the page's layout. What is hidden is the visibility, not the space — otherwise the content reflows and the user loses the spot they were looking at.",
          "A row's label is a real label only when the control on the right is given a controlId. Clicking the label then operates the control, and a screen reader reads the two together.",
          "The menu is a group with its own name from label. That way a screen reader says what the user has entered before it starts reading the items.",
          "A badge showing a preference's current value must carry text, not only colour. Help switched on and help switched off must not differ by shade alone."
        ]
      },
      "props": [
        {
          "name": "children",
          "type": "ReactNode",
          "required": true
        },
        {
          "name": "label",
          "type": "string",
          "required": true
        },
        {
          "name": "testId",
          "type": "string",
          "required": false
        }
      ],
      "extraProps": [
        {
          "name": "IngotUserMenuSection",
          "props": [
            {
              "name": "children",
              "type": "ReactNode",
              "required": true
            },
            {
              "name": "testId",
              "type": "string",
              "required": false
            }
          ]
        },
        {
          "name": "IngotUserMenuRow",
          "props": [
            {
              "name": "label",
              "type": "ReactNode",
              "required": true
            },
            {
              "name": "controlId",
              "type": "string",
              "required": false
            },
            {
              "name": "children",
              "type": "ReactNode",
              "required": true
            },
            {
              "name": "testId",
              "type": "string",
              "required": false
            }
          ]
        }
      ],
      "page": {
        "cs": "https://ingot.forgmatic.com/komponenty/user-menu",
        "en": "https://ingot.forgmatic.com/en/komponenty/user-menu"
      }
    }
  ]
}
