<p align="center">
  <img src="https://raw.githubusercontent.com/ZoliQua/React-Advanced-Odontogram/main/src/assets/react-module-logo.png" alt="React Advanced Odontogram logo" width="160" />
</p>

# 🦷 React Advanced Odontogram

[![Download](https://img.shields.io/badge/Download-React--Odontogram--Modul-blue?style=for-the-badge&logo=github)](https://github.com/ZoliQua/React-Advanced-Odontogram/releases)
[![Version](https://img.shields.io/badge/version-2.6.0-green?style=for-the-badge)](https://github.com/ZoliQua/React-Advanced-Odontogram)
[![npm](https://img.shields.io/npm/v/react-advanced-odontogram?style=for-the-badge&logo=npm&color=CB3837)](https://www.npmjs.com/package/react-advanced-odontogram)
[![License](https://img.shields.io/badge/license-MIT-orange?style=for-the-badge)](https://github.com/ZoliQua/React-Advanced-Odontogram/blob/main/LICENSE)
[![DOI](../src/assets/zenodo.21156787.svg)](https://doi.org/10.5281/zenodo.21156787)

[![React](https://img.shields.io/badge/React-18%20%7C%2019-61DAFB?style=for-the-badge&logo=react)](https://reactjs.org/)
[![TypeScript](https://img.shields.io/badge/TypeScript-5-3178C6?style=for-the-badge&logo=typescript)](https://www.typescriptlang.org/)

---

> 🌐 **Languages:**  🇬🇧 [English](README-en.md) | 🇪🇸 [Español](README-es.md) | 🇩🇪 [Deutsch](README-de.md) | 🇭🇺 [Magyar](README-hu.md) | 🇮🇹 [Italiano](README-it.md) | 🇸🇰 [Slovenčina](README-sk.md) | 🇵🇱 [Polski](README-pl.md) | 🇷🇺 [Русский](README-ru.md) | 🇧🇷 [Português (BR)](README-pt-br.md) | 🇸🇦 [العربية](README-ar.md) | 🇨🇳 [简体中文](README-zh.md) | 🇫🇷 [Français](README-fr.md)

---

## 📑 Содержание

- [📋 Обзор](#-обзор)
- [📦 Использование в качестве npm-пакета](#-использование-в-качестве-npm-пакета)
- [✨ Основные возможности](#-основные-возможности)
- [📦 Модули](#-модули)
- [🛠️ Элементы управления интерфейса](#-элементы-управления-интерфейса)
- [🦷 Типы зубов и состояния](#-типы-зубов-и-состояния)
- [⚙️ Настройки](#-настройки)
- [🖼️ Система SVG-шаблонов](#-система-svg-шаблонов)
- [🔢 Системы нумерации](#-системы-нумерации)
- [🚀 Использование](#-использование)
- [🔗 Интеграция](#-интеграция)
- [🧪 Тестирование](#-тестирование)
- [📖 Документация API](#-документация-api)
- [📡 Публичный API](#-публичный-api)
- [💾 Сохранение состояния (localStorage)](#-сохранение-состояния-localstorage)
- [💾 Формат экспорта/импорта статуса](#-формат-экспортаимпорта-статуса)
- [🖨️ Экспорт](#-экспорт)
- [📁 Структура папок](#-структура-папок)
- [⚙️ Технологический стек](#-технологический-стек)
- [📝 Примечания](#-примечания)
- [🔒 Примечания по безопасности](#-примечания-по-безопасности)
- [📖 Как цитировать](#-как-цитировать)

## 🇷🇺 Русский

### 📋 Обзор
Данный проект представляет собой интерактивный браузерный редактор одонтограммы, обеспечивающий быстрое заполнение зубной карты с удобным интерфейсом. Компонент отображает многослойные SVG-шаблоны зубов для представления реставраций, кариеса, эндодонтического статуса, подвижности и других клинических данных, а также поддерживает множественный выбор, фильтры выделения и предустановленные статусные шаблоны.

---
![Одонтограмма — предпросмотр (русский)](screenshot_ru_odontogram.png)

🔗 **Test URL:** https://react-advanced-odontogram.vercel.app/

---

### 📦 Использование в качестве npm-пакета

Одонтограмма поставляется как самодостаточная библиотека React-компонентов на npm:
[`react-advanced-odontogram`](https://www.npmjs.com/package/react-advanced-odontogram).

#### Требования
- **React 18 или 19** (объявлен как peer-зависимость — предоставляется вашим приложением).
- **Сборщик**, понимающий поле `exports` и ESM: Vite, webpack 5, Next.js, Rollup, esbuild, Parcel. Пакет **только ESM**.
- Node **≥ 18** для инструментария.

#### Установка

```bash
npm install react-advanced-odontogram react react-dom
```

#### Базовое использование

Отрисуйте `OdontogramShell` и импортируйте таблицу стилей **один раз** в любом месте вашего приложения:

```tsx
import { OdontogramShell } from "react-advanced-odontogram";
import "react-advanced-odontogram/style.css";

export function Chart() {
  return (
    <OdontogramShell
      language="ru"          // hu | en | de | es | it | sk | pl | ru | pt-br | ar | zh | fr
      numberingSystem="FDI"  // FDI | Universal | Palmer
      darkMode={false}
    />
  );
}
```

#### Пропсы компонента

`OdontogramShell` — управляемый компонент. Наиболее часто используемые пропсы:

| Проп | Тип | По умолчанию | Описание |
|------|------|---------|-------------|
| `language` | `Language` | `"hu"` | Язык интерфейса (`hu`/`en`/`de`/`es`/`it`/`sk`/`pl`/`ru`/`pt-br`/`ar`/`zh`). |
| `numberingSystem` | `"FDI" \| "Universal" \| "Palmer"` | `"FDI"` | Система нумерации зубов. |
| `darkMode` | `boolean` | `false` | Переключатель тёмной темы. |
| `readOnly` | `boolean` | `false` | Отключить всё редактирование (только просмотр). |
| `themeConfig` | `OdontogramThemeConfig` | — | Переопределить CSS-переменные темы (`--odon-*`). |
| `plugins` | `OdontogramPlugin[]` | — | Зарегистрировать пользовательские плагины состояния / дополнительные слои. |
| `enableNotes` | `boolean` | `false` | Включить заметки по каждому зубу. |
| `enableIcdas` | `boolean` | `false` | Включить оценку кариеса по ICDAS II. |
| `fillingComplexity` | `"complex" \| "simple"` | `"complex"` | Сложность пломбы: `"simple"` (один материал на зуб) или `"complex"` (материалы по поверхностям). |
| `fillingDefectEnabled` | `boolean` | `true` | Включает фиксацию дефектов пломбы на карточке «Пломбы». |
| `fillingMaterialAvailability` | `Record<string, boolean>` | все доступны | Доступные пломбировочные материалы в виде логической карты по `amalgam`/`composite`/`gic`/`temporary` (неизвестные ключи игнорируются). |
| `fissureSealingEnabled` | `boolean` | `true` | Включает герметизацию фиссур на карточке «Пломбы». |
| `screenToothSpacing` | `"wide" \| "normal" \| "close"` | `"normal"` | Расстояние между зубами на экране. |
| `screenToothNumberSize` | `"small" \| "normal" \| "xlarge"` | `"normal"` | Размер номера зуба в сетке. |
| `selectionColor` | `string` | `"#3b7bff"` | Цвет кольца выделения (`#rrggbb`). |
| `selectionBorderStyle` | `"solid" \| "dashed" \| "dotted"` | `"dashed"` | Стиль границы кольца выделения. |
| `toothInfo` | `boolean` | `true` | Показывать панель информации о зубе. |
| `onFillingComplexityChange` / `onFillingDefectEnabledChange` / `onFillingMaterialAvailabilityChange` / `onFissureSealingEnabledChange` | `(...) => void` | — | Срабатывает, когда пользователь меняет соответствующую настройку в Настройки → Пломбы. |
| `onLanguageChange` / `onNumberingChange` / `onDarkModeChange` | `(value) => void` | — | Срабатывает, когда пользователь меняет настройку через интерфейс. |

Также принимаются более тонкие пропсы уровня детализации (`pulpDetailLevel`, `secondaryCariesMode`, `rootCariesMode`, `radiographicDepthMode`, `wearDetailLevel`, `discolorationDetailLevel`, `surfaceNotation`, `showStatusCard`, `showOrthoCard`) — полный типизированный список см. в поставляемых типах `.d.ts`.

Четыре пропса заполнений выше предназначены **только для восстановления**: пропущенный пропс никогда не пишет в движок (императивный вызов `setFillingComplexity()` до монтирования сохраняется, автономный режим не меняется), а переданный пропс записывает движок и состояние модального окна «Настройки» вместе, поэтому окно никогда не показывает устаревшее значение. `fillingMaterialAvailability` применяется через diff по каноническому сериализованному ключу — повторный рендер с inline-литералом идентичного содержимого никогда не перезаписывает движок. Соответствующие колбэки `on*Change` вызываются из Настройки → Пломбы: это путь обратной записи для хостов, сохраняющих предпочтения.

#### Публичный API (именованные экспорты)

`OdontogramShell` является одновременно экспортом по умолчанию и именованным экспортом. Императивный API состояния, самостоятельный компонент `PerioChart`, направляемый тур и все публичные типы являются именованными экспортами из той же точки входа:

```ts
import {
  OdontogramShell,           // также экспорт по умолчанию
  PerioChart,                // самостоятельный компонент пародонтальной карты
  // чтение состояния
  getOdontogramSummary,
  getToothStateSummary,
  onStateChange,             // подписка на изменения состояния
  // экспорт / импорт
  exportFhir,                // пакет HL7 FHIR R4
  exportSvg, exportImage,    // векторный / растровый экспорт карты
  setImportFormat,
  // управление
  setReadOnly, getReadOnly,
  clearSelection, getSelectedTeeth,
  registerPlugins, setPluginState, getPluginState,
  startIntroTour,            // запустить обучающий тур
  // …и многие другие функции настроек setX/getX
} from "react-advanced-odontogram";
```

Полная поверхность API (≈ 44 функции + типы, такие как `OdontogramSummary`, `OdontogramThemeConfig`, `OdontogramPlugin`, `FhirExportOptions`, `PerioViewMode`, …) полностью типизирована во встроенных объявлениях.

#### Компонуемые поверхности (продвинутый уровень)

`OdontogramShell` — это поддерживаемый компонент «всё в одном», не требующий дополнительной настройки. Если вам нужно разместить области одонтограммы в разных частях собственной вёрстки, четыре поверхности интерфейса оболочки также экспортируются и могут быть скомпонованы под одним `OdontogramProvider`, при этом все они используют один сеанс, управляемый пакетом:

```tsx
import {
  OdontogramProvider,
  OdontogramTopbar,
  OdontogramChartSurface,
  ToothInfoSurface,
  ToothControlsSurface,
} from "react-advanced-odontogram";
import "react-advanced-odontogram/style.css";

function Workspace() {
  return (
    <OdontogramProvider language="en" numberingSystem="FDI">
      <MyHeaderArea><OdontogramTopbar /></MyHeaderArea>
      <MyMainArea>
        <OdontogramChartSurface />
        <ToothInfoSurface />
      </MyMainArea>
      <MySidePanel><ToothControlsSurface /></MySidePanel>
    </OdontogramProvider>
  );
}
```

`OdontogramProvider` принимает те же пропсы, что и `OdontogramShell`. Для создания собственных поверхностей доступны хук `useOdontogramUi()` (и тип `OdontogramUiContextValue`). Текущее ограничение: используйте один провайдер на страницу. Поверхности можно монтировать и размонтировать по мере необходимости — при повторном монтировании они автоматически перепривязываются. Сам `OdontogramShell` не изменился — это именно такая композиция в компоновке по умолчанию.

Для ещё более тонкой композиции также экспортируются отдельные карточки управления — `OrthodonticsCard`, `StatusesCard`, `CariesCard`, `FillingsCard`, `RootPeriodontiumCard` и `ToothDetailsCard` — каждая представляет собой самостоятельный декларативный компонент, который читает и записывает общий сеанс через API движка (для создания собственных экспортируется хук `useEngineState()`). Монтируйте только те карточки, которые нужны конкретной компоновке, в любом порядке, под одним `OdontogramProvider`.

#### Использование с Next.js (App Router)

Компонент работает только на клиенте, поэтому отрисовывайте его из клиентского компонента:

```tsx
"use client";
import { OdontogramShell } from "react-advanced-odontogram";
import "react-advanced-odontogram/style.css";

export default function OdontogramClient() {
  return <OdontogramShell language="ru" numberingSystem="FDI" />;
}
```

Либо загружайте его через клиентский динамический импорт: `dynamic(() => import("./OdontogramClient"), { ssr: false })`.

#### Важные замечания и текущие ограничения
- **Только ESM** — пакет публикует единый ES-модуль (`dist/odontogram.js`) плюс точку входа для деклараций типов (`dist/index.d.ts`). Он ориентирован на разрешение модулей сборщиком; сборки CommonJS нет.
- **Таблица стилей отдельная** — вы **обязаны** импортировать `react-advanced-odontogram/style.css` один раз; она не подключается автоматически. Стилизация — это глобальный CSS, ограниченный областью `.odontogram-root` и управляемый CSS-переменными `--odon-*`.
- **SSR / только клиент** — компонент обращается к DOM при монтировании (`document`), поэтому он должен выполняться в браузере. В SSR-фреймворках отрисовывайте его в клиентском компоненте (`"use client"`) или через клиентский динамический импорт.
- **Ресурсы самодостаточны** — SVG зубов и иконок встраиваются в JavaScript-бандл на этапе сборки; **не требуется** загрузка ресурсов во время выполнения и нечего дополнительно копировать в вашу публичную папку.
- **Один экземпляр на страницу** — состояние движка в настоящее время является одиночным объектом на уровне модуля, поэтому отрисовка двух экземпляров `<OdontogramShell>` на одной странице приведёт к тому, что они будут использовать общее состояние одной карты. Поддержка нескольких экземпляров запланирована на будущий релиз.

---

### ✨ Основные возможности
- 🖱️ Быстрый выбор и множественный выбор (CMD/CTRL + клик)
- 🦷 Типы зубов: постоянные, молочные, имплантаты, поддесневые, отсутствующие
- 🦷 Субстрат зуба (независимо от вида реставрации): натуральный, radix (корневой остаток), сломанный, подготовлен под коронку
- 👑 Реставрации по типу × материалу: коронка / вкладка (inlay) / накладка (onlay) / винир / мост из e.max, золота, gradia, циркония, металла, металлокерамики, телескопической конструкции или временной (накладка доступна только в окклюзионном виде) — выбираются из единого комбинированного селектора «Fix: Коронка – …» с минимумом кликов; устаревшие коронки `metal` автоматически мигрируют в `metal-ceramic` (металлокерамика); имплантаты используют ту же модель тип × материал, дополненную слоем имплантационного коннектора. Селектор ограничен видом зуба: имплантат предлагает только коронку/мост (плюс пять вариантов аттачментов, см. ниже); отсутствующий зуб/дефект предлагает только тело моста (плюс съёмный частичный/полный протез); субстрат `radix` полностью скрывает элемент управления реставрацией (на корневом остатке реставрация не может быть назначена)
- 🦿 Съёмное/аттачментное протезирование на отдельной оси `prosthesis` (записи «Kivehető:» в комбинированном селекторе): формирователь десны имплантата, локатор, локатор с покрывным протезом, балка, балка с покрывным протезом; съёмный частичный или полный протез с опорой на зубы
- 🌉 Зубы моста отображают одновременно коронковую часть и седловидный коннектор; оверлей многозубого мостовидного пролёта отрисовывает единый непрерывный, учитывающий дугу коннектор через последовательные зубы моста (тело + опоры) и межзубные промежутки между ними (верхняя и нижняя дуга используют зеркальную геометрию седла, сохраняя выравнивание коннектора на обеих дугах), включён в экспорт PNG/JPG/SVG; применение моста через шаблон статусов немедленно пересчитывает оверлей
- 🔍 Картирование кариеса на 6 поверхностях: мезиальная, дистальная, щёчная, язычная, окклюзионная, подкоронковая
- 🪥 Материалы пломб по поверхностям: амальгама, композит, стеклоиономер (СИЦ), временная
- 🏥 Единый объединённый селектор «Статус пульпы / эндо» (сгруппирован: витальная пульпа vs. леченная/эндо): эндодонтические состояния (лечебное пломбирование, пломбирование канала, неполное пломбирование канала, стекловолоконный штифт, металлический штифт) и диагноз пульпы по AAE (`pulpDx`: норма / обратимый / необратимый пульпит / некроз) взаимоисключающие — зуб с эндодонтическим лечением (установлен `endo`) не может одновременно нести диагноз витальной пульпы; при лечении `pulpDx` нормализуется к `normal`, а глиф поражённой пульпы скрывается. Обратимый пульпит отображается уменьшенным глифом пульпы. Опциональная 3-уровневая настройка детализации пульпы (`pulpDetailLevel`: simple / AAE / практический латинский) раскрывает 9 практических латинских подтипов пульпы (pulpa sana … gangraena pulpae) через `pulpLatin`; резекция и парапульпарный штифт остаются отдельными специальными индикаторами
- 🦴 Апикальный диагноз (`apicalDx`: симптоматический/бессимптомный апикальный периодонтит, острый/хронический апикальный абсцесс, конденсирующий остеит) напрямую определяет периапикальный глиф; уточняющий подтип очага гранулёма/киста отображается только при симптоматическом/бессимптомном апикальном периодонтите (избыточный подтип «абсцесс» убран — он уже покрывается апикальным диагнозом)
- 🩹 Объединённая карточка «Корень и пародонт» (единый сворачиваемый раздел для находок корня/периапикальной области и пародонта)
- ⚕️ Модификации: периапикальное воспаление (отображается только на отсутствующих зубах/лунках после удаления; скрыто на присутствующих зубах, где периапикальный глиф определяется исключительно `apicalDx`, и на имплантатах, где это покрывает `periImplant`), заболевание пародонта, степени подвижности (M1/M2/M3, скрыты на имплантатах)
- 🦷🔩 Статус периимплантных тканей (`periImplant`: `none` / `mucositis` / `peri-implantitis-mild` / `peri-implantitis-moderate` / `peri-implantitis-severe`) — стадирование по классификации Всемирного семинара 2018 года, отображается как отдельный селектор на имплантатах; мукозит использует глиф пародонтальной десны, периимплантит добавляет градуированный слой `peri-implant-bone-loss` (непрозрачность 0,4/0,7/1,0). Имплантаты больше не отображают глиф периапикального очага — их воспаление выражается через эту ось — а флажки пародонтальных модификаторов скрыты на имплантатах (специальное переименование флажка «Периимплантит» упразднено)
- 🏷️ Специальные индикаторы: требуется коронка, требуется замена коронки, закрытый дефект после удаления, планируемое удаление, герметизация фиссур, потеря контактного пункта
- 👁️ Переключение видимости: окклюзионный вид, зубы мудрости, кость и пульпа
- 🔢 12 фильтров выбора (все, присутствующие, постоянные, молочные, имплантаты, отсутствующие, верхние/нижние, фронтальные/моляры)
- 📊 Предустановленные статусные шаблоны (сброс, молочный прикус, сменный прикус, беззубый)
- 📦 34 предустановленных шаблона реставраций (мосты, съёмные протезы, балочные протезы с имплантатами)
- 💾 Экспорт/импорт статуса в JSON (версия 2.22; импорт по-прежнему принимает устаревшие версии 1.4 и 2.0–2.21 и автоматически мигрирует их, с пользовательскими состояниями плагинов и заметками к зубам)
- 💽 Опциональное сохранение состояния в localStorage (`enablePersistence`/`disablePersistence`/`clearPersistedState`/`isPersistenceEnabled`) — по умолчанию отключено; автоматически сохраняет статусную карту (и, опционально, карту плана) при каждом изменении состояния и восстанавливает её при следующем монтировании компонента, с ограничением размера 4 МБ и передачей ошибок хранения/парсинга в колбэк `onError` (или `console.warn`) вместо выброса исключения
- 🔗 Экспорт HL7 FHIR R4 (коллекция Bundle наблюдений по каждому зубу, кодирование зубов ISO 3950 для постоянного прикуса **и** молочных зубов (51–85, без потерь при обратном импорте), локальная система кодов, а также опциональный слой SNOMED CT (Настройки → SNOMED CT)); компонент кариеса с заданной тяжестью также несёт кодировку системы оценки — ICDAS на первичной (без пломбы) поверхности, CARS на рецидивирующей (с пломбой) С DX-11 вместе с ним передаётся и ПЛАН: каждое запланированное вмешательство становится `ServiceRequest` (`intent: "plan"`) с кодом намеченного клинического состояния зуба.
- ✚ Интерфейс выбора поверхностей «крест/плюс» (B/M/O/D/L) для кариеса и пломб
- 🧱 Материалы реставраций по поверхностям (смешанные пломбы, например щёчная амальгама + дистальный композит)
- 🖼️ Экспорт зубной карты в PNG/JPG/SVG (для загрузки; PNG/JPG растеризованы из векторного SVG)
- 🦷 Кариес/вторичный кариес как конечный автомат состояний по поверхностям: кариозная поверхность без пломбы отображается как первичный кариес (непрозрачность по уровням ICDAS); как только на этой поверхности появляется пломба, она отображается как вторичный (рецидивирующий) кариес (слой `subcaries-{surface}`, с оценкой CARS) — оба состояния никогда не активны одновременно на одной поверхности
- 🎯 Единая оценка тяжести по поверхности (`cariesSeverity`, 0–6, заменяет прежние отдельные поля глубины ICDAS и CARS): читается как глубина ICDAS на первичной поверхности, как именованная оценка CARS (Здоровый … Обширная полость) на рецидивирующей, через контекстное всплывающее окно, показывающее только шкалу, соответствующую текущему состоянию поверхности
- 🌱 Кариес корня (`rootCaries`: none / active / arrested / active-cavitated), задействующий отдельный слой изображения кариеса корня с непрозрачностью, зависящей от тяжести (active 0,5 / arrested 0,7 / active-cavitated полная непрозрачность)
- 📡 Рентгенологическая глубина кариеса (`radiographicDepth`: none / E1 / E2 / D1 / D2 / D3 по поверхности), независимая от визуальной шкалы тяжести ICDAS/CARS, отображается в виде значка и передаётся через собственное наблюдение FHIR
- 🎚️ Три настройки детализации кариеса (`secondaryCariesMode`, `rootCariesMode`, `radiographicDepthMode`) и переключатель `cariesDepthEnabled`, сворачивающие каждую шкалу в более простой вид выбора без потери сохранённого значения
- 🩹 Строка сводки по вторичному кариесу на панели пломб: под элементами управления пломбами перечисляет каждый выбранный зуб со вторичным кариесом и его поверхности (например, «На пломбе зуба 36 (O) обнаружен вторичный кариес.»)
- 🪛 Дефекты пломб по поверхностям (`fillingDefect`: none / marginal / fracture / wear) на прямых реставрациях, независимо от вторичного кариеса — задаются через индикатор по поверхностям на карточке пломб (по аналогии с индикатором глубины кариеса, список опций расположен вертикально), отображаются на карте, во всплывающей подсказке и в общей сводке пломб по всей полости рта с явной меткой (например, «36 (O) – Дефект пломбы: O: краевой»), так же как маркируется вторичный кариес в строке кариеса; карточка пломб также показывает подсказку для любого выбранного зуба с зарегистрированным дефектом пломбы (например, «У зуба 36 зарегистрирован дефект пломбы.»), аналогично существующей подсказке о вторичном кариесе
- 🦷💥 Стирание зубов, типизированное по клинической причине и локализации (`wearEdge`: none / attrition (истирание) / erosion (эрозия), режущий край/окклюзионная поверхность; `wearCervical`: none / abrasion (абразия) / abfraction (абфракция) / erosion (эрозия), пришеечная область) — заменяет два бинарных флага стирания при бруксизме; задаётся через два выпадающих списка в строке стирания, использует существующую графику стирания и отображается во всплывающей подсказке и в новом сводном разделе «Стирание» по всей полости рта
- 🎨 Изменение цвета зуба по причине (`discoloration`: none / tetracycline (тетрациклиновое) / fluorosis (флюороз) / nonvital (депульпированный) / extrinsic (внешнее) / other (другое)) на постоянных и молочных зубах — придаёт отображаемой натуральной коронке репрезентативный оттенок, когда зуб не имеет реставрации и натуральный субстрат; отображается во всплывающей подсказке и в новом сводном разделе «Изменение цвета» по всей полости рта; дополняет набор поверхностных и структурных состояний наряду с дефектами пломб и стиранием
- ✏️ Передние зубы (резцы/клыки) обозначают свою жевательную поверхность как «режущая» (incisal) во всём интерфейсе (выбор, всплывающее окно, сводки); сохранённый ключ поверхности остаётся `occlusal`
- 🔤 Позиционно-зависимая нотация поверхностей (Настройки → Детали зуба → «Нотация поверхностей», simple/full, по умолчанию full): в полном режиме буква и метка поверхности кариеса/пломбы следуют анатомии зуба — occlusal → I/incisal (режущая) на передних зубах, buccal → L/labial (губная) на передних зубах, lingual → P/palatal (нёбная) на верхних зубах и L/lingual (язычная) на нижних зубах (mesial/distal/subcrown не затрагиваются); упрощённый режим всегда использует общий набор B/M/O/D/L/SC независимо от положения зуба. Применяется к сводке по всей полости рта и к обоим селекторам поверхностей (кариес и дефект пломбы) (буква + подпись); сохранённый ключ поверхности не затрагивается
- 🦷↕️ Ортодонтическое картирование по зубам (`orthoAppliance`: none / bracket (брекет) / band (кольцо); `orthoDrift`: none / mesial (мезиальный) / distal (дистальный); `orthoVertical`: none / extrusion (экструзия) / intrusion (интрузия); `orthoRotation`: логическое значение) на присутствующем натуральном зубе (постоянном или молочном) — использует ранее не задействованную графику ортодонтии версии 2.5.0 (без новых SVG); отображается на карте, во всплывающей подсказке и в новом сводном разделе «Ортодонтия» по всей полости рта
- 🪨 Зубной камень, а также резорбция корня, типизируемая как внутренняя или наружная цервикальная (`resorptionType`)
- 📏 Глубина кариеса по поверхностям (поверхностный / дентин / глубокий), или опциональная оценка по ICDAS II (0–6) через `enableIcdas`
- 🩹 Переключатель краевой негерметичности коронки, отображается только при реставрации коронкой или мостом
- 🧬 Кодирование диагнозов по стандарту (WHO ICD-10, всегда включено): каждая зафиксированная находка формирует диагноз, закодированный по ICD-10 — кариес зубов (K02), кариес корня и приостановившийся кариес (K02.2/.3), пульпит и некроз пульпы (K04.0/.1), апикальный периодонтит, периапикальный абсцесс и радикулярная киста (K04.4–.9), стирание/абразия/эрозия/абфракция (K03.0–.8), зубной камень (K03.6), резорбция зуба (K03.3), изменение цвета зуба (K00.3/K00.8/K03.7), потеря зуба (K08.1), корневой остаток (K08.3) и перелом зуба (S02.5) — отображается во всплывающей подсказке и сводке по всей полости рта, экспортируется как ресурсы FHIR Condition.
- 🩺 Карточка «Диагнозы» по зубу: просматривайте выведенные ICD-10-диагнозы зуба и редактируйте их — подавите ошибочно выведенный диагноз или добавьте тот, который на карте не отражён. Итоговый набор (выведенные − подавленные + добавленные) определяет экспорт FHIR.
- 🗺️ Состояния случая / регионарные: фиксируйте диагнозы для всей полости рта, не привязанные к одному зубу — аномалия прикуса и болезни височно-нижнечелюстного сустава (K07), кисты полости рта (K09), болезни слюнных желёз (K11), стоматит и поражения слизистой оболочки рта (K12/K13), а также аномалии развития зубного ряда на уровне дуги (K00) — каждый диагноз можно опционально указать со стороной поражения (слева / справа / двусторонне).
- 🌍 Национальные пакеты кодирования (Настройки → Система кодирования диагнозов): накладывает национальную систему кодов поверх базы WHO ICD-10 — BNO-10 (венгерская, локализованное отображение; сохраняет код WHO) или US ICD-10-CM (перекодированные коды, например диапазон K07 для дентофациальных аномалий → M26). Добавление пакета для другой страны — это небольшая запись `CodingPack` — см. `CODING_PACKS.md`.
- 🔬 Слой SNOMED CT (Настройки → SNOMED CT, опционально, по умолчанию выключено): добавляет кодирование SNOMED CT наряду с кодом WHO и любым национальным пакетом, а также кодирует периимплантные находки, для которых нет кода WHO ICD-10. Коды ICD-10-CM и идентификаторы понятий SNOMED являются справочными/ориентировочными — перед клиническим использованием проверьте их по официальному табличному перечню ICD-10-CM / браузеру SNOMED CT.
- 🔁 Двусторонний обмен FHIR Condition: диагнозы экспортируются как ресурсы FHIR `Condition` (привязанные к зубу, а также состояния на уровне пациента с полем `bodySite` для указания стороны поражения) наряду с Observation, а импорт восстанавливает их — состояния случая напрямую, а переопределения добавления/подавления по зубам — путём сравнения (diff) импортированных Condition с заново выведенной картой.
- 🩺 **Карточка диагнозов, обновлена:** в каждой строке диагноза по зубу сначала показан код ICD-10 (`K04.0 Pulpitis`), строки отсортированы по коду. У каждой строки есть переключатель **исключить** (убирает диагноз из экспорта FHIR, но оставляет его на карте) и кнопка **удалить** (×), которая убирает диагноз *и* соответствующую находку на зубе. Добавление диагноза из выбора записывает и лежащую в основе находку на карте, поэтому значок появляется сразу.
- 🗂️ **Всплывающее окно диагнозов случая/региональных диагнозов:** диагнозы для всей полости рта и региональные диагнозы (аномалии челюсти, кисты, заболевания слюнных желёз и слизистой…) перенесены из боковой панели пародонта в отдельный диалог, открываемый кнопкой **Диагнозы** рядом с переключателем Одонтограмма / Пародонтальный статус; выбор в нём отсортирован по коду, код указывается первым.
- 🇭🇺 **Пакет BNO-10:** отображаемые венгерские названия теперь являются официальными наименованиями BNO-10 (NEAK), а пакет использует стандартный URI системы ICD-10 (BNO-X идентичен ICD-10 ВОЗ).
- ✅ **Экспорт FHIR, чистый для валидаторов HL7:** каждая запись Bundle несёт детерминированный `id` и абсолютный `fullUrl` (без заполнителей `urn:uuid`), а Bundle включает в себя собственную **CodeSystem** движка, чтобы его локальные коды разрешались при валидации; та же CodeSystem опубликована в репозитории как `fhir/CodeSystem-odontogram.json` (передайте `includeCodeSystem: false` в опциях экспорта FHIR, чтобы её опустить).
- 🎯 **Коды ICD, следующие за картой (специфичность на основе данных).** Глубина кариеса берётся из рентгенологической глубины, если она зафиксирована (E1/E2 → эмаль, D1–D3 → дентин), а иначе — из тяжести по ICDAS как запасного варианта (1–3 → эмаль, 4–6 → дентин); это уточняет код ВОЗ `K02` до `K02.0` / `K02.1`, а код ICD-10-CM `K02.9` — до `K02.51/.52` (ямки и фиссуры) или `K02.61/.62` (гладкая поверхность) в зависимости от поверхности × глубины. Хронический пародонтит получает свой код ICD-10-CM из стадии и распространённости 2017 года (`K05.311`–`K05.329`). Одна Condition на зуб, с самым глубоким поражением.
- 🔄 **Пародонтальные данные теперь возвращаются и через FHIR.** Импорт считывает пародонтальные панели LOINC 74029-0 обратно в каждый зуб — глубину зондирования, десневой край (восстановленный из CAL, поэтому значения псевдокармана сохраняются), BOP, фуркацию, налёт по O'Leary, индексы PI/GI и имплантатные mPI/mBI, а также ширину кератинизированной десны — плюс доказательные Observation по статусу курения и HbA1c. Также распознаются Condition, закодированные только в ICD-10-CM или SNOMED CT, так что чужой bundle импортирует всё, что может.
- 🧬 **SNOMED CT для всего каталога диагнозов.** 48 из 50 позиций теперь несут проверенное понятие SNOMED CT International (активное, в основном модуле, с соответствующим FSN). Два остаются намеренно незаполненными, поскольку у SNOMED International нет для них обобщающего понятия: аномалия размера челюсти (K07.0) и дентофациальные функциональные нарушения (K07.5). Наложение SNOMED остаётся опциональным в настройках.
- 📦 **Загружаемый пакет терминологии FHIR.** Папка `fhir/` в репозитории — это FHIR NPM-пакет (`react-advanced-odontogram.fhir`, FHIR 4.0.1), содержащий CodeSystem движка и сгенерированные ValueSet — по одному на каждую группу значений клинической оси, один для типов находок и один сводный набор всех кодов. Направьте на него валидатор через `-ig ./fhir`.
- 🧰 Унифицированная панель иконок в верхней части с вкладками в модальном окне настроек (Общие / Панели / Детали зуба / Кариес / Пульпа / Заметки / Периодонтальный — нумерация, заметки, видимость панелей, ICDAS, переключатель глубины кариеса, детализация кариеса корня/рентгенологической глубины, уровень детализации пульпы, уровень детализации стирания/изменения цвета зубов, информация о зубах)
- 🗂️ Вкладка настроек «Панели»: независимое отображение/скрытие сводных панелей «Статусы» и «Ортодонтия» по всей полости рта
- 🦷🩺 Вкладка настроек «Периодонтальный»: 16 переключателей показать/скрыть для каждого индекса строк пародонтальной карты (сгруппированы по карманам/гигиене/мукогингивальным параметрам/поддержке/периимплантным индексам — PD/GM/CAL/BOP, зубной налёт, PI, GI, видимость CEJ, вогнутость корня, KG, GT, фуркация, подвижность, класс Миллера, mPI, mBI), каждый с описанием, плюс режим отображения названия индекса переведённое/каноническое (каноническое = фиксированное научное название на английском/латыни на любом языке интерфейса; всплывающие подсказки всегда остаются локализованными независимо от этой настройки). Обе настройки — предпочтения на уровне приложения (как `perioViewMode`) — никогда не входят в экспортируемый payload
- 🩹 Настройки вторичного кариеса (CARS) объединены с вкладкой настроек кариеса, размещены над рентгенологической глубиной (отдельная вкладка «Вторичный кариес» упразднена)
- 🎚️ Уровень детализации в разделе «Детали зуба» (Настройки → Детали зуба): настройка simple/complex для стирания зубов и для изменения цвета. Упрощённый режим показывает переключатель да/нет для каждой находки (стирание вкл. → attrition/abrasion, изменение цвета вкл. → other); сложный режим (по умолчанию) сохраняет выпадающие списки типа/причины, при этом сохранённое значение сохраняется при переключении уровней
- 📋 Панель информации о зубах: текстовое сводное описание всей одонтограммы в реальном времени (количество зубов, списки присутствующих/отсутствующих, кариес вкл. вторичный, пломбы, каналы, протетика, имплантаты, состояние пародонта) — отображается по умолчанию, переключается в настройках
- 🗂️ Сводное выпадающее меню экспорта (Статус JSON / FHIR / PNG / JPG)
- 📥 Выпадающее меню импорта с поддержкой FHIR (загружает ранее экспортированные Bundle)
- ⏳ Индикатор прогресса при экспорте изображения
- 🎓 Интерактивное обучение из 18 шагов
- 🔢 Три системы нумерации (FDI, Universal, Palmer)
- 🌐 Интернационализация — 12 языков интерфейса (HU/EN/DE/ES/IT/SK/PL/RU/PT-BR/AR/ZH/FR) с переключателем языков; арабский отображает интерфейс справа налево, а зубные/пародонтологические карты закреплены слева направо (машинный перевод, проверка носителем языка для AR/ZH/FR ожидается); в основной бандл входит только английский — каждый другой язык — отдельный чанк, загружаемый при первом выборе
- 🌗 Поддержка тёмного режима с кнопкой переключения (автономный или управляемый родительским приложением)
- 🎨 Настраиваемая тема (`themeConfig` prop) с CSS-переменными (`--odon-*`)
- 📱 Мобильный сенсорный интерфейс: всплывающее окно по касанию для масштабирования, контекстное меню по долгому нажатию, масштабирование жестом «щипок», сенсорные цели WCAG 44 пикселя, переключение зубной дуги
- 🔌 Система пользовательских плагинов SVG: визуальные наложения, пользовательское состояние каждого зуба, поддержка экспорта/импорта JSON — вывод `renderSvg()` плагина санитизируется библиотекой DOMPurify (SVG-профиль) перед вставкой в живую карту; плагины по-прежнему выполняются как доверенный код, поэтому загружайте плагины только из источников, которым вы доверяете
- 🛡️ Content-Security-Policy: production-сборка демо-приложения внедряет CSP-мета-тег (dev-сервер не затрагивается) — приложения-хосты, встраивающие компонент, должны задавать собственную CSP
- ⚠️ Предупреждения о несовместимых комбинациях состояний зуба
- 🏷️ Автоматические подсказки о состоянии на ячейках зубов (отображают все активные состояния)
- 🩺 Модернизированная всплывающая подсказка по зубу и сводная панель по всей полости рта: обе отображают полный набор клинических находок (диагноз пульпы/апикальный + подтип очага, резорбция корня, статус периимплантных тканей, градуированный кариес корня, зубной камень, краевая негерметичность коронки, перелом, потеря контакта, типизированное стирание края/пришеечной области), с отдельным разделом «Диагнозы» на панели, отдельным разделом «Стирание», а также приблизительным качественным показателем тяжести кариеса (поверхностный/умеренный/глубокий)
- ♿ Клавиатурная доступность (WCAG): роли ARIA listbox/option, выбор клавишами Enter/Пробел, навигация клавишами-стрелками, видимые контуры фокуса
- 🔒 Режим «только чтение»: отключение всех взаимодействий для печати, отчётов или просмотра
- ✨ Анимация выбора: пульсирующая пунктирная рамка и светящаяся тень на выбранных зубах (с поддержкой prefers-reduced-motion)
- 📝 Заметки к зубам: двойной щелчок для добавления/редактирования, значок заметки рядом с номером зуба, всплывающая подсказка с текстом заметки, строка «Индивидуальные заметки» в сводной панели по всей полости рта, включение в PDF-отчёт, экспорт/импорт в JSON
- 🔀 Разделение карты «Статус ↔ План»: переключатель `Статус | План` в заголовке карты переключает между текущей картой **статуса** и картой **плана** (предполагаемое лечение), каждая со своими состояниями зубов; карта плана при первом переключении на неё начинается как копия статуса, и изменения в одной карте никогда не влияют на другую. Экспорт/импорт (`exportStatus`/`exportFhir`/импорт файла) всегда относятся к карте статуса; карта плана читается/записывается отдельно через собственный API (см. Публичный API ниже) и — если она отличается от статуса — включается в экспорт JSON как дополнительный раздел `plan`
- 📝 Блок «Что изменится»: если план отличается от текущего статуса, блок под панелью информации о зубах перечисляет каждое различие по зубу и по оси лечения (наличие, субстрат, реставрация, протезирование, планируемая коронка, ортодонтия, пульпа/эндо, апикальный статус) в виде строки `зуб: ось  из → в`; также доступен программно через `getPlanChanges()`

![Пародонтологическая карта всей полости рта (русский)](screenshot_ru_perio.png)

- 🩺 Пародонтологическое картирование: **глубина зондирования (PD)**, **десневой край (GM)**, **кровоточивость при зондировании (BOP)** (+ суппурация) по каждой из шести стандартных точек на зуб, с производным **уровнем клинического прикрепления (CAL = PD + десневой край)**, рецессией и общим показателем **%BOP** по всей полости рта. **Графическая пародонтальная карта на всю полость рта** — каждая дуга отрисовывается как **два отдельных SVG — щёчный и нёбный/язычный** (переиспользуют графику зуба с единой ориентацией «коронки к центральной полосе» на обеих сторонах; отдельная **графика имплантата** для зубов-имплантатов) с красной **линией CEJ**, **пронумерованной миллиметровой координатной сеткой** и **кривой десневого края / глубины кармана** над зубами, разделённой **центральной полосой пародонтальных индексов** (с подписью `▲ Buccal … Lingual/Palatal ▼`), несущей общие для обеих сторон индексы по зубу — **класс Миллера** в самом верху, а **зубной налёт/PI/GI/mPI/mBI** отображаются как **анатомическая ромбовидная плитка** на зуб (щёчная вершина вверх, язычная — вниз, мезиальная/дистальная стороны в среднем ряду меняются местами в зависимости от стороны, так что мезиальная сторона всегда направлена к средней линии дуги); числовые строки (полные названия индексов — PD/GM/CAL/BOP + подвижность + фуркация — в более крупных, удобных для касания ячейках) выровнены по столбцам, плюс сводка (средние PD/CAL, %BOP, PI%), с вводом через **автоматический переход по клавиатуре**; карта **динамически масштабируется, заполняя доступную ширину**, адаптивна при любом размере окна. Представлена как **переключатель вида** «Одонтограмма | Пародонтальный статус», правая панель которого при активном виде переоформляется в **боковую панель пародонтального контекста** (данные пациента, классификация 2017 года и сводка по всей полости рта) (опция в настройках переключает всё представление обратно во **всплывающее окно**), а также остаётся **отдельно вызываемым компонентом** (экспорт `PerioChart`), так что принимающее приложение может вызывать пародонтальную карту независимо от базовой одонтограммы. Экспорт **FHIR** по каждой точке через пародонтальную панель LOINC (`74029-0`; PD `32910-2`, рецессия `32911-0`, CAL `32912-8`)
- 🅿️ Предлагаемое оформление: в режиме «План» находки, которые план **добавляет** по сравнению с текущим статусом (планируемая коронка, удаление, ортодонтическое перемещение, протез, …), отображаются отдельным **пунктирным, тонированным контуром «предлагается»**, чтобы план читался как намерение, а не как факт — с легендой «пунктир = предлагается» на карточке карты. Отображение в режиме «Статус» побайтово идентично; лечение существует только в плане и полностью сбрасывается при переключении обратно
- 🚦 Ограничение режима «План»: карта «План» показывает только то, что стоматолог может *сделать* — базовый выбор предлагает только «Отсутствует» / «Постоянный» / «Имплант», а находки, относящиеся только к статусу (кариес, стирание зубов, изменение цвета, а также весь пародонтальный блок — подвижность, сетка зондирования по шести точкам, модификаторы воспаления/пародонта, зубной камень, статус периимплантных тканей), скрыты; элемент управления пульпой/эндодонтией сохраняет эндодонтическое **лечение** (пломбирование корневого канала / штифт / апикоэктомия / парапульпарный штифт), но скрывает **диагноз** пульпы/апикальной области и резорбцию корня. Реставрация, протезирование, ортодонтия, необходимость/замена коронки и план удаления остаются доступными для планирования
- 🧪 Обширный набор автоматических тестов Vitest: нумерация, переводы, шаблоны, i18n, компонент App, тема, сенсорный ввод, плагины, доступность и паритет клинических/диагностических осей
- 📖 Документация API TypeDoc с JSDoc-комментариями ко всем публичным экспортам (`npm run docs`)

### 📦 Модули
- 🦷 Сетка одонтограммы и интерфейс ячеек зубов
- 🎛️ Панель управления и статуса
- 🎨 Движок слоёв SVG и шаблоны
- 🔢 Нумерация зубов и сопоставление меток (FDI/Universal/Palmer)
- 🌐 Локализация — 12 языков интерфейса (HU/EN/DE/ES/IT/SK/PL/RU/PT-BR/AR/ZH/FR), включая арабский (RTL)
- 💾 Экспорт/импорт статуса
- 📋 Дополнительные статусы: предустановленные шаблоны реставраций
- 🎨 Настройка темы: настраиваемая цветовая палитра через CSS-свойства `--odon-*`
- 📱 Мобильные сенсорные взаимодействия (масштабирование по касанию, долгое нажатие, масштабирование жестом «щипок», переключение дуги)
- 🔌 Система пользовательских плагинов SVG
- ⚠️ Система валидации состояний и подсказок
- ♿ Клавиатурная доступность и поддержка ARIA
- 🔒 Режим «только чтение»
- ✨ Анимация выбора
- 📝 Система заметок к зубам
- 🧪 Набор автоматизированных тестов (Vitest + Testing Library)

### 🛠️ Элементы управления интерфейса

**🔝 Верхняя панель:**
- Переключатель языков (выпадающий список HU/EN/DE/ES/IT/SK/PL/RU/PT-BR/AR/ZH/FR)
- Кнопка переключения тёмного режима (иконка солнца/луны, переключает между светлой и тёмной темой)
- Переключатель системы нумерации (выпадающий список FDI/Universal/Palmer)
- Кнопки «Экспорт статуса» / «Импорт статуса»

**📊 Заголовок карты:**
- Переключатель окклюзионного вида
- Переключатель видимости зубов мудрости
- Переключатель видимости кости
- Переключатель видимости пульпы
- Кнопка сброса выделения

**🔍 Фильтры выбора:**
- Все / Все присутствующие / Постоянные / Молочные / Имплантаты / Все отсутствующие
- Верхние / Верхние 6 фронтальных / Верхние моляры
- Нижние / Нижние 6 фронтальных / Нижние моляры

**📋 Статусные шаблоны:**
- Сбросить всё (сброс полости рта)
- Молочный прикус
- Сменный прикус
- Переключатель «Беззубый»

**📦 Выпадающее меню дополнительных статусов:**
- Верхние/нижние циркониевые мосты (12-22, 13-23, 16-26, полная дуга)
- Верхние/нижние металлические мосты (12-22, 13-23, 16-26, полная дуга)
- Верхние/нижние частичные съёмные протезы
- Верхние/нижние полные съёмные протезы
- Верхние/нижние балочные протезы с имплантатами

**🦷 Панель редактора зуба** (для выбранного зуба/зубов, сгруппирована в сворачиваемые карточки):
- **Базовая строка:** выбор зуба (базовый тип, включая варианты сломанной коронки) и субстрат зуба (натуральный/radix/сломанный/подготовлен под коронку)
- **Строка реставрации:** комбинированный выпадающий список реставрации «Fix: …» / «Kivehető: …» (фиксированные опции `restorationType`×`restorationMaterial` плюс опции аттачмента/съёмного протеза `prosthesis`, ограниченные видом зуба); флажок краевой негерметичности коронки (только коронка/мост); флажки локализации сломанной коронки; переключатели «требуется коронка» / «требуется замена коронки»
- **Строка стирания и изменения цвета:** выпадающий список типа стирания режущего края/окклюзионной поверхности, выпадающий список типа пришеечного стирания, выпадающий список причины изменения цвета (каждый переключается на простой переключатель да/нет в режиме Настройки → Детали зуба → упрощённый режим)
- **Карточка ортодонтии:** аппарат, мезиальный/дистальный дрейф, вертикальное перемещение (экструзия/интрузия), переключатель ротации — отображается на присутствующем натуральном зубе
- **Карточка кариеса:** выпадающий список режима глубины кариеса, флажок подкоронкового кариеса, выпадающий список тяжести кариеса корня и селектор поверхностей кариеса B/M/O/D/L с контекстным всплывающим окном глубины ICDAS/CARS и значком рентгенологической глубины
- **Карточка пломб:** выпадающий список материала пломбы, селектор пломбы по поверхностям (с материалом по каждой поверхности), индикатор дефекта пломбы по поверхностям (краевой/перелом/стирание), подсказки о вторичном кариесе и дефекте пломбы
- **Карточка «Корень и пародонт»:** объединённый селектор «Статус пульпы / эндо», селектор апикального диагноза, селектор подтипа периапикального очага (только симптоматический/бессимптомный апикальный периодонтит), селектор типа резорбции корня, селектор степени подвижности, селектор статуса периимплантных тканей (только имплантаты)
- **Специальные индикаторы:** план/лунка после удаления, закрытый дефект после удаления, герметизация фиссур, потеря контактного пункта, зубной камень, парапульпарный штифт, эндодонтическая резекция, опора моста

### 🦷 Типы зубов и состояния

**Выбор зуба (базовый тип):**
| Значение | Описание |
|---|---|
| `none` | Отсутствующий зуб |
| `tooth-base` | Постоянный зуб |
| `milktooth` | Молочный зуб |
| `implant` | Имплантат |
| `tooth-under-gum` | Поддесневой (непрорезавшийся) зуб |

**Варианты сломанного зуба:**
`tooth-broken-inicisal`, `tooth-broken-distal-inicisal`, `tooth-broken-distal`, `tooth-broken-mesial-distal-inicisal`, `tooth-broken-mesial-distal`, `tooth-broken-mesial-inicisal`, `tooth-broken-mesial`, `no-tooth-after-extraction`

**Субстрат зуба (постоянные зубы):**
`natural` (по умолчанию), `radix` (корневой остаток), `broken`, `crownprep` (подготовлен под коронку)

**Тип реставрации (постоянные зубы):**
`none`, `crown`, `inlay`, `onlay` (только окклюзионный вид), `veneer`, `bridge`

**Материал реставрации (постоянные зубы):**
`none`, `emax`, `gold`, `gradia`, `zircon`, `metal`, `metal-ceramic` (устаревшие коронки `metal` мигрируют сюда), `telescope`, `temporary`

**Опции реставрации ограничены видом зуба** (`restorationOptions()` в `src/registry/restorations.ts`): имплантат предлагает только типы реставрации `crown`/`bridge` (в сочетании со слоем имплантационного коннектора) плюс пять записей аттачмента `prosthesis`, перечисленных ниже; отсутствующий зуб/дефект предлагает только тело моста `bridge` плюс две записи съёмного протеза `prosthesis`; субстрат `radix` полностью скрывает элемент управления реставрацией. Устаревшие плоские поля `crownMaterial`/`bridgeUnit` (значения аттачмента имплантата/моста до версии 1.14) исключены из актуальной модели — принимаются только как путь миграции для чтения старых данных (только для чтения).

**Протезирование** (`prosthesis`; независимая ось съёмного/аттачментного протезирования, отображается как записи «Kivehető:» в комбинированном выпадающем списке реставрации):
`none`, `healing-abutment`, `locator`, `locator-denture`, `bar`, `bar-denture` (аттачменты имплантата, с покрывным протезом или без него), `removable-partial`, `removable-full` (съёмные протезы с опорой на зубы на отсутствующем зубе/дефекте). У зуба может быть либо фиксированная реставрация, либо протез, но никогда оба одновременно — установка одного очищает другое.

**Краевая негерметичность коронки** (`crownLeakage`; логическое значение): отображается только когда `restorationType` равен `crown` или `bridge`; активирует слой изображения `crown-leakage`.

**Эндодонтические варианты (постоянные зубы):**
`none`, `endo-medical-filling`, `endo-filling`, `endo-filling-incomplete`, `endo-glass-pin`, `endo-metal-pin`

**Эндодонтические варианты (молочные зубы):**
`none`, `endo-medical-filling`

`endo` и `pulpDx` отображаются через единый объединённый селектор `<select>` «Статус пульпы / эндо» (сгруппирован: витальная пульпа vs. леченная/эндо) и являются взаимоисключающими — выбор леченной опции (`endo != none`) сбрасывает `pulpDx` в `normal`, а выбор диагноза пульпы сбрасывает `endo` в `none`.

**Материалы пломб (постоянные зубы):**
`amalgam`, `composite`, `gic`, `temporary`

**Материалы пломб (молочные зубы):**
`composite`, `gic`, `temporary`

**Поверхности пломбирования/кариеса:**
`mesial`, `distal`, `buccal`, `lingual`, `occlusal`, `subcrown` (только для кариеса)

**Модификации:**
`inflammation` (периапикальное), `parodontal` (пародонтальное), `mobility` (M1/M2/M3)

**Тип периапикального очага** (`periapicalType`; уточняет периапикальный глиф, отображается только при симптоматическом/бессимптомном апикальном периодонтите):
`none`, `granuloma`, `cyst` — опции для назначения; устаревшее значение `abscess` по-прежнему принимается/сохраняется, но больше не предлагается в селекторе, так как дублирует апикальный диагноз. При импорте оно отбрасывается: сворачивается в `apicalDx`, если у зуба установлен модификатор воспаления, иначе очищается в `none`

**Диагноз пульпы** (терминология AAE; `pulpDx`):
`normal`, `reversible-pulpitis` (отображается уменьшенным глифом пульпы), `irreversible-pulpitis`, `necrosis` — взаимоисключающий с `endo`; нормализуется в `normal` на зубе с эндодонтическим лечением

**Диагноз пульпы, практический латинский** (`pulpLatin`; селектор пульпы показывает его только когда `pulpDetailLevel` равен `latin`):
`none`, `pulpa-sana`, `hyperaemia-pulpae`, `pulpitis-acuta-serosa`, `pulpitis-acuta-purulenta`, `pulpitis-chronica-clausa`, `pulpitis-chronica-ulcerosa`, `pulpitis-chronica-hyperplastica`, `necrosis-pulpae`, `gangraena-pulpae`

**Уровень детализации пульпы** (`pulpDetailLevel`, глобальная настройка): `simple`, `aae` (по умолчанию), `latin` — определяет, какой словарь предлагает селектор пульпы

**Апикальный диагноз** (`apicalDx`; определяет периапикальный глиф):
`normal`, `symptomatic-apical-periodontitis`, `asymptomatic-apical-periodontitis`, `acute-apical-abscess`, `chronic-apical-abscess`, `condensing-osteitis`

**Тип резорбции корня** (`resorptionType`):
`none`, `internal`, `external-cervical`

**Статус периимплантных тканей** (`periImplant`; только для имплантатов, стадирование по классификации Всемирного семинара 2018 года): `mucositis` использует глиф пародонтальной десны; `peri-implantitis-*` добавляет слой `peri-implant-bone-loss` с непрозрачностью, зависящей от степени тяжести (лёгкая 0,4 / средняя 0,7 / тяжёлая 1,0). Имплантаты больше не отображают глиф периапикального очага (их воспаление выражается через эту ось), а флажки модификаторов `mods` воспаления/пародонта скрыты на имплантатах:
`none`, `mucositis`, `peri-implantitis-mild`, `peri-implantitis-moderate`, `peri-implantitis-severe`

**Тяжесть кариеса** (`cariesSeverity`; единое поле по поверхности, `0`–`6`): на поверхности без пломбы читается как шкала глубины кариеса ICDAS (`superficial` / `dentin` / `deep`, либо необработанные коды ICDAS II `0–6` при включённом `enableIcdas`) и отображает первичный слой `caries-{surface}`; на поверхности с пломбой читается как именованная оценка CARS (`0` здоров … `6` обширная полость) и вместо этого отображает слой `subcaries-{surface}` (вторичный кариес) — поверхность никогда не является одновременно первичной и рецидивирующей

**Кариес корня** (`rootCaries`; задействует слой изображения `caries-root` на присутствующем зубе, с непрозрачностью, зависящей от тяжести — `active` 0,5 / `arrested` 0,7 / `active-cavitated` полная непрозрачность):
`none`, `active`, `arrested`, `active-cavitated`

**Рентгенологическая глубина кариеса** (`radiographicDepth`; по поверхности, независима от визуальной шкалы ICDAS/CARS `cariesSeverity`):
`none`, `E1`, `E2`, `D1`, `D2`, `D3`

**Настройки детализации кариеса** (глобальные): `secondaryCariesMode` (`simple`/`standard`/`full`, по умолчанию `standard`), `rootCariesMode` (`simple`/`severity`, по умолчанию `simple`), `radiographicDepthMode` (`off`/`threeLevel`/`detailed`, по умолчанию `off`), `cariesDepthEnabled` (логическое значение, по умолчанию `true`) — каждая настройка сворачивает свою шкалу в более простой вид выбора, не изменяя сохранённое значение

**Специальные индикаторы:**
`crownNeeded`, `crownReplace`, `missingClosed`, `extractionPlan`, `extractionWound`, `bridgePillar`, `fissureSealing`, `contactMesial`, `contactDistal`, `endoResection`, `calculus`, `parapulpalPin`

**Стирание зубов** (`wearEdge`, `wearCervical`; клинический тип по локализации, ограничен постоянным зубом без реставрации и с натуральным субстратом; задействуют существующие слои `tooth-bruxism-wear`/`tooth-bruxism-neck-wear`):
`wearEdge`: `none`, `attrition`, `erosion` — `wearCervical`: `none`, `abrasion`, `abfraction`, `erosion`

**Изменение цвета** (`discoloration`; причина по зубу, ограничена натуральным постоянным или молочным зубом без реставрации и с натуральным субстратом; окрашивает заливку отображаемой натуральной коронки — без нового SVG):
`none`, `tetracycline`, `fluorosis`, `nonvital`, `extrinsic`, `other`

**Дефект пломбы** (`fillingDefect`; по поверхности, находка прямой реставрации, независимая от вторичного кариеса — ограничена поверхностями, присутствующими в `fillingSurfaceMaterials`; отображает слой изображения `defect-{surface}`):
`none`, `marginal`, `fracture`, `wear`

**Ортодонтия** (`orthoAppliance`, `orthoDrift`, `orthoVertical`, `orthoRotation`; по зубам, ограничена присутствующим натуральным зубом — постоянным или молочным):
`orthoAppliance`: `none`, `bracket`, `band` — `orthoDrift`: `none`, `mesial`, `distal` — `orthoVertical`: `none`, `extrusion` (глиф стрелки вверх), `intrusion` (глиф стрелки вниз) — `orthoRotation`: логическое значение

**Настройки детализации/нотации зуба** (глобальные настройки сессии, Настройки → Детали зуба): `wearDetailLevel` и `discolorationDetailLevel` (`ToothDetailLevel`: `simple`/`complex`, по умолчанию `complex` — упрощённый режим показывает переключатель да/нет вместо полного выпадающего списка типа/причины, не изменяя сохранённое значение) и `surfaceNotation` (`simple`/`full`, по умолчанию `full` — определяет, зависят ли буквы/метки поверхностей кариеса/пломбы от положения зуба; см. «Позиционно-зависимая нотация поверхностей» выше)

### ⚙️ Настройки
Открывается по значку шестерёнки на верхней панели; модальное окно `dialog` ARIA с блокировкой фокуса и вкладками (Esc/клик по фону для закрытия, стрелки для переключения вкладок). Все настройки являются состоянием интерфейса только на уровне сессии, если не указано иное, — ни одна из них не изменяет данные по зубам или экспортируемый payload.

- **Общие:** система нумерации (FDI/Universal/Palmer), язык, тёмная/светлая тема, видимость панели информации о зубах
- **Одонтограмма:** профиль анатомии зубов (`classic` по умолчанию / `measured`) — `measured` отображает шестнадцать шаблонов зубов, измеренных по литературе, — по одному на каждую клинически различную позицию, так что каждый моляр несёт собственный контур коронки и собственное число корней, — в двухдуговой раскладке с индивидуальной шириной зуба; переключается во время работы, не влияет на значение по умолчанию `classic`; его графика вынесена в отдельный чанк и загружается только при переключении, поэтому вариант `classic` по умолчанию ничего не стоит; зуб, отмеченный как молочный, отрисовывается из собственного молочного шаблона (позиции 1-5), а не как слой внутри постоянного рисунка — и в зубной формуле, и в пародонтальной карте
- **Панели:** независимое отображение/скрытие сводной карточки «Статусы» и карточки «Ортодонтия» по всей полости рта (обе по умолчанию видимы)
- **Детали зуба:** уровень детализации стирания и уровень детализации изменения цвета (simple/complex, каждый по умолчанию complex), нотация поверхностей (simple/full, по умолчанию full)
- **Кариес:** переключатель оценки ICDAS II (`enableIcdas`), переключатель глубины кариеса (`cariesDepthEnabled`), детализация кариеса корня (`rootCariesMode`: simple/severity), детализация вторичного кариеса/CARS (`secondaryCariesMode`: simple/standard/full), детализация рентгенологической глубины (`radiographicDepthMode`: off/threeLevel/detailed) — прежняя отдельная вкладка «Вторичный кариес» объединена с этой, элемент управления CARS расположен непосредственно над рентгенологической глубиной
- **Пульпа:** уровень детализации пульпы (`pulpDetailLevel`: simple/AAE/практический латинский, по умолчанию AAE) — определяет, какой словарь предлагает селектор «Статус пульпы / эндо»; изменение сразу обновляет сводку по всей полости рта и все открытые всплывающие подсказки
- **Заметки:** включение/отключение заметок к зубам (`enableNotes`)
- **Периодонтальный:** переключатели показать/скрыть для каждого из 16 индексов строк пародонтальной карты (`perioRowVisibility`, по умолчанию все видимы), сгруппированные по Карманам (PD/GM/CAL/BOP) / Гигиене (зубной налёт/PI/GI) / Мукогингивальным параметрам (видимость CEJ/вогнутость корня/KG/GT) / Поддержке (фуркация/подвижность/класс Миллера) / Периимплантным индексам (mPI/mBI), каждая строка со своим описанием; плюс режим названия индекса переведённое/каноническое (`perioIndexNameMode`: `translated` по умолчанию / `canonical` — фиксированное научное название на английском/латыни, отображаемое на любом языке интерфейса). Только предпочтения на уровне приложения (аналогично `perioViewMode`) — никогда не сериализуются, всплывающие подсказки остаются локализованными в обоих режимах

### 🖼️ Система SVG-шаблонов

**Шаблоны зубов** (в `src/assets/teeth-svgs/`):
| Шаблон | Применяется для зубов |
|---|---|
| `11.svg` | 11, 12, 21, 22, 31, 32, 41, 42 (резцы) |
| `13.svg` | 13, 23, 33, 43 (клыки) |
| `14.svg` / `14_occl.svg` | 14, 15, 24, 25, 34, 35, 44, 45 (премоляры) |
| `16.svg` / `16_occl.svg` | 16, 17, 18, 26, 27, 28, 36, 37, 38, 46, 47, 48 (моляры) |

Шаблоны поворачиваются на 180 градусов для нижней челюсти и отражаются горизонтально для левой стороны.

**Иконки SVG** (в `src/assets/icon-svgs/`):
`icon_8.svg` (зубы мудрости), `icon_gum.svg` (кость), `icon_no_selection.svg` (сброс), `icon_occl.svg` (окклюзионный вид), `icon_pulp.svg` (пульпа)

### 🔢 Системы нумерации

**FDI (ISO 3950):** Зубы взрослых 11-18, 21-28, 31-38, 41-48. Молочные зубы 51-55, 61-65, 71-75, 81-85.

**Universal (США):** Зубы взрослых пронумерованы 1-32. Молочные зубы обозначены буквами A-T.

**Palmer (Zsigmondy-Palmer):** Формат квадрант + позиция (например, UR-1, LL-5). Молочные зубы обозначены буквами A-E в каждом квадранте.

### 🚀 Использование
Разработка:
```bash
npm install
npm run dev
```
Сборка:
```bash
npm run build
```
Предварительный просмотр:
```bash
npm run preview
```

### 🔗 Интеграция
Компонент можно встроить в любое приложение React.
Пример:
```tsx
import App from "./App";

export default function Host(){
  return (
    <App
      language="ru"
      onLanguageChange={(lang) => console.log(lang)}
      numberingSystem="FDI"
      onNumberingChange={(system) => console.log(system)}
      darkMode={false}
      onDarkModeChange={(dark) => console.log(dark)}
    />
  );
}
```

**Интеграция тёмного режима:**
- **Автономный режим:** Опустите prop `darkMode` — компонент самостоятельно управляет состоянием темы через кнопку переключения на панели и добавляет/удаляет класс `.dark` на элементе `<html>`.
- **Управляемый режим:** Передайте `darkMode` и `onDarkModeChange` — родительское приложение управляет темой. Кнопка переключения по-прежнему отображается, но вызывает `onDarkModeChange` вместо управления внутренним состоянием. Родительское приложение отвечает за добавление/удаление класса `.dark` на элементе `<html>`.

**Пользовательская тема:**
```tsx
<App
  themeConfig={{
    colors: {
      accent: '#e74c3c',
      background: '#fafafa',
      text: '#222222',
    },
  }}
/>
```

**Интеграция плагинов:**
```tsx
import App, { type OdontogramPlugin, setPluginState } from "./App";

const myPlugin: OdontogramPlugin = {
  id: "implant-brand",
  label: { en: "Implant Brand", hu: "Implantátum márka" },
  layer: "overlay",
  renderSvg: (toothNo, _quadrant, state) => {
    if (!state) return null;
    return `<text x="16" y="60" font-size="6" fill="#3b7bff">${state}</text>`;
  },
};

<App plugins={[myPlugin]} />

// Set plugin state for a tooth:
setPluginState(11, "implant-brand", "Straumann");
```

### 🧪 Тестирование
```bash
npm run test           # Запустить весь набор тестов Vitest
npm run test:watch     # Watch mode
npm run test:coverage  # Coverage report
npm run test:e2e       # Browser tests (Playwright; once: npx playwright install chromium)
```

### 📖 Документация API

🅰️ **Версия для Angular:** доступен официальный порт для Angular — [Angular Advanced Odontogram](https://github.com/ZoliQua/Angular-Advanced-Odontogram) (`angular-advanced-odontogram` в npm); экспорт JSON и FHIR R4 взаимно совместим между обеими библиотеками.

```bash
npm run docs           # Generate TypeDoc docs in docs/
```

### 📡 Публичный API

**Props компонента:**

| Prop | Тип | По умолчанию | Описание |
|---|---|---|---|
| `language` | `string` | `'hu'` | Язык интерфейса (hu/en/de/es/it/sk/pl/ru/pt-br) |
| `onLanguageChange` | `(lang) => void` | — | Обратный вызов при смене языка |
| `numberingSystem` | `string` | `'FDI'` | Система нумерации (FDI/Universal/Palmer) |
| `onNumberingChange` | `(system) => void` | — | Обратный вызов при смене нумерации |
| `darkMode` | `boolean` | `undefined` | Состояние тёмного режима. Опустите для автономного режима. |
| `onDarkModeChange` | `(dark) => void` | — | Обратный вызов при переключении тёмного режима. Обязателен для управляемого режима. |
| `themeConfig` | `OdontogramThemeConfig` | `undefined` | Пользовательские цветовые настройки через CSS-переменные (`--odon-*`). |
| `plugins` | `OdontogramPlugin[]` | `undefined` | Пользовательские SVG-плагины для визуальных наложений и пользовательского состояния зубов. |
| `readOnly` | `boolean` | `undefined` | Отключение всех взаимодействий (клик, сенсорный ввод, клавиатура). Полезно для печати/просмотра отчётов. |
| `enableNotes` | `boolean` | `undefined` | Включение заметок к зубам. Дважды щёлкните зуб для добавления/редактирования. |

**Экспортированные функции для внешнего управления:**

| Функция | Описание |
|---|---|
| `initOdontogram()` | Инициализация движка и отрисовка всех зубов |
| `destroyOdontogram()` | Очистка движка и удаление обработчиков событий |
| `setNumberingSystem(system)` | Переключение между FDI, Universal, Palmer |
| `clearSelection()` | Сброс выделения всех зубов |
| `getSelectedTeeth()` | Выбранные в данный момент зубы (номера FDI) в порядке выбора |
| `getNumberingSystem()` | Текущая система нумерации зубов |
| `getScreenToothSpacing()` / `setScreenToothSpacing(v)` | Получить/задать расстояние между зубами на экране |
| `getScreenToothNumberSize()` / `setScreenToothNumberSize(v)` | Получить/задать размер номера зуба |
| `getSelectionColor()` / `setSelectionColor(hex)` | Получить/задать цвет кольца выделения (`#rrggbb`) |
| `getSelectionBorderStyle()` / `setSelectionBorderStyle(v)` | Получить/задать стиль границы кольца выделения |
| `getToothInfoVisible()` / `setToothInfoVisible(on)` | Получить/задать видимость панели информации о зубе |
| `setOcclusalVisible(on)` | Включение/отключение окклюзионного вида |
| `setWisdomVisible(on)` | Показать/скрыть зубы мудрости |
| `setShowBase(on)` | Показать/скрыть слой кости |
| `setHealthyPulpVisible(on)` | Показать/скрыть здоровую пульпу |
| `registerPlugins(plugins)` | Регистрация пользовательских SVG-плагинов |
| `setPluginState(toothNo, pluginId, value)` | Установить пользовательское состояние плагина для зуба |
| `getPluginState(toothNo, pluginId)` | Получить пользовательское состояние плагина для зуба |
| `getToothStateSummary(toothNo)` | Получить локализованное описание всех активных состояний |
| `getOdontogramSummary()` | Получить структурированное локализованное текстовое описание всей одонтограммы (счётчики, разделы) |
| `onStateChange(callback)` | Подписка на изменения состояния; возвращает функцию отписки |
| `setReadOnly(value)` | Включение/отключение режима «только чтение» |
| `getReadOnly()` | Получить текущее состояние режима «только чтение» |
| `setNotesEnabled(value)` | Включение/отключение заметок к зубам |
| `getNotesEnabled()` | Получить текущее состояние включения заметок |
| `setPulpDetailLevel(level)` | Установить словарь селектора пульпы — `"simple"`, `"aae"` или `"latin"` |
| `getPulpDetailLevel()` | Получить текущий уровень детализации пульпы |
| `getChartMode()` | Получить активную карту — `"status"` или `"plan"` |
| `setChartMode(mode)` | Переключить активную карту на `"status"` или `"plan"`; при первом переходе карта плана глубоко копируется из статуса |
| `getStatusChart()` | Получить payload карты статуса (`{version, globals, teeth}`), независимо от того, какая карта активна в данный момент |
| `getPlanChart()` | Получить payload карты плана (`{version, globals, teeth}`), независимо от того, какая карта активна в данный момент |
| `setPlanChart(payload)` | Заменить зубы карты плана данными из payload (статус остаётся нетронутым); отмечает карту плана как инициализированную |
| `getPlanChanges()` | Получить структурированную разницу статус→план (`{ toothNo, axis, from, to }[]`) — одна запись на зуб на ось лечения, отличающуюся между картами статуса и плана; пусто, если план не существует. Также отображается в `getOdontogramSummary()` как `plannedChanges` |
| `compareExams(before, after)` | Сравнивает два ЭКСПОРТИРОВАННЫХ осмотра одной и той же полости рта — изменения по осям (та же форма, что у `getPlanChanges()`), пародонтальные значения по участкам (глубина зондирования и уровень прикрепления, с тем, что сдвинулось на ≥ 2 мм) и классификацию 2017 для каждого осмотра. Чистая функция: активную схему не трогает |
| `setPerioSite(toothNo, site, patch)` | Установить пародонтальные данные для одной из шести точек (`patch` = `{ pd?, gm?, bop?, sup? }`); `pd`, равное null/`<1`, снимает данные с точки. Проверяет и ограничивает значения (PD 1–15, GM −10…+20) |
| `getToothPerio(toothNo)` | Получить пародонтальную запись зуба по точкам (только зафиксированные точки) |
| `getToothCal(toothNo)` | Получить производный CAL по точкам (`pd + десневой край`) для зуба |
| `getPerioSummary()` | Сводные пародонтальные показатели по всей полости рта: количество зафиксированных точек, количество кровоточащих точек, %BOP, наихудший CAL, максимальный PD |
| `getPerioChart()` | Получить пародонтальные записи по зубам активной карты |
| `PerioChart` | React-компонент (именованный экспорт) — оверлей пародонтальной карты на всю полость рта (`{ open, onClose }`), монтируемый независимо от `OdontogramShell` для интеграции с принимающим приложением |
| `openPerioOverlay()` / `closePerioOverlay()` / `isPerioOverlayOpen()` | Программно открыть/закрыть/проверить состояние оверлея пародонтальной карты — позволяет принимающему приложению вызывать пародонтальную карту отдельно от базовой одонтограммы (общее состояние случая) |
| `getPerioViewMode()` / `setPerioViewMode(mode)` | Получить/установить способ отображения пародонтальной карты — `"toggle"` (переключатель вида «Одонтограмма | Зубная карта», по умолчанию) или `"popup"` (оверлей) |
| `getPerioOverlayLayer()` / `setPerioOverlayLayer(layer)` | Получить/установить оверлей выделения зубной карты — `"none"` (по умолчанию) / `"pd"` / `"cal"` / `"gr"` / `"plaque"` / `"bop"` / `"pd5"` / `"pd6"` / `"cairo"`; перекрашивает зубы по этому показателю (только отображение поверх существующих данных) |
| `getToothRecessionType(toothNo)` | Получить производный **тип рецессии по Каиро** — `"none"` / `"rt1"` / `"rt2"` / `"rt3"` (вычисляется из соотношения интерпроксимального и щёчного CAL зуба) |
| `setCejVisibility(toothNo, v)` / `getCejVisibility(toothNo)` | Видимость CEJ по зубу — `"none"` / `"detectable"` / `"not-detectable"` |
| `setRootConcavity(toothNo, v)` / `getRootConcavity(toothNo)` | Вогнутость поверхности корня по зубу — `"none"` / `"mild"` / `"deep"` |
| `setPlaqueIndex(toothNo, surface, grade)` / `getPlaqueIndex(toothNo, surface)` | Оценка индекса зубного налёта Silness-Löe по поверхности — `0`-`3` |
| `setGingivalIndex(toothNo, surface, grade)` / `getGingivalIndex(toothNo, surface)` | Оценка десневого индекса Löe-Silness по поверхности — `0`-`3` |
| `setKeratinizedWidth(toothNo, mm)` / `getKeratinizedWidth(toothNo)` | Ширина щёчной кератинизированной десны по зубу в мм — `0`-`15`, либо `null`, если не зафиксировано |
| `setGingivalThickness(toothNo, v)` / `getGingivalThickness(toothNo)` | Фенотип толщины десны по зубу — `"unknown"` / `"thin"` / `"medium"` / `"thick"` |
| `setMillerClass(toothNo, v)` / `getMillerClass(toothNo)` | Класс рецессии по Миллеру по зубу — `"none"` / `"i"` / `"ii"` / `"iii"` / `"iv"` |
| `setPeriImplantPlaque(toothNo, surface, grade)` / `getPeriImplantPlaque(toothNo, surface)` | Только для имплантатов — оценка модифицированного индекса зубного налёта Mombelli (mPI) по поверхности — `0`-`3`; не действует на зубе без имплантата |
| `setPeriImplantBleeding(toothNo, surface, grade)` / `getPeriImplantBleeding(toothNo, surface)` | Только для имплантатов — оценка модифицированного индекса кровоточивости борозды Mombelli (mBI) по поверхности — `0`-`3`; не действует на зубе без имплантата |
| `furcationEntrances(toothNo)` | Входы фуркации для зуба — `["mesial","distal","buccal"]` (верхние моляры), `["buccal","lingual"]` (нижние моляры), `["mesial","distal"]` (верхние первые премоляры), иначе `[]` |
| `setFurcation(toothNo, entrance, grade)` / `getToothFurcation(toothNo)` | Установить/получить поражение фуркации по входу (Glickman `1`–`4`; `null` очищает) |
| `setPlaque(toothNo, surface, present)` / `getToothPlaque(toothNo)` | Установить/получить наличие зубного налёта по O'Leary по поверхности (мезиальная/дистальная/щёчная/язычная); учитывается в показателе PI% по всей полости рта в `getPerioSummary()` |
| `getCaseMeta()` | Получить объект метаданных уровня случая (`{age, smokingStatus, cigarettesPerDay, diabetesStatus, hba1c, toothLossPerio, maxRblPercent, patientName, patientDob, examDate}`) — единый общий блок, не по зубам/не для двух состояний (отражает ключ payload верхнего уровня `globals`); влияет на классификацию стадирования/градации пародонтита и на заголовок PDF-отчёта |
| `setPatientName(v)` | Установить имя пациента в случае (обрезается; пустая строка или `null` очищает) — только для идентификации, никогда не участвует в пародонтальном выводе |
| `setPatientDob(v)` | Установить дату рождения пациента в случае (`ГГГГ-ММ-ДД`; недопустимое/пустое значение очищает) — используется только для идентификации в PDF-отчёте |
| `setExamDate(v)` | Установить дату осмотра случая (`ГГГГ-ММ-ДД`; недопустимое/пустое значение очищает) |
| `setCaseAge(v)` | Установить возраст пациента в случае, в годах — `0`-`120`, либо `null` для очистки |
| `setSmokingStatus(v)` | Установить статус курения в случае — `"unknown"` / `"never"` / `"former"` / `"current"` |
| `setCigarettesPerDay(v)` | Установить количество сигарет в день (имеет значение только при статусе курения `"current"`) — `0`-`99`, либо `null` для очистки |
| `setDiabetesStatus(v)` | Установить статус диабета в случае — `"unknown"` / `"none"` / `"present"` |
| `setHba1c(v)` | Установить HbA1c % (имеет значение только при статусе диабета `"present"`) — `3.0`-`20.0` (один десятичный знак), либо `null` для очистки |
| `setToothLossPerio(v)` | Установить количество зубов, потерянных из-за пародонтита — `0`-`32`, либо `null` для очистки |
| `setMaxRblPercent(v)` | Установить максимальный % рентгенологической потери костной ткани — `0`-`100`, либо `null` для очистки |
| `resetCaseMeta()` | Сбросить объект метаданных уровня случая к пустым значениям по умолчанию |
| `getPerioClassification()` | Получить пародонтальную классификацию Всемирного семинара 2017 года (`{diagnosis, stage, grade, extent, derived, overridden}`) — диагноз/стадия/степень/распространённость вычисляются из зафиксированных пародонтальных данных и метаданных случая, каждая ось заменяется переопределением врача при его наличии (`derived` всегда содержит неизменённые вычисленные значения, `overridden` отмечает, какие оси были переопределены) |
| `setDiagnosisOverride(v)` | Переопределить вычисленный пародонтальный диагноз — `"health"` / `"gingivitis"` / `"periodontitis"`, либо `null` для очистки (вернуться к вычисленному значению) |
| `setStageOverride(v)` | Переопределить вычисленную пародонтальную стадию — `"I"` / `"II"` / `"III"` / `"IV"`, либо `null` для очистки (вернуться к вычисленному значению) |
| `setGradeOverride(v)` | Переопределить вычисленную пародонтальную степень — `"A"` / `"B"` / `"C"`, либо `null` для очистки (вернуться к вычисленному значению) |
| `setExtentOverride(v)` | Переопределить вычисленную пародонтальную распространённость — `"localized"` / `"generalized"` / `"molar-incisor"`, либо `null` для очистки (вернуться к вычисленному значению) |
| `exportFhir(options?)` | Экспорт одонтограммы в виде HL7 FHIR R4 collection Bundle (загрузка JSON). Опциональная ссылка `{ subject }`; при отсутствии встраивается Patient-заглушка |
| `exportImage(format)` | Загрузка одонтограммы в виде изображения — `"png"` или `"jpg"` |
| `exportSvg()` | Загрузка одонтограммы в виде масштабируемого SVG (вектор) |
| `hasAnyPerioData()` | `true`, если хотя бы одна пародонтальная ось зафиксирована где-либо в полости рта — управляет автоматическим пропуском пародонтального экспорта и отключает пункты меню пародонтального экспорта на пустой карте |
| `exportPerioSvg()` | Загрузить полную пародонтальную карту (графика зубов + числовые строки + классификация 2017 года) в виде одного самостоятельного векторного SVG, построенного изолированно из состояния через `buildPerioSvg()` |
| `exportPerioImage(format)` | Загрузить пародонтальную карту в виде растрового изображения — `"png"` или `"jpg"` |
| `exportPdf(opts)` | Загрузить PDF-отчёт, собранный нативно средствами jsPDF (`{patientData, odontogramChart, odontogramDescription, individualNotes, perioStatus, perioDescription}`, каждый раздел опционален) — векторный текст плюс растровые изображения зубной карты/пародонтальной карты; раздел индивидуальных заметок автоматически пропускается, если ни у одного зуба нет заметки, а оба пародонтальных раздела автоматически пропускаются, если `hasAnyPerioData()` равно false, независимо от `opts` |
| `importFhirBundle(input)` | Импорт FHIR R4 Bundle (объект или строка JSON), созданного этим модулем |
| `setImportFormat(format)` | Установить парсер для следующего импорта файла — `"status"` или `"fhir"` |
| `startIntroTour()` | Запустить интерактивное обучение из 18 шагов |

### 💾 Сохранение состояния (localStorage)

Опциональное сохранение состояния карты в `localStorage` (`src/persistence.ts`, реэкспортируется из точки входа пакета). По умолчанию отключено — существующие интеграции не затрагиваются, если приложение-хост явно не включит эту функцию; вызывать следует **после** монтирования одонтограммы (восстановление перерисовывает живой DOM через `importStatus()`):

```ts
import {
  enablePersistence, disablePersistence,
  clearPersistedState, isPersistenceEnabled,
} from "react-advanced-odontogram";

enablePersistence({
  key: "my-app-odontogram",   // по умолчанию: "react-advanced-odontogram"
  includePlan: true,          // сохранять также карту плана; по умолчанию: false
  onError: (err) => console.error("odontogram persistence:", err),
});
```

| Функция | Описание |
|---|---|
| `enablePersistence(options?)` | Восстанавливает ранее сохранённый случай (если есть) через `importStatus()`, затем сохраняет статусную карту в `localStorage` при каждом изменении состояния. Идемпотентна — повторный вызов заменяет предыдущую подписку/опции. **Должна вызываться после монтирования одонтограммы.** |
| `disablePersistence()` | Останавливает сохранение; сохранённая запись остаётся на месте. |
| `clearPersistedState()` | Удаляет сохранённую запись для активного (или стандартного) ключа. |
| `isPersistenceEnabled()` | `true`, пока подписка на изменение состояния активна. |

**`PersistenceOptions`:**

| Поле | Тип | По умолчанию | Описание |
|---|---|---|---|
| `key` | `string` | `"react-advanced-odontogram"` | Ключ `localStorage`. |
| `includePlan` | `boolean` | `false` | Также сохранять карту плана (поле `plan` в payload). |
| `onError` | `(err: Error) => void` | — | Вызывается при любой ошибке хранения/парсинга вместо `console.warn`. |

Примечания: ничего не читается из `localStorage` и не записывается в него, пока не вызвана `enablePersistence()`; ограничение размера 4 МБ пропускает слишком большое сохранение (сообщается через `onError`/`console.warn`), вместо того чтобы выбросить исключение; любой сбой хранения/JSON — превышение квоты, заблокированный iframe, повреждённые или нераспознанные сохранённые данные и т. д. — перехватывается и сообщается. Этот модуль никогда не выбрасывает исключения.

Примечание: включение сохранения восстанавливает сохранённый случай через `importStatus()`, что заменяет текущий случай — включая незавершённую карту плана, если в сохранённых данных её нет. Включайте сохранение при запуске (сразу после монтирования), а не в середине сеанса.

Примечание: сохранённые данные могут содержать идентифицирующую пациента информацию (имя пациента, дата осмотра) в открытом виде в `localStorage`. Если вы фиксируете такие данные, обеспечьте защиту на уровне устройства или удаляйте их с помощью `clearPersistedState()`, когда это уместно.

### 💾 Формат экспорта/импорта статуса
При экспорте создаётся файл JSON (версия `2.22`; импорт также принимает устаревшие версии `1.4` и `2.0`–`2.21` и автоматически мигрирует их), содержащий:

**Глобальные поля:**
- `wisdomVisible` — видимость зубов мудрости
- `showBase` — видимость слоя кости
- `occlusalVisible` — активность окклюзионного вида
- `showHealthyPulp` — видимость здоровой пульпы
- `edentulous` — активность режима беззубого

**Поля для каждого зуба (32 зуба):**
- `toothSelection` — базовый тип зуба
- `toothSubstrate` — субстрат зуба (natural/radix/broken/crownprep), независим от вида реставрации
- `restorationType` — тип реставрации (none/crown/inlay/onlay/veneer/bridge)
- `restorationMaterial` — материал реставрации (emax/gold/gradia/zircon/metal/metal-ceramic/telescope/temporary), сопряжён с `restorationType`
- `prosthesis` — ось съёмного/аттачментного протезирования (none/healing-abutment/locator/locator-denture/bar/bar-denture/removable-partial/removable-full), взаимоисключающая с фиксированным `restorationType` коронка/мост
- `crownLeakage` — флаг краевой негерметичности коронки, имеет значение только когда `restorationType` равен crown или bridge
- `endo` — эндодонтический статус; взаимоисключающий с `pulpDx` (отображаются вместе через единый объединённый селектор «Статус пульпы / эндо» — лечение зуба нормализует `pulpDx` в `normal`)
- `mods` — массив модификаций (inflammation, parodontal); `inflammation` упразднён в интерфейсе для присутствующих зубов (там глиф определяется `apicalDx`), но по-прежнему применяется к отсутствующим зубам/лункам после удаления
- `caries` — активные поверхности кариеса
- `cariesActiveDepth` — значение глубины ICDAS, устанавливаемое селектором глубины кариеса при применении к новой поверхности (не сохраняемое значение по поверхности; см. `cariesSeverity` для сохранённого поля по поверхности)
- `rootCaries` — степень тяжести кариеса корня (none/active/arrested/active-cavitated)
- `cariesSeverity` — единая тяжесть по поверхностям (0-6): глубина ICDAS на первичной (без пломбы) поверхности, оценка CARS на рецидивирующей (с пломбой) поверхности
- `radiographicDepth` — рентгенологическая глубина кариеса по поверхностям (none/E1/E2/D1/D2/D3), независимая от визуальной шкалы ICDAS/CARS
- `fillingMaterial` — материал пломбы
- `fillingSurfaces` — запломбированные поверхности
- `fillingSurfaceMaterials` — материал пломбы по поверхностям (смешанные пломбы, например щёчная амальгама + дистальный композит)
- `fillingDefect` — дефект пломбы по поверхности (none/marginal/fracture/wear), ограничен запломбированными поверхностями, независим от вторичного кариеса
- `pulpDx` — диагноз пульпы по AAE (normal/reversible-pulpitis/irreversible-pulpitis/necrosis); reversible-pulpitis отображается уменьшенным глифом
- `pulpLatin` — практический латинский подтип пульпы (селектор пульпы показывает его только когда `pulpDetailLevel` равен `latin`)
- `apicalDx` — апикальный диагноз, определяющий периапикальный глиф
- `periapicalType` — подтип периапикального очага (none/granuloma/cyst), отображается только при симптоматическом/бессимптомном апикальном периодонтите; устаревшее значение `abscess` по-прежнему принимается при импорте
- `resorptionType` — тип резорбции корня (none/internal/external-cervical)
- `periImplant` — статус периимплантных тканей только для имплантатов (none/mucositis/peri-implantitis-mild/-moderate/-severe), стадирование по классификации Всемирного семинара 2018 года
- `dxOverrides` — переопределения кодирования диагноза на уровне зуба (версия 2.21): объект с ключами кодов диагнозов ICD-10 → `add` | `suppress`, принудительно включающий закодированный диагноз при отсутствии соответствующей клинической находки или отключающий его при её наличии; формирует итоговый набор закодированных диагнозов, экспортируемых как FHIR `Condition`
- `endoResection` — флаг апикоэктомии
- `fissureSealing` — флаг герметизации фиссур
- `calculus` — флаг зубного камня
- `contactMesial` — потеря мезиального контактного пункта
- `contactDistal` — потеря дистального контактного пункта
- `wearEdge` — тип стирания режущего края/окклюзионной поверхности (none/attrition/erosion)
- `wearCervical` — тип пришеечного стирания (none/abrasion/abfraction/erosion)
- `discoloration` — причина изменения цвета по зубу (none/tetracycline/fluorosis/nonvital/extrinsic/other), окрашивает заливку натуральной коронки на натуральном постоянном/молочном зубе без реставрации
- `orthoAppliance` — ортодонтический аппарат (none/bracket/band)
- `orthoDrift` — ортодонтический дрейф (none/mesial/distal)
- `orthoVertical` — ортодонтическое вертикальное перемещение (none/extrusion/intrusion)
- `orthoRotation` — флаг ортодонтической ротации
- `brokenMesial`, `brokenIncisal`, `brokenDistal` — локализация переломов
- `extractionWound` — лунка после удаления зуба
- `extractionPlan` — планируемое удаление
- `parapulpalPin` — флаг парапульпарного штифта
- `bridgePillar` — зуб-опора мостовидного протеза
- `mobility` — степень подвижности (none/m1/m2/m3)
- `crownNeeded` — индикатор необходимости коронки
- `crownReplace` — индикатор необходимости замены коронки
- `missingClosed` — закрытый дефект после удаления
- `customStates` — пользовательские состояния плагинов (объект с ключами по ID плагина)
- `note` — текстовая заметка к зубу (строка, необязательная — присутствует только при наличии содержимого)

**Поле верхнего уровня `plan` (версия 2.11+):**
- `plan` — необязательный объект той же формы, что и `teeth` (поля по зубам выше), содержащий карту **плана** (предполагаемое лечение после вмешательства). Присутствует только тогда, когда карта плана была инициализирована (переключатель `Статус | План` хотя бы раз переключался на «План») И её содержимое отличается от карты статуса — экспорт только статуса полностью опускает это поле и остаётся побайтово идентичным экспорту до версии 2.11, за исключением номера версии. При импорте отсутствие `plan` очищает/деинициализирует карту плана (она никогда не восстанавливает устаревший план, оставшийся с момента до импорта); присутствие `plan` восстанавливает карту плана вместе со статусом. Карта плана также может читаться/записываться независимо от импорта/экспорта через `getPlanChart()`/`setPlanChart()` (см. Публичный API выше), а `getStatusChart()` всегда возвращает payload карты статуса независимо от активного режима карты.

**Объект верхнего уровня `case` (версия 2.17+, расширен в 2.18, 2.19, 2.20 и 2.22):**
- `case` — необязательный объект метаданных уровня случая — НЕ является полем по зубам и не имеет двух состояний (один и тот же объект является общим для карт статуса и плана, отражая ключ payload верхнего уровня `globals`). Содержит возраст пациента (`age`, 0-120), статус курения (`smokingStatus`: unknown/never/former/current, с `cigarettesPerDay` 0-99), статус диабета (`diabetesStatus`: unknown/none/present, с `hba1c` 3.0-20.0), две сводные статистики исхода пародонтита (`toothLossPerio` 0-32 и `maxRblPercent` 0-100), клинические переопределения пародонтальной классификации 2017 года (`diagnosisOverride`/`stageOverride`/`gradeOverride`/`extentOverride`), а также три поля идентификации случая — `patientName` (обрезанная строка или `null`), `patientDob` (`ГГГГ-ММ-ДД` или `null`, добавлено в версии 2.20) и `examDate` (`ГГГГ-ММ-ДД` или `null`) — используемые исключительно в заголовке PDF-отчёта и нигде более; ни одно из них не входит в экспорт FHIR. Помимо этого, объект дополнительно содержит (версия 2.22) поле `caseConditions` — диагнозы уровня случая/региона (аномалии прикуса и ВНЧС K07, кисты полости рта K09, заболевания слюнных желёз K11, стоматит и поражения слизистой оболочки полости рта K12/K13, зубочелюстные аномалии развития на уровне зубного ряда K00), каждый из которых сопоставлен со стороной поражения (не уточнено / слева / справа / двусторонне). Сериализуется с пропуском пустых полей, и весь объект `case` отсутствует, если все его поля имеют значения по умолчанию. Управляется через `getCaseMeta()`/`resetCaseMeta()` и отдельные сеттеры (см. Публичный API выше).

### 🖨️ Экспорт
`exportFhir()` создаёт Bundle, чистый для валидаторов HL7: каждая запись несёт детерминированный `id` и абсолютный `fullUrl` (без заполнителей `urn:uuid`), а Bundle включает собственную CodeSystem движка, чтобы его локальные коды разрешались при валидации (также опубликована как `fhir/CodeSystem-odontogram.json` в репозитории; её можно опустить через `includeCodeSystem: false`).

Пародонтальные данные теперь возвращаются и через импорт FHIR, а не только через JSON-полезную нагрузку: `importFhirBundle()` считывает пародонтальные панели LOINC `74029-0` обратно в пародонтальную запись каждого зуба — глубину зондирования, десневой край (восстановленный из CAL, поэтому значения псевдокармана сохраняются), BOP, фуркацию, налёт по O'Leary, индексы PI/GI и имплантатные mPI/mBI, а также ширину кератинизированной десны — плюс доказательные Observation по статусу курения и HbA1c на уровне случая. Единственное исключение — суппурация: она остаётся только в JSON, поскольку не входит в экспорт FHIR.

Помимо собственного экспорта одонтограммы в Статус JSON / FHIR / PNG / JPG / SVG, **пародонтальная карта** имеет собственный путь экспорта:
- **Пародонтальный SVG/PNG/JPG:** `exportPerioSvg()` / `exportPerioImage("png"|"jpg")` отрисовывают полную пародонтальную карту (графика зубов + числовые строки + классификация 2017 года) в виде одного самостоятельного векторного SVG (`buildPerioSvg()`), независимо от смонтированного DOM `PerioChart`. Все три пункта меню экспорта отключены, если `hasAnyPerioData()` равно false (в пустой карте нечего экспортировать по пародонту).
- **PDF-отчёт:** пункт меню экспорта «PDF-отчёт…» открывает `ExportOptionsModal` — диалог настроек (поля имени пациента, даты рождения и даты осмотра, напрямую связанные с метаданными случая, при этом дата осмотра по умолчанию — сегодняшняя; флажки разделов: данные пациента, одонтограмма, описание одонтограммы, индивидуальные заметки — недоступен, если ни у одного зуба нет заметки — пародонтальный статус, описание пародонта) перед вызовом `exportPdf(opts)`. Пустые поля идентификации заменяются значениями-плейсхолдерами («John Doe» / «1980-01-01»), чтобы экспорт всегда завершался успешно. PDF собирается нативно средствами jsPDF — векторный текст через `.text()`, растровые изображения зубной карты/пародонтальной карты через `.addImage()` — **без зависимости от svg2pdf.js**. Раздел индивидуальных заметок автоматически пропускается, если ни у одного зуба нет заметки, а оба пародонтальных раздела — если `hasAnyPerioData()` равно false, независимо от флажков диалога.
- **Ограничение mPI/mBI по имплантатам:** периимплантные индексы Mombelli (mPI/mBI) отображаются как строки только в той дуге, которая содержит хотя бы один зуб-имплантат — как на живой пародонтальной карте, так и в экспорте SVG/PDF.
- Имя пациента, дата рождения и дата осмотра — это только метаданные идентификации карты (payload `2.20`, аддитивно) — они **не** входят в экспорт FHIR.

### 📁 Структура папок
- `src/App.tsx` — оболочка интерфейса, элементы верхней панели, переключатели языка/нумерации/тёмного режима/темы/плагина
- `src/odontogram.ts` — движок слоёв SVG, управление состоянием зубов, сенсорные взаимодействия, наложения плагинов, связка с интерфейсом
- `src/plugin.ts` — тип `OdontogramPlugin`, `PluginLayer`, `getQuadrant()`, z-index приоритеты `LAYER_Z`
- `src/theme.ts` — тип `OdontogramThemeConfig` и утилита `applyThemeConfig()`
- `src/status_extras.ts` — 34 предустановленных шаблона реставраций (мосты, протезы, балочные конструкции)
- `src/i18n/` — переводы (HU/EN/DE/ES/IT/SK/PL/RU/PT-BR/AR/ZH/FR) и хук i18n
- `src/utils/numbering.ts` — преобразование нумерации FDI, Universal, Palmer
- `src/registry/` — декларативный реестр клинических осей: сопоставления полей FHIR, набор очистки SVG/активация логических флагов, матрица тип×материал реставраций, списки опций интерфейса (единый источник истины, генерирующий экспорт/импорт, FHIR и интерфейс выбора)
- `src/fhir/` — экспорт/импорт HL7 FHIR R4: `toFhir.ts`/`fromFhir.ts`, системы кодов, сопоставления полей, примитивы
- `src/bridgeOverlay.ts` — оверлей коннектора многозубого мостовидного пролёта (геометрия седла с учётом дуги)
- `src/SettingsModal.tsx` — модальное окно настроек с вкладками (Общие/Панели/Детали зуба/Кариес/Пульпа/Заметки/Периодонтальный)
- `src/perioExport.ts` — `buildPerioSvg()`: полная пародонтальная карта в виде одного самостоятельного векторного SVG
- `src/perioPdf.ts` — чистый сборщик PDF-отчёта (`assemblePdf`) для `exportPdf()`
- `src/ExportOptionsModal.tsx` — диалог настроек экспорта «PDF-отчёт…»
- `src/__tests__/` + `src/registry/__tests__/` — обширный набор автоматических тестов Vitest
- `src/assets/teeth-svgs/` — SVG-шаблоны зубов (6 файлов: резцы, клыки, премоляры, моляры + окклюзионные виды)
- `src/assets/icon-svgs/` — SVG-иконки панели инструментов (5 файлов)

### ⚙️ Технологический стек
- React 18 + Vite + TypeScript
- Tailwind CSS для стилизации интерфейса
- Слоирование SVG через манипуляции с DOM (без React-состояния для производительности)
- Лёгкая кастомная система i18n
- Vitest + Testing Library для автоматизированных тестов
- TypeDoc для документации API
- Псевдоним пути Vite: `@` соответствует `./src`

### 📝 Примечания
- SVG-шаблоны загружаются из `src/assets/teeth-svgs` и `src/assets/icon-svgs`, поэтому статический хостинг должен обслуживать публичную директорию.
- Движок одонтограммы использует собственное внутреннее состояние (не React-состояние) для обеспечения производительности и простоты.
- Молочные зубы поддерживают ограниченный набор доступных материалов (без амальгамных пломб, без эндодонтии со штифтами).
- Имплантаты предлагают другой набор вариантов коронок/абатментов по сравнению с натуральными зубами.

### 🔒 Примечания по безопасности

- **Плагины выполняются как доверенный код.** Возвращаемое значение `renderSvg()` плагина вставляется в SVG живой карты. Этот вывод санитизируется библиотекой [DOMPurify](https://github.com/cure53/DOMPurify) (SVG-профиль плюс `svgFilters`) перед вставкой — `<script>`, `<iframe>`, `<object>`, `<embed>` и `<foreignObject>` запрещены полностью, а полностью вредоносный вывод отбрасывается, а не частично отображается. Это снижает радиус поражения при компрометации или ошибке в плагине, но плагины по-прежнему следует загружать только из источников, которым вы доверяете — санитизация является дополнительной защитой, а не заменой проверки.
- **Content-Security-Policy.** Production-сборка демо-приложения внедряет следующую политику через тег `<meta http-equiv="Content-Security-Policy">` (dev-сервер не затрагивается):

  ```
  default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self'; connect-src 'self'; object-src 'none'; base-uri 'self'
  ```

  Приложения-хосты, встраивающие `OdontogramShell`, должны задавать собственную CSP, соответствующую их развёртыванию — компонент не внедряет CSP при использовании в качестве библиотеки.

### 📖 Как цитировать

Если вы используете этот модуль в своей работе, пожалуйста, процитируйте его.

**Эта версия (v2.4.0):**
> Dul, Z. (2026). *React Advanced Odontogram* (v2.4.0). Zenodo. https://doi.org/10.5281/zenodo.21156787

**Все версии (концептуальный DOI):** https://doi.org/10.5281/zenodo.21156787

> Указанный выше концептуальный DOI для всех версий всегда указывает на самый
> последний заархивированный релиз; DOI для конкретной версии присваивается
> при каждом релизе в момент его архивирования на Zenodo. Пока v2.4.0 не
> заархивирована, ссылайтесь на неё через концептуальный DOI.

Машиночитаемые метаданные для цитирования находятся в файле [`CITATION.cff`](../CITATION.cff).

## 🙌 Благодарности

React Advanced Odontogram создаётся и поддерживается Zoltan Dul ([@ZoliQua](https://github.com/ZoliQua)), создателем и ведущим разработчиком всего движка. При ценной помощи участников, перечисленных ниже. Спасибо всем, кто внёс свой вклад.

**Участники**

- [@odontodev](https://github.com/odontodev): гидратация состояния и API жизненного цикла, настройки пломб как управляемые props, идемпотентные сеттеры и сворачиваемые карточки; настройки отображения схемы и `getNumberingSystem()`, доступные хост-приложению, а также недостающие уведомления `onStateChange` для настроек сессии и заметок по зубам
- [@JulianoBazzi](https://github.com/JulianoBazzi): перевод на бразильский португальский
- [@yassine-bhn](https://github.com/yassine-bhn): перевод на французский и предлагаемая измеренная анатомия
- [@saegerdirk-star](https://github.com/saegerdirk-star): измеренная анатомия зуба и генератор зубов, а также предложение компонуемого интерфейса; три исправления, перенесённые из их форка (данные пациента в PDF, ориентация зубов в пародонтограмме, скорость выбора)
- [@sofia-cluadette](https://github.com/sofia-cluadette): API выбора `getSelectedTeeth()`
- [@Ditherys](https://github.com/Ditherys): расширенный набор измеренных зубов — полная спецификация генератора анатомии для постоянного и молочного прикуса, метаданные привязки накладок и набор проверок, перенесённые из их форка

**Создано с помощью** [jsPDF](https://github.com/parallax/jsPDF), [DOMPurify](https://github.com/cure53/DOMPurify), [React](https://react.dev), [Vite](https://vite.dev), [TypeScript](https://www.typescriptlang.org) и [Tailwind CSS](https://tailwindcss.com).

Вклад приветствуется. Откройте pull request на GitHub, и вы будете указаны здесь. Если проект вам полезен, пожалуйста, [поставьте ему звезду на GitHub](https://github.com/ZoliQua/React-Advanced-Odontogram).
