@alfadocs/ui-kit-debug 1.4.16 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_chunks/{anamnesis-summary-rows-CVzxC2X3.js → anamnesis-summary-rows-BpYztFUv.js} +20 -4
- package/dist/_chunks/{anamnesis-summary-rows-CVzxC2X3.js.map → anamnesis-summary-rows-BpYztFUv.js.map} +1 -1
- package/dist/_chunks/{care-plan-card-C83Hb9EE.js → care-plan-card-CehAU7F_.js} +2 -2
- package/dist/_chunks/{care-plan-card-C83Hb9EE.js.map → care-plan-card-CehAU7F_.js.map} +1 -1
- package/dist/_chunks/{care-plan-entry-card-CHMPSTCz.js → care-plan-entry-card-ChWRMfMw.js} +2 -2
- package/dist/_chunks/{care-plan-entry-card-CHMPSTCz.js.map → care-plan-entry-card-ChWRMfMw.js.map} +1 -1
- package/dist/_chunks/{periodontal-chart-brakjj0s.js → periodontal-chart-C0nNQKKK.js} +121 -121
- package/dist/_chunks/{periodontal-chart-brakjj0s.js.map → periodontal-chart-C0nNQKKK.js.map} +1 -1
- package/dist/_chunks/tooth-data-DR8Kdnup.js +903 -0
- package/dist/_chunks/tooth-data-DR8Kdnup.js.map +1 -0
- package/dist/_chunks/tooth-finding-palette-vqC-D2Ye.js +708 -0
- package/dist/_chunks/tooth-finding-palette-vqC-D2Ye.js.map +1 -0
- package/dist/_chunks/tooth-scheme-BPXE1_3N.js +2051 -0
- package/dist/_chunks/tooth-scheme-BPXE1_3N.js.map +1 -0
- package/dist/agent-catalog.json +465 -21
- package/dist/agent-i18n/en.json +23 -6
- package/dist/components/care-plan-card/index.js +1 -1
- package/dist/components/care-plan-entry-card/index.js +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/periodontal-chart/index.js +1 -1
- package/dist/components/tooth-finding-palette/index.d.ts +4 -0
- package/dist/components/tooth-finding-palette/index.d.ts.map +1 -0
- package/dist/components/tooth-finding-palette/index.js +9 -0
- package/dist/components/tooth-finding-palette/index.js.map +1 -0
- package/dist/components/tooth-finding-palette/tooth-finding-palette.agent.d.ts +4 -0
- package/dist/components/tooth-finding-palette/tooth-finding-palette.agent.d.ts.map +1 -0
- package/dist/components/tooth-finding-palette/tooth-finding-palette.d.ts +89 -0
- package/dist/components/tooth-finding-palette/tooth-finding-palette.d.ts.map +1 -0
- package/dist/components/tooth-scheme/index.d.ts +2 -2
- package/dist/components/tooth-scheme/index.d.ts.map +1 -1
- package/dist/components/tooth-scheme/index.js +57 -30
- package/dist/components/tooth-scheme/tooth-data.d.ts +279 -10
- package/dist/components/tooth-scheme/tooth-data.d.ts.map +1 -1
- package/dist/components/tooth-scheme/tooth-scheme.agent.d.ts +2 -2
- package/dist/components/tooth-scheme/tooth-scheme.agent.d.ts.map +1 -1
- package/dist/components/tooth-scheme/tooth-scheme.d.ts +30 -3
- package/dist/components/tooth-scheme/tooth-scheme.d.ts.map +1 -1
- package/dist/i18n/locales/ar.d.ts +40 -1
- package/dist/i18n/locales/ar.d.ts.map +1 -1
- package/dist/i18n/locales/ar.js +41 -2
- package/dist/i18n/locales/ar.js.map +1 -1
- package/dist/i18n/locales/de.d.ts +40 -1
- package/dist/i18n/locales/de.d.ts.map +1 -1
- package/dist/i18n/locales/de.js +41 -2
- package/dist/i18n/locales/de.js.map +1 -1
- package/dist/i18n/locales/el.d.ts +40 -1
- package/dist/i18n/locales/el.d.ts.map +1 -1
- package/dist/i18n/locales/el.js +41 -2
- package/dist/i18n/locales/el.js.map +1 -1
- package/dist/i18n/locales/en.d.ts +40 -1
- package/dist/i18n/locales/en.d.ts.map +1 -1
- package/dist/i18n/locales/en.js +41 -2
- package/dist/i18n/locales/en.js.map +1 -1
- package/dist/i18n/locales/es.d.ts +40 -1
- package/dist/i18n/locales/es.d.ts.map +1 -1
- package/dist/i18n/locales/es.js +41 -2
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.d.ts +40 -1
- package/dist/i18n/locales/fr.d.ts.map +1 -1
- package/dist/i18n/locales/fr.js +41 -2
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hi.d.ts +40 -1
- package/dist/i18n/locales/hi.d.ts.map +1 -1
- package/dist/i18n/locales/hi.js +41 -2
- package/dist/i18n/locales/hi.js.map +1 -1
- package/dist/i18n/locales/it.d.ts +40 -1
- package/dist/i18n/locales/it.d.ts.map +1 -1
- package/dist/i18n/locales/it.js +41 -2
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/ja.d.ts +40 -1
- package/dist/i18n/locales/ja.d.ts.map +1 -1
- package/dist/i18n/locales/ja.js +41 -2
- package/dist/i18n/locales/ja.js.map +1 -1
- package/dist/i18n/locales/nl.d.ts +40 -1
- package/dist/i18n/locales/nl.d.ts.map +1 -1
- package/dist/i18n/locales/nl.js +41 -2
- package/dist/i18n/locales/nl.js.map +1 -1
- package/dist/i18n/locales/pl.d.ts +40 -1
- package/dist/i18n/locales/pl.d.ts.map +1 -1
- package/dist/i18n/locales/pl.js +41 -2
- package/dist/i18n/locales/pl.js.map +1 -1
- package/dist/i18n/locales/pt.d.ts +40 -1
- package/dist/i18n/locales/pt.d.ts.map +1 -1
- package/dist/i18n/locales/pt.js +41 -2
- package/dist/i18n/locales/pt.js.map +1 -1
- package/dist/i18n/locales/ro.d.ts +40 -1
- package/dist/i18n/locales/ro.d.ts.map +1 -1
- package/dist/i18n/locales/ro.js +41 -2
- package/dist/i18n/locales/ro.js.map +1 -1
- package/dist/i18n/locales/ru.d.ts +40 -1
- package/dist/i18n/locales/ru.d.ts.map +1 -1
- package/dist/i18n/locales/ru.js +41 -2
- package/dist/i18n/locales/ru.js.map +1 -1
- package/dist/i18n/locales/sq.d.ts +40 -1
- package/dist/i18n/locales/sq.d.ts.map +1 -1
- package/dist/i18n/locales/sq.js +41 -2
- package/dist/i18n/locales/sq.js.map +1 -1
- package/dist/i18n/locales/sv.d.ts +40 -1
- package/dist/i18n/locales/sv.d.ts.map +1 -1
- package/dist/i18n/locales/sv.js +41 -2
- package/dist/i18n/locales/sv.js.map +1 -1
- package/dist/i18n/locales/tr.d.ts +40 -1
- package/dist/i18n/locales/tr.d.ts.map +1 -1
- package/dist/i18n/locales/tr.js +41 -2
- package/dist/i18n/locales/tr.js.map +1 -1
- package/dist/i18n/locales/zh.d.ts +40 -1
- package/dist/i18n/locales/zh.d.ts.map +1 -1
- package/dist/i18n/locales/zh.js +41 -2
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/index.js +448 -415
- package/dist/index.js.map +1 -1
- package/dist/locales/ar.json +41 -2
- package/dist/locales/de.json +41 -2
- package/dist/locales/el.json +41 -2
- package/dist/locales/en.json +41 -2
- package/dist/locales/es.json +41 -2
- package/dist/locales/fr.json +41 -2
- package/dist/locales/hi.json +41 -2
- package/dist/locales/it.json +41 -2
- package/dist/locales/ja.json +41 -2
- package/dist/locales/nl.json +41 -2
- package/dist/locales/pl.json +41 -2
- package/dist/locales/pt.json +41 -2
- package/dist/locales/ro.json +41 -2
- package/dist/locales/ru.json +41 -2
- package/dist/locales/sq.json +41 -2
- package/dist/locales/sv.json +41 -2
- package/dist/locales/tr.json +41 -2
- package/dist/locales/zh.json +41 -2
- package/dist/patterns/anamnesis/anamnesis-i18n.d.ts.map +1 -1
- package/dist/patterns/anamnesis/index.js +1 -1
- package/dist/tokens.css +1 -1
- package/package.json +5 -1
- package/dist/_chunks/tooth-data-BwuoKFf4.js +0 -660
- package/dist/_chunks/tooth-data-BwuoKFf4.js.map +0 -1
- package/dist/_chunks/tooth-scheme-s0ipC0-Z.js +0 -1483
- package/dist/_chunks/tooth-scheme-s0ipC0-Z.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tooth-data-DR8Kdnup.js","sources":["../../src/components/tooth-scheme/tooth-data.ts"],"sourcesContent":["/* ------------------------------------------------------------------ */\n/* ToothScheme — data model + lookup tables. */\n/* */\n/* FDI ISO 3950 is the canonical internal id. This module exposes */\n/* conversion tables to Universal (ADA) and a simplified Palmer-style */\n/* quadrant+position string so consuming UIs can toggle numbering. */\n/* */\n/* Why simplified Palmer? True Palmer notation uses quadrant bracket */\n/* glyphs (\"⌐\", \"¬\", \"L\", \"J\") that do not render reliably in every */\n/* browser/font combination. We emit a readable `UR3` / `LL6` form */\n/* instead and document the deviation in the component MDX. */\n/* ------------------------------------------------------------------ */\n\n/* ------------------------------------------------------------------ */\n/* Core types */\n/* ------------------------------------------------------------------ */\n\nexport type FdiId = string; // '11'–'48' permanent, '51'–'85' primary\n\nexport type Dentition = 'permanent' | 'primary' | 'mixed';\nexport type Numbering = 'fdi' | 'universal' | 'palmer';\nexport type ToothMode = 'interactive' | 'display';\nexport type Surface = 'mesial' | 'distal' | 'occlusal' | 'buccal' | 'lingual';\n/**\n * Built-in findings. `destroyed` is the platform's *radice persistente*\n * (retained root); the id is frozen for backward compatibility but the label\n * and docs read \"Retained root\" so the two systems describe the same thing.\n * `xray` is the chartable radiograph SYMBOL — distinct from the `radiographs`\n * image archive a tooth can also carry.\n */\nexport type ToothCondition =\n | 'caries'\n | 'filled'\n | 'crowned'\n | 'temporaryCrown'\n | 'bridge'\n | 'missing'\n | 'implant'\n | 'implantExtraction'\n | 'stub'\n | 'destroyed'\n | 'rootCanal'\n // Extended vocabulary — artwork for each already ships under `side/`.\n | 'pin'\n | 'rootAmputation'\n | 'anchorage'\n | 'hook'\n | 'attachment'\n | 'mucogingivalSurgery'\n | 'periodontalSurgery'\n | 'curettage'\n | 'medication'\n | 'xray';\n\n/**\n * Open condition id: a built-in, or an id from a consumer's condition registry.\n * The `(string & {})` keeps editor autocomplete on the built-ins while still\n * accepting a practice-defined finding.\n */\nexport type ConditionId = ToothCondition | (string & {});\n\n/** Which anatomical layer a symbol marks — mirrors the platform's `part`. */\nexport type SymbolPart = 'element' | 'surface' | 'root';\n\n/** Planned vs already executed. Matches the Care Plan's blue/green convention. */\nexport type SymbolStatus = 'planned' | 'executed';\n\n/**\n * A tooth's roots, named anatomically; `'single'` covers every one-rooted\n * tooth. For flank-mounted markers (anchorage, hook) `mesial` / `distal` select\n * which SIDE of the tooth the marker sits on rather than a root proper.\n */\nexport type Root = 'mesial' | 'distal' | 'palatal' | 'buccal' | 'single';\n\n/**\n * One charted finding. Unlike the legacy `conditions` + `surfaces` pair — where\n * a single flat surface list is shared by every condition on the tooth — a\n * symbol scopes its own surfaces and roots, so two fillings on one tooth (one\n * mesial, one distal) are finally representable.\n */\nexport interface ToothSymbol {\n /**\n * Provenance — the platform's carePlanEntryId / findingEntryId. Lets a\n * consumer route a click on the symbol back to the record that produced it.\n */\n id?: string;\n condition: ConditionId;\n /** Defaults to the condition's natural part (see `conditionPart`). */\n part?: SymbolPart;\n surfaces?: Surface[];\n roots?: Root[];\n /**\n * Where a flank-mounted marker sits — the gingival surgeries ship\n * `left` / `right` / `total` / `repeat` renders rather than per-root ones.\n * Omitted, they fall back to `repeat`.\n */\n region?: SymbolRegion;\n status?: SymbolStatus;\n /** Per-practice colour override (a CSS colour or `var(--token)`). */\n color?: string;\n note?: string;\n}\n\nexport interface ToothState {\n conditions: ToothCondition[];\n surfaces: Surface[];\n /**\n * Per-symbol findings. When present these WIN over `conditions`/`surfaces`;\n * when absent `symbolsForTooth()` derives the equivalent list from them, so\n * the renderer only ever walks one shape.\n */\n symbols?: ToothSymbol[];\n notes?: string;\n}\n\nexport type ToothChart = Record<FdiId, ToothState>;\n\n/** Radiograph (x-ray) type. `'periapical'` is the intraoral *endorale*. */\nexport type RadiographKind = 'periapical' | 'bitewing' | 'panoramic' | 'other';\n\n/**\n * A single radiograph attached to a tooth. Image urls are opaque,\n * consumer-hosted strings, interpolated into an SVG `<image href>` / `<img src>`\n * (which cannot execute scripts) — never bundled and never runtime-validated.\n * Real radiographs are PHI: stories and demos must use synthetic placeholders.\n */\nexport interface Radiograph {\n /** Stable id — keys the archive list and the imperative open call. */\n id: string;\n /** Full-resolution image url. */\n url: string;\n /** Optional smaller preview url (falls back to `url`). */\n thumbnailUrl?: string;\n /** ISO acquisition date (`YYYY-MM-DD`). */\n date?: string;\n /** Free-text clinical note. */\n note?: string;\n /** Radiograph type. Default `'periapical'` (an *endorale*). */\n kind?: RadiographKind;\n /**\n * Accessible description for the FULL-SIZE image, consumed by a downstream\n * viewer (e.g. the Dental Imaging Hub archive `<img>`). The chart's slot\n * render is decorative (`aria-hidden` — the tooth's accessible name carries\n * the radiograph count), so `alt` is intentionally not read by the chart.\n */\n alt?: string;\n}\n\nexport type Anatomy = 'incisor' | 'canine' | 'premolar' | 'molar';\nexport type Arch = 'upper' | 'lower';\nexport type Side = 'right' | 'left'; // patient's side (NOT viewer's)\nexport type Quadrant = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;\n\nexport interface ToothMeta {\n fdi: FdiId;\n /** '1'–'32' for permanent, 'A'–'T' for primary. */\n universal: string;\n /** Simplified Palmer quadrant-and-position string, e.g. 'UR3', 'LL6'. */\n palmer: string;\n quadrant: Quadrant;\n /** 1–8 (permanent) or 1–5 (primary), counted from the midline outward. */\n positionInQuadrant: number;\n anatomy: Anatomy;\n arch: Arch;\n side: Side;\n dentition: 'permanent' | 'primary';\n}\n\n/* ------------------------------------------------------------------ */\n/* Layout orders */\n/* */\n/* Layout reads from the viewer's perspective, top-left to bottom- */\n/* right. The chart is FIXED in clinical convention: the patient's */\n/* right side sits on the viewer's left, regardless of document `dir`. */\n/* ------------------------------------------------------------------ */\n\n/**\n * 32 permanent teeth in visual layout order:\n * upper row: 18..11 (patient upper-right, outermost → midline), then 21..28 (upper-left, midline → outermost)\n * lower row: 48..41 (patient lower-right, outermost → midline), then 31..38 (lower-left, midline → outermost)\n */\nexport const PERMANENT_TEETH: FdiId[] = [\n // upper\n '18',\n '17',\n '16',\n '15',\n '14',\n '13',\n '12',\n '11',\n '21',\n '22',\n '23',\n '24',\n '25',\n '26',\n '27',\n '28',\n // lower\n '48',\n '47',\n '46',\n '45',\n '44',\n '43',\n '42',\n '41',\n '31',\n '32',\n '33',\n '34',\n '35',\n '36',\n '37',\n '38',\n];\n\n/**\n * 20 primary teeth in visual layout order:\n * upper row: 55..51 (upper-right outermost → midline), then 61..65\n * lower row: 85..81, then 71..75\n */\nexport const PRIMARY_TEETH: FdiId[] = [\n // upper\n '55',\n '54',\n '53',\n '52',\n '51',\n '61',\n '62',\n '63',\n '64',\n '65',\n // lower\n '85',\n '84',\n '83',\n '82',\n '81',\n '71',\n '72',\n '73',\n '74',\n '75',\n];\n\n/* ------------------------------------------------------------------ */\n/* Anatomy by position */\n/* ------------------------------------------------------------------ */\n\nfunction permanentAnatomy(positionInQuadrant: number): Anatomy {\n if (positionInQuadrant <= 2) return 'incisor';\n if (positionInQuadrant === 3) return 'canine';\n if (positionInQuadrant <= 5) return 'premolar';\n return 'molar';\n}\n\nfunction primaryAnatomy(positionInQuadrant: number): Anatomy {\n if (positionInQuadrant <= 2) return 'incisor';\n if (positionInQuadrant === 3) return 'canine';\n // Primary dentition has no premolars — positions 4 & 5 are molars.\n return 'molar';\n}\n\n/* ------------------------------------------------------------------ */\n/* Palmer helper */\n/* ------------------------------------------------------------------ */\n\nfunction palmerCode(quadrant: Quadrant, position: number): string {\n // Permanent: 1=UR, 2=UL, 3=LL, 4=LR. Primary: 5=UR, 6=UL, 7=LL, 8=LR.\n // Mapping is identical within each dentition ring; the number is what\n // changes, so we fold 5→1, 6→2, 7→3, 8→4 to derive the quadrant code.\n const normalised = ((quadrant - 1) % 4) + 1;\n const code =\n normalised === 1\n ? 'UR'\n : normalised === 2\n ? 'UL'\n : normalised === 3\n ? 'LL'\n : 'LR';\n return `${code}${position}`;\n}\n\n/* ------------------------------------------------------------------ */\n/* Universal (ADA) conversion */\n/* */\n/* Permanent — Universal numbers 1..32 run clockwise from the */\n/* patient's upper-right 3rd molar (FDI 18 = Universal 1), across */\n/* the upper arch (18→11 = 1→8, 21→28 = 9→16), then down the left */\n/* lower arch (38→31 = 17→24) and across the right lower (41→48 = */\n/* 25→32). */\n/* */\n/* Primary — Universal letters A..T walk the same path: */\n/* 55→51 = A→E, 61→65 = F→J, 75→71 = K→O, 81→85 = P→T. */\n/* ------------------------------------------------------------------ */\n\nfunction permanentUniversal(quadrant: Quadrant, position: number): string {\n switch (quadrant) {\n case 1:\n // 18→1, 17→2, …, 11→8\n return String(1 + (8 - position));\n case 2:\n // 21→9, 22→10, …, 28→16\n return String(8 + position);\n case 3:\n // 38→17, 37→18, …, 31→24\n return String(17 + (8 - position));\n case 4:\n // 41→25, 42→26, …, 48→32\n return String(24 + position);\n default:\n return '';\n }\n}\n\nfunction primaryUniversal(quadrant: Quadrant, position: number): string {\n const letters = 'ABCDEFGHIJKLMNOPQRST';\n let index = -1;\n switch (quadrant) {\n case 5:\n // 55→A, 54→B, 53→C, 52→D, 51→E\n index = 0 + (5 - position);\n break;\n case 6:\n // 61→F, 62→G, 63→H, 64→I, 65→J\n index = 5 + (position - 1);\n break;\n case 7:\n // 75→K, 74→L, 73→M, 72→N, 71→O\n index = 10 + (5 - position);\n break;\n case 8:\n // 81→P, 82→Q, 83→R, 84→S, 85→T\n index = 15 + (position - 1);\n break;\n default:\n index = -1;\n }\n return index >= 0 ? letters[index] : '';\n}\n\n/* ------------------------------------------------------------------ */\n/* Meta builder */\n/* ------------------------------------------------------------------ */\n\nfunction buildMeta(fdi: FdiId): ToothMeta {\n const quadrant = Number(fdi[0]) as Quadrant;\n const position = Number(fdi[1]);\n const isPrimary = quadrant >= 5;\n const anatomy = isPrimary\n ? primaryAnatomy(position)\n : permanentAnatomy(position);\n const arch: Arch =\n quadrant === 1 || quadrant === 2 || quadrant === 5 || quadrant === 6\n ? 'upper'\n : 'lower';\n const side: Side =\n quadrant === 1 || quadrant === 4 || quadrant === 5 || quadrant === 8\n ? 'right'\n : 'left';\n const universal = isPrimary\n ? primaryUniversal(quadrant, position)\n : permanentUniversal(quadrant, position);\n const palmer = palmerCode(quadrant, position);\n return {\n fdi,\n universal,\n palmer,\n quadrant,\n positionInQuadrant: position,\n anatomy,\n arch,\n side,\n dentition: isPrimary ? 'primary' : 'permanent',\n };\n}\n\nexport const FDI_TO_META: Record<FdiId, ToothMeta> = [\n ...PERMANENT_TEETH,\n ...PRIMARY_TEETH,\n].reduce<Record<FdiId, ToothMeta>>((acc, fdi) => {\n acc[fdi] = buildMeta(fdi);\n return acc;\n}, {});\n\nexport const FDI_TO_UNIVERSAL: Record<FdiId, string> = Object.fromEntries(\n Object.entries(FDI_TO_META).map(([fdi, meta]) => [fdi, meta.universal]),\n);\n\nexport const FDI_TO_PALMER: Record<FdiId, string> = Object.fromEntries(\n Object.entries(FDI_TO_META).map(([fdi, meta]) => [fdi, meta.palmer]),\n);\n\n/**\n * FDI used to resolve a tooth's RENDER. There are no separate deciduous\n * renders — primary teeth reuse the permanent image of the same position\n * (quadrant 5→1, 6→2, 7→3, 8→4; position digit unchanged), scaled down by\n * the renderer. Permanent teeth resolve to themselves.\n */\nexport function assetFdi(meta: ToothMeta): FdiId {\n if (meta.dentition !== 'primary') return meta.fdi;\n return `${meta.quadrant - 4}${meta.positionInQuadrant}`;\n}\n\n/** Render scale applied to a tooth image (primary teeth are smaller). */\nexport const PRIMARY_TOOTH_SCALE = 0.72;\n\n/* ------------------------------------------------------------------ */\n/* Root anatomy */\n/* */\n/* `ToothMeta` carries no root data, so root-scoped findings derive it */\n/* here — from the ARTWORK, which is the only authority that cannot */\n/* 404. The per-root renders name their files by SCREEN position */\n/* (left / right / center), so the anatomical → positional mapping is */\n/* quadrant-sensitive; `rootScreenSide` is the one place that knows. */\n/* ------------------------------------------------------------------ */\n\n/** Positional file stem used by the per-root `rootTreatment` renders. */\nexport type RootFileStem = 'full' | 'left' | 'right' | 'center';\n\n/**\n * The roots a tooth's artwork distinguishes: upper molars are three-rooted\n * (mesial, distal, palatal), lower molars two-rooted, everything else renders\n * as one root. Derived from `assetFdi` rather than `meta.anatomy` because a\n * primary molar reuses PREMOLAR art (54 draws 14) and so has one root's worth\n * of renders. The upper first premolars 14/24 are treated as single-rooted for\n * the same reason — clinically they are often bifurcated, but no second render\n * ships, and a wrong answer here resolves to a file that does not exist.\n */\nexport function rootsForTooth(meta: ToothMeta): Root[] {\n const position = Number(assetFdi(meta)[1]);\n if (position < 6) return ['single'];\n return meta.arch === 'upper'\n ? ['mesial', 'distal', 'palatal']\n : ['mesial', 'distal'];\n}\n\n/**\n * Which SCREEN side a tooth's mesial or distal root faces. The chart is FIXED\n * to clinical orientation — the patient's right sits on the viewer's left in\n * BOTH arches — so the midline falls on the viewer's RIGHT for the patient's\n * right quadrants (1 & 4) and on the viewer's LEFT for the patient's left\n * quadrants (2 & 3). Mesial means \"toward the midline\", so it flips with it;\n * distal is always its opposite. This is the same flip `zoneSurface` applies\n * to the plan view's left/right zones. Primary quadrants normalise (5-8 → 1-4).\n */\nexport function rootScreenSide(\n meta: ToothMeta,\n root: 'mesial' | 'distal',\n): 'left' | 'right' {\n const q = meta.quadrant <= 4 ? meta.quadrant : meta.quadrant - 4;\n const mesialIsRight = q === 1 || q === 4;\n if (root === 'mesial') return mesialIsRight ? 'right' : 'left';\n return mesialIsRight ? 'left' : 'right';\n}\n\n/**\n * Map an anatomical root to the `rootTreatment` file stem. `center` is the\n * palatal root and ships for upper molars only; a root the tooth does not\n * distinguish falls back to the whole-tooth render rather than a dead URL.\n */\nexport function rootFileStem(meta: ToothMeta, root: Root): RootFileStem {\n if (!rootsForTooth(meta).includes(root)) return 'full';\n if (root === 'palatal') return 'center';\n if (root === 'mesial' || root === 'distal') return rootScreenSide(meta, root);\n return 'full';\n}\n\n/* ------------------------------------------------------------------ */\n/* Condition → token + pattern */\n/* */\n/* Every condition carries BOTH a colour AND a fill pattern so */\n/* colour-blind clinicians can distinguish states in the SVG (and in */\n/* black-and-white prints) — colour is never the sole channel. */\n/* */\n/* The closed palette has fewer semantic families than the vocabulary */\n/* has findings, so conditions are grouped by CLINICAL family and the */\n/* artwork glyph carries the distinction within a family. `--success` */\n/* is deliberately left free: it means \"executed\" on a symbol's status */\n/* and would read ambiguously as a condition tint. */\n/* ------------------------------------------------------------------ */\n\n/** CSS variable name (without the leading `var(...)`) that drives a condition's colour. */\nexport const CONDITION_TOKENS: Record<ToothCondition, string> = {\n caries: '--destructive',\n filled: '--info',\n crowned: '--warning',\n temporaryCrown: '--warning',\n bridge: '--primary',\n missing: '--muted-foreground',\n implant: '--accent',\n implantExtraction: '--accent',\n stub: '--muted-foreground',\n destroyed: '--destructive',\n rootCanal: '--primary',\n // Endodontics — a post sits inside the root next to the root canal.\n pin: '--primary',\n // Apicectomy removes tooth structure: the pathology-and-loss family.\n rootAmputation: '--destructive',\n // Fixtures & appliances, alongside the implants.\n anchorage: '--accent',\n hook: '--accent',\n attachment: '--accent',\n // Soft-tissue work, alongside the crown family. These three ship side art but\n // no plan art, so in the occlusal view they fall through to the glyph badge —\n // a thin stroke, where `--warning` (yellow-500) is ~1.6:1 on white. The\n // readable alias is the one built for that job.\n mucogingivalSurgery: '--warning-readable',\n periodontalSurgery: '--warning-readable',\n curettage: '--warning-readable',\n // An interim dressing — reads as a restoration.\n medication: '--info',\n // Purely diagnostic, nothing on the tooth to tint.\n xray: '--muted-foreground',\n};\n\n/**\n * Convenience: the `fill=\"var(--destructive)\"` string ready to paste onto\n * an SVG attribute. DERIVED from `CONDITION_TOKENS` so the two can never drift.\n * Used for the legend swatch + the surface-condition ring; whole-tooth\n * conditions render a distinct symbol image (the glyph is the\n * colour-independent channel).\n */\nexport const CONDITION_COLORS: Record<ToothCondition, string> = Object.entries(\n CONDITION_TOKENS,\n).reduce<Record<ToothCondition, string>>(\n (acc, [condition, token]) => {\n acc[condition as ToothCondition] = `var(${token})`;\n return acc;\n },\n {} as Record<ToothCondition, string>,\n);\n\n/**\n * Every built-in condition id, in declaration order. Derived from\n * `CONDITION_TOKENS`, which the compiler forces to be exhaustive — so a new\n * condition can never silently fall out of a hand-maintained list.\n */\nexport const TOOTH_CONDITIONS: ToothCondition[] = Object.keys(\n CONDITION_TOKENS,\n) as ToothCondition[];\n\n/** True when an id names a built-in condition rather than a registry entry. */\nexport function isBuiltInCondition(id: ConditionId): id is ToothCondition {\n return id in CONDITION_TOKENS;\n}\n\n/** The layer each built-in finding marks, used when a symbol omits `part`. */\nexport const CONDITION_PART: Record<ToothCondition, SymbolPart> = {\n caries: 'surface',\n filled: 'surface',\n crowned: 'element',\n temporaryCrown: 'element',\n bridge: 'element',\n missing: 'element',\n implant: 'element',\n implantExtraction: 'element',\n stub: 'element',\n destroyed: 'element',\n rootCanal: 'root',\n pin: 'root',\n rootAmputation: 'root',\n // Flank-mounted appliances and gingival work sit on the whole element; their\n // mesial/distal or left/right discriminator selects a side, not a root.\n anchorage: 'element',\n hook: 'element',\n attachment: 'element',\n mucogingivalSurgery: 'element',\n periodontalSurgery: 'element',\n curettage: 'element',\n medication: 'element',\n xray: 'element',\n};\n\n/**\n * Built-ins the chart draws ITSELF rather than from artwork — `missing` is an\n * SVG cross, `caries` / `filled` fall back to the surface ring in the side\n * view. They must never take the registry's glyph badge.\n */\nexport const INTRINSIC_CONDITIONS: ToothCondition[] = [\n 'missing',\n 'caries',\n 'filled',\n];\n\nconst INTRINSIC = new Set<string>(INTRINSIC_CONDITIONS);\n\n/* ------------------------------------------------------------------ */\n/* Execution status */\n/* */\n/* Planned vs executed follows the AlfaDocs Care Plan convention */\n/* (blue = planned, green = executed). Status is NEVER signalled by */\n/* colour alone: planned symbols also render at reduced opacity and */\n/* fold the status into the tooth's accessible name and tooltip. */\n/* ------------------------------------------------------------------ */\n\nexport const STATUS_TOKENS: Record<SymbolStatus, string> = {\n planned: '--info',\n executed: '--success',\n};\n\n/** The `var(...)` form of `STATUS_TOKENS`, ready for an SVG attribute. */\nexport const STATUS_COLORS: Record<SymbolStatus, string> = {\n planned: `var(${STATUS_TOKENS.planned})`,\n executed: `var(${STATUS_TOKENS.executed})`,\n};\n\n/** The second, non-colour channel: a planned symbol renders lighter. */\nexport const STATUS_OPACITY: Record<SymbolStatus, number> = {\n planned: 0.55,\n executed: 1,\n};\n\n/** i18n key for a status label (`ui.toothScheme.status.*`). */\nexport function statusLabelKey(status: SymbolStatus): string {\n return `toothScheme.status.${status}`;\n}\n\n/* ------------------------------------------------------------------ */\n/* Condition → side-view symbol overlay */\n/* */\n/* Whole-tooth findings overlay a photoreal symbol render on the */\n/* facial tooth. `caries` / `filled` are per-surface findings shown */\n/* in the plan (occlusal) view and carry no side symbol here. The */\n/* folder layout is `{symbol}/{top|bottom}/...` under `side/`. */\n/* ------------------------------------------------------------------ */\n\n/**\n * Screen-relative marker slot. `repeat` is what the BARE gingival-surgery type\n * resolves to — it is the default marker, not a \"repeat treatment\" variant.\n */\nexport type SymbolRegion = 'left' | 'right' | 'total' | 'repeat';\n\n/** Corner glyph stem in `side/other/` — curettage, medication, x-ray. */\nexport type SymbolLetter = 'c' | 'm' | 'x';\n\n/**\n * How a symbol's file resolves inside `{folder}/{view}/`. A discriminated union\n * rather than a bare enum because two shapes carry their own data: `per-fdi-root`\n * has to know whether a multi-rooted tooth also ships a whole-tooth render, and\n * `letter` pins the single glyph its condition owns.\n */\nexport type SideSymbolSpec =\n /** `{fdi}.webp` — one render per tooth. */\n | { folder: string; resolve: 'per-fdi' }\n /** `general.webp` — one render per arch. */\n | { folder: string; resolve: 'general' }\n /** `{fdi}/{full|left|right|center}.webp` — per-FDI subdirectory. */\n | { folder: string; resolve: 'rct' }\n /**\n * `{fdi}[-{mesial|distal|palatal}].webp`. `wholeTooth` distinguishes two\n * folders that look symmetric and are not: `pin` ships a bare file for every\n * FDI, `rootAmputation` only for single-rooted teeth.\n */\n | {\n folder: string;\n resolve: 'per-fdi-root';\n wholeTooth: 'always' | 'single-rooted-only';\n }\n /** `{fdi}-{mesial|distal}.webp` — always suffixed, no bare file. */\n | { folder: string; resolve: 'per-fdi-side' }\n /** `{fdi}_{left|right}.webp` — the only folder using an UNDERSCORE. */\n | { folder: string; resolve: 'per-fdi-lr' }\n /** `{left|right|total|repeat}.webp` — no FDI in the name. */\n | { folder: string; resolve: 'region' }\n /** `{left|right}.webp` — no FDI, and only the two sides ship. */\n | { folder: string; resolve: 'screen-side' }\n /** `{c|m|x}.webp`. */\n | { folder: string; resolve: 'letter'; letter: SymbolLetter };\n\n/**\n * Which instance of a symbol to resolve. The two fields are independent\n * discriminators read by different `resolve` strategies (roots by the per-root\n * shapes, regions by the screen-side ones) — passed in, never module state.\n */\nexport interface SymbolTarget {\n /** Root, or tooth side for a flank-mounted marker. */\n root?: Root;\n /** Explicit screen-relative slot; wins over `root` where both apply. */\n region?: SymbolRegion;\n}\n\nexport const CONDITION_SIDE_SYMBOL: Partial<\n Record<ToothCondition, SideSymbolSpec>\n> = {\n crowned: { folder: 'crown', resolve: 'per-fdi' },\n temporaryCrown: { folder: 'temporaryCrown', resolve: 'per-fdi' },\n bridge: { folder: 'replacement', resolve: 'per-fdi' },\n destroyed: { folder: 'destroyed', resolve: 'per-fdi' },\n implant: { folder: 'implant', resolve: 'general' },\n implantExtraction: { folder: 'implantExtraction', resolve: 'general' },\n stub: { folder: 'stub', resolve: 'general' },\n rootCanal: { folder: 'rootTreatment', resolve: 'rct' },\n pin: { folder: 'pin', resolve: 'per-fdi-root', wholeTooth: 'always' },\n rootAmputation: {\n folder: 'rootAmputation',\n resolve: 'per-fdi-root',\n wholeTooth: 'single-rooted-only',\n },\n anchorage: { folder: 'anchorage', resolve: 'per-fdi-side' },\n hook: { folder: 'hook', resolve: 'per-fdi-lr' },\n attachment: { folder: 'attachment', resolve: 'general' },\n mucogingivalSurgery: { folder: 'mucogingivalSurgery', resolve: 'region' },\n periodontalSurgery: { folder: 'periodontalSurgery', resolve: 'region' },\n curettage: { folder: 'other', resolve: 'letter', letter: 'c' },\n medication: { folder: 'other', resolve: 'letter', letter: 'm' },\n xray: { folder: 'other', resolve: 'letter', letter: 'x' },\n};\n\n/**\n * Resolve the screen side a marker sits on: an explicit region wins, else a\n * mesial/distal root is flipped through the quadrant, else the fallback.\n */\nfunction markerSide(\n meta: ToothMeta,\n target: SymbolTarget,\n fallback: 'left' | 'right',\n): 'left' | 'right' {\n if (target.region === 'left' || target.region === 'right')\n return target.region;\n if (target.root === 'mesial' || target.root === 'distal')\n return rootScreenSide(meta, target.root);\n return fallback;\n}\n\n/**\n * Build a symbol overlay URL for either projection. Every branch is gated on\n * what actually ships, never on the platform's class emission — the platform\n * SCSS emits palatal pins for lower molars and per-root amputations for\n * anteriors, and both are live 404s.\n */\nfunction symbolUrl(\n projection: Projection,\n meta: ToothMeta,\n assetBaseUrl: string,\n spec: SideSymbolSpec,\n target: SymbolTarget,\n): string {\n const view = meta.arch === 'upper' ? 'top' : 'bottom';\n const base = `${assetBaseUrl}/${projection}/${spec.folder}/${view}`;\n const fdi = assetFdi(meta);\n switch (spec.resolve) {\n case 'general':\n return `${base}/general.webp`;\n case 'rct': {\n const stem = target.root ? rootFileStem(meta, target.root) : 'full';\n return `${base}/${fdi}/${stem}.webp`;\n }\n case 'per-fdi-root': {\n const roots = rootsForTooth(meta);\n const root = target.root;\n if (root && root !== 'single' && roots.includes(root))\n return `${base}/${fdi}-${root}.webp`;\n if (spec.wholeTooth === 'always' || roots.length === 1)\n return `${base}/${fdi}.webp`;\n // A molar amputation has no whole-tooth render — mark the mesial root.\n return `${base}/${fdi}-mesial.webp`;\n }\n case 'per-fdi-side':\n return `${base}/${fdi}-${target.root === 'distal' ? 'distal' : 'mesial'}.webp`;\n case 'per-fdi-lr':\n return `${base}/${fdi}_${markerSide(meta, target, 'left')}.webp`;\n case 'region':\n return `${base}/${target.region ?? 'repeat'}.webp`;\n case 'screen-side':\n return `${base}/${markerSide(meta, target, 'left')}.webp`;\n case 'letter':\n return `${base}/${spec.letter}.webp`;\n case 'per-fdi':\n default:\n return `${base}/${fdi}.webp`;\n }\n}\n\n/**\n * Resolve the side-view symbol overlay URL for a condition on a tooth. `target`\n * picks a specific root / side; omitted, every strategy resolves to the\n * whole-tooth or default marker, which is what the legacy call sites want.\n */\nexport function conditionSymbolUrl(\n meta: ToothMeta,\n assetBaseUrl: string,\n spec: SideSymbolSpec,\n target: SymbolTarget = {},\n): string {\n return symbolUrl('side', meta, assetBaseUrl, spec, target);\n}\n\n/* ------------------------------------------------------------------ */\n/* Plan (occlusal) view — per-surface findings */\n/* */\n/* The \"Stato dentale\" / Findings surface marks caries & fillings on */\n/* individual tooth surfaces in the occlusal (plan) view. The asset */\n/* surface names differ from the clinical `Surface` enum: buccal → */\n/* vestibular (cheek/lip side), lingual → oral (tongue side). */\n/* ------------------------------------------------------------------ */\n\nexport type Projection = 'side' | 'plan';\n/** The user-selectable view: a single projection, or both stacked together. */\nexport type ViewMode = Projection | 'both';\n\n/** Map the clinical `Surface` enum to the asset surface filename token. */\nexport const SURFACE_ASSET_NAME: Record<Surface, string> = {\n mesial: 'mesial',\n distal: 'distal',\n occlusal: 'occlusal',\n buccal: 'vestibular',\n lingual: 'oral',\n};\n\n/** Conditions that render as per-surface fills in the plan view. */\nexport const CONDITION_PLAN_FOLDER: Partial<Record<ToothCondition, string>> = {\n caries: 'caries',\n filled: 'filling',\n};\n\n/** Resolve a per-surface plan-view overlay URL (e.g. caries on the distal). */\nexport function surfaceSymbolUrl(\n meta: ToothMeta,\n assetBaseUrl: string,\n folder: string,\n surface: Surface,\n): string {\n const view = meta.arch === 'upper' ? 'top' : 'bottom';\n return `${assetBaseUrl}/plan/${folder}/${view}/${assetFdi(meta)}-${SURFACE_ASSET_NAME[surface]}.webp`;\n}\n\n/**\n * Whole-tooth symbols in the PLAN projection. Without these a crowned tooth\n * reads as a plain tooth in the occlusal view, which also made `projection`\n * `'both'` disagree with itself between its own rows.\n *\n * `anchorage` is the odd one: the plan art is two generic screen-side markers\n * (no FDI in the name) composited over the default render, so its mesial/distal\n * root has to be flipped through the quadrant — `symbolUrl` does that.\n */\nexport const CONDITION_PLAN_SYMBOL: Partial<\n Record<ToothCondition, SideSymbolSpec>\n> = {\n crowned: { folder: 'crown', resolve: 'per-fdi' },\n temporaryCrown: { folder: 'temporaryCrown', resolve: 'per-fdi' },\n // The bundled plan/replacement renders are byte-identical to plan/default, so\n // this overlay is a visual no-op until distinct bridge art lands.\n bridge: { folder: 'replacement', resolve: 'per-fdi' },\n implant: { folder: 'implant', resolve: 'general' },\n implantExtraction: { folder: 'implantExtraction', resolve: 'general' },\n stub: { folder: 'stub', resolve: 'general' },\n anchorage: { folder: 'anchorage', resolve: 'screen-side' },\n};\n\n/** Resolve the plan-view whole-tooth symbol overlay URL for a condition. */\nexport function planSymbolUrl(\n meta: ToothMeta,\n assetBaseUrl: string,\n spec: SideSymbolSpec,\n target: SymbolTarget = {},\n): string {\n return symbolUrl('plan', meta, assetBaseUrl, spec, target);\n}\n\n/** The canvas every plan render is drawn on, bar the exceptions below. */\nconst PLAN_CANVAS = 328;\n\n/**\n * Plan renders that ship on a LARGER canvas than the rest. Their tooth is drawn\n * at the same absolute size, so the feature occupies proportionally less of the\n * frame — fitted into the same rect as a 328 render it lands ~7.5% inset on\n * every side and visibly misregisters (tooth 36 crowned is the shipped case).\n * Forcing width/height cannot correct this; only re-scaling by the ratio can.\n */\nconst PLAN_CANVAS_OVERRIDES: Record<string, number> = {\n 'anchorage/bottom/left.webp': 375,\n 'anchorage/bottom/right.webp': 375,\n 'anchorage/top/left.webp': 375,\n 'anchorage/top/right.webp': 375,\n 'crown/bottom/36.webp': 375,\n 'implant/bottom/general.webp': 375,\n 'implant/top/general.webp': 375,\n 'implantExtraction/bottom/general.webp': 375,\n 'implantExtraction/top/general.webp': 375,\n 'replacement/bottom/background.webp': 375,\n 'replacement/top/background.webp': 375,\n 'stub/bottom/general.webp': 375,\n 'stub/top/general.webp': 375,\n 'temporaryCrown/bottom/36.webp': 375,\n 'temporaryCrown/bottom/37.webp': 348,\n 'temporaryCrown/bottom/46.webp': 375,\n 'temporaryCrown/bottom/47.webp': 348,\n};\n\n/**\n * How much to scale a plan overlay so its tooth registers with the base render.\n * `1` for everything on the standard canvas.\n */\nexport function planOverlayScale(url: string): number {\n const key = url.split('/plan/')[1];\n const canvas = key ? PLAN_CANVAS_OVERRIDES[key] : undefined;\n return canvas ? canvas / PLAN_CANVAS : 1;\n}\n\n/* ------------------------------------------------------------------ */\n/* Condition registry — practice-defined findings */\n/* */\n/* `ToothCondition` is closed and baked into the token maps and the */\n/* `ui.*` i18n keys, so a practice cannot add its own finding without */\n/* forking the kit. A registry entry supplies its own already- */\n/* translated label (the consumer owns custom copy — the kit must not */\n/* invent `ui.*` keys for it) and, where art exists, its own specs. */\n/* Every resolver below reads registry → built-in → fallback, field by */\n/* field, so an entry that only re-tints a built-in keeps its artwork. */\n/* ------------------------------------------------------------------ */\n\nexport interface ConditionDefinition {\n id: string;\n /** Already-translated label — the CONSUMER owns custom-condition copy. */\n label: string;\n color?: string;\n part?: SymbolPart;\n sideSymbol?: SideSymbolSpec;\n planSymbol?: SideSymbolSpec;\n planFolder?: string;\n /** 1–3 char mark rendered when the condition has no artwork. */\n glyph?: string;\n}\n\nexport type ConditionRegistry = readonly ConditionDefinition[];\n\n/**\n * A condition's display name. Built-ins resolve to an i18n key; registry\n * entries carry literal text, because their copy lives in the consuming app.\n */\nexport type ConditionLabel =\n | { kind: 'key'; key: string }\n | { kind: 'text'; text: string };\n\n/** Colour used when a condition is neither registered nor built in. */\nexport const FALLBACK_CONDITION_TOKEN = '--muted-foreground';\n\n/** i18n key for a built-in condition label (`ui.toothScheme.condition.*`). */\nexport function conditionLabelKey(condition: ToothCondition): string {\n return `toothScheme.condition.${condition}`;\n}\n\n/**\n * Look up a registry entry. Registry ids shadow built-ins field by field, so a\n * practice can re-tint or re-label a shipped finding without losing its art.\n */\nexport function conditionDefinition(\n condition: ConditionId,\n registry?: ConditionRegistry,\n): ConditionDefinition | undefined {\n return registry?.find((entry) => entry.id === condition);\n}\n\nexport function conditionLabel(\n condition: ConditionId,\n registry?: ConditionRegistry,\n): ConditionLabel {\n const custom = conditionDefinition(condition, registry);\n if (custom) return { kind: 'text', text: custom.label };\n if (isBuiltInCondition(condition))\n return { kind: 'key', key: conditionLabelKey(condition) };\n // An unregistered custom id still names itself rather than rendering blank.\n return { kind: 'text', text: condition };\n}\n\n/** Paintable colour for a condition — never empty, so nothing renders unseen. */\nexport function conditionColor(\n condition: ConditionId,\n registry?: ConditionRegistry,\n): string {\n const custom = conditionDefinition(condition, registry);\n if (custom?.color) return custom.color;\n if (isBuiltInCondition(condition)) return CONDITION_COLORS[condition];\n return `var(${FALLBACK_CONDITION_TOKEN})`;\n}\n\n/** Paint colour for ONE charted symbol — its own override wins. */\nexport function symbolColor(\n symbol: ToothSymbol,\n registry?: ConditionRegistry,\n): string {\n return symbol.color ?? conditionColor(symbol.condition, registry);\n}\n\nexport function conditionSideSymbol(\n condition: ConditionId,\n registry?: ConditionRegistry,\n): SideSymbolSpec | undefined {\n return (\n conditionDefinition(condition, registry)?.sideSymbol ??\n (isBuiltInCondition(condition)\n ? CONDITION_SIDE_SYMBOL[condition]\n : undefined)\n );\n}\n\nexport function conditionPlanSymbol(\n condition: ConditionId,\n registry?: ConditionRegistry,\n): SideSymbolSpec | undefined {\n return (\n conditionDefinition(condition, registry)?.planSymbol ??\n (isBuiltInCondition(condition)\n ? CONDITION_PLAN_SYMBOL[condition]\n : undefined)\n );\n}\n\nexport function conditionPlanFolder(\n condition: ConditionId,\n registry?: ConditionRegistry,\n): string | undefined {\n return (\n conditionDefinition(condition, registry)?.planFolder ??\n (isBuiltInCondition(condition)\n ? CONDITION_PLAN_FOLDER[condition]\n : undefined)\n );\n}\n\nexport function conditionPart(\n condition: ConditionId,\n registry?: ConditionRegistry,\n): SymbolPart {\n return (\n conditionDefinition(condition, registry)?.part ??\n (isBuiltInCondition(condition) ? CONDITION_PART[condition] : 'element')\n );\n}\n\n/** Split `rootAmputation` → `RA`, `pin` → `P`. */\nfunction initialsFrom(id: string): string {\n const words = id\n .replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n .split(/[\\s._-]+/)\n .filter(Boolean);\n if (words.length === 0) return '?';\n return words\n .slice(0, 2)\n .map((word) => word[0].toUpperCase())\n .join('')\n .slice(0, 3);\n}\n\n/**\n * The 1–3 character mark that stands in when a condition ships no artwork.\n * Falls back to the id's initials so a practice-defined finding can be charted\n * the day it is registered, rather than rendering as a silent blank.\n */\nexport function conditionGlyph(\n condition: ConditionId,\n registry?: ConditionRegistry,\n): string {\n const glyph = conditionDefinition(condition, registry)?.glyph;\n return glyph ? glyph.slice(0, 3) : initialsFrom(condition);\n}\n\n/**\n * Whether a condition has artwork in a projection. When false the renderer\n * must fall back to the glyph badge — that seam is what lets a practice chart\n * a finding before a designer draws it.\n */\nexport function conditionHasArtwork(\n condition: ConditionId,\n projection: Projection,\n registry?: ConditionRegistry,\n): boolean {\n if (projection === 'side') {\n if (conditionSideSymbol(condition, registry)) return true;\n return INTRINSIC.has(condition);\n }\n if (conditionPlanSymbol(condition, registry)) return true;\n if (conditionPlanFolder(condition, registry)) return true;\n return INTRINSIC.has(condition);\n}\n\n/* ------------------------------------------------------------------ */\n/* Symbol normaliser */\n/* */\n/* The ONE way to read findings off a `ToothState`. Legacy charts */\n/* carry a flat `conditions` + `surfaces` pair; rich charts carry */\n/* `symbols`. Normalising here means the renderer has a single code */\n/* path and the two shapes can never drift apart visually. */\n/* ------------------------------------------------------------------ */\n\n/**\n * The findings on a tooth as a symbol list. `state.symbols` wins outright when\n * present (an explicit empty array therefore means \"no findings\"); otherwise\n * the legacy pair is derived faithfully — the shared `surfaces` list attaches\n * only to surface-part conditions, exactly as the flat renderer treated it.\n */\nexport function symbolsForTooth(\n state: ToothState | undefined,\n registry?: ConditionRegistry,\n): ToothSymbol[] {\n if (!state) return [];\n if (state.symbols) return state.symbols;\n return state.conditions.map<ToothSymbol>((condition) => {\n const part = conditionPart(condition, registry);\n return part === 'surface' && state.surfaces.length > 0\n ? { condition, part, surfaces: state.surfaces }\n : { condition, part };\n });\n}\n\n/* ------------------------------------------------------------------ */\n/* Surface zones — interactive per-surface picking (plan view) */\n/* ------------------------------------------------------------------ */\n\n/**\n * Clickable surface regions of a plan-view tooth, named by SCREEN position.\n * `zoneSurface()` resolves each to the anatomical `Surface` per quadrant.\n */\nexport type SurfaceZone = 'top' | 'bottom' | 'left' | 'right' | 'center';\n\n/**\n * Which zones a tooth exposes. Molars & premolars have a central occlusal\n * table (5 zones); incisors & canines have no occlusal surface to mark, so\n * their crown splits into 4 directional zones.\n */\nexport function zonesForTooth(meta: ToothMeta): SurfaceZone[] {\n const fiveZone = meta.anatomy === 'molar' || meta.anatomy === 'premolar';\n return fiveZone\n ? ['top', 'bottom', 'left', 'right', 'center']\n : ['top', 'bottom', 'left', 'right'];\n}\n\n/**\n * Map a screen zone to the anatomical surface, QUADRANT-AWARE. Two flips make\n * this non-trivial (ported from the platform odontogram's `_getAreaBySide`, so\n * a finding always lands on the correct surface wherever the tooth sits):\n *\n * - mesial / distal (left & right zones) flip between the patient's right\n * quadrants {1,4} and left quadrants {2,3} — \"toward the midline\" reverses.\n * - buccal(vestibular) / lingual(oral) (top & bottom zones) flip between the\n * upper and lower arch.\n * - centre is always occlusal.\n *\n * Primary quadrants (5-8) normalise to their permanent counterpart (1-4).\n */\nexport function zoneSurface(meta: ToothMeta, zone: SurfaceZone): Surface {\n if (zone === 'center') return 'occlusal';\n if (zone === 'top') return meta.arch === 'upper' ? 'buccal' : 'lingual';\n if (zone === 'bottom') return meta.arch === 'upper' ? 'lingual' : 'buccal';\n // left / right → mesial / distal, by quadrant.\n const q = meta.quadrant <= 4 ? meta.quadrant : meta.quadrant - 4;\n const leftIsDistal = q === 1 || q === 4; // Q1 (upper-right) & Q4 (lower-left)\n if (zone === 'left') return leftIsDistal ? 'distal' : 'mesial';\n return leftIsDistal ? 'mesial' : 'distal';\n}\n\nconst r2 = (n: number) => Math.round(n * 100) / 100;\n\n/**\n * SVG `d` path for a clickable surface zone within a tooth's local box.\n * 5-zone teeth frame a central occlusal rect with four edge trapezoids;\n * 4-zone teeth split the box along both diagonals into four triangles meeting\n * at the centre.\n */\nexport function zonePath(\n zone: SurfaceZone,\n rect: { x: number; y: number; w: number; h: number },\n hasCenter: boolean,\n): string {\n const { x, y, w, h } = rect;\n const x1 = x + w;\n const y1 = y + h;\n const p = (px: number, py: number) => `${r2(px)} ${r2(py)}`;\n if (!hasCenter) {\n const cx = x + w / 2;\n const cy = y + h / 2;\n switch (zone) {\n case 'top':\n return `M ${p(x, y)} L ${p(x1, y)} L ${p(cx, cy)} Z`;\n case 'bottom':\n return `M ${p(x, y1)} L ${p(x1, y1)} L ${p(cx, cy)} Z`;\n case 'left':\n return `M ${p(x, y)} L ${p(cx, cy)} L ${p(x, y1)} Z`;\n case 'right':\n return `M ${p(x1, y)} L ${p(cx, cy)} L ${p(x1, y1)} Z`;\n default:\n return '';\n }\n }\n // 5-zone: central occlusal rect inset 30% on each axis, four trapezoids.\n const ix = x + w * 0.3;\n const iy = y + h * 0.3;\n const ix1 = x + w * 0.7;\n const iy1 = y + h * 0.7;\n switch (zone) {\n case 'center':\n return `M ${p(ix, iy)} L ${p(ix1, iy)} L ${p(ix1, iy1)} L ${p(ix, iy1)} Z`;\n case 'top':\n return `M ${p(x, y)} L ${p(x1, y)} L ${p(ix1, iy)} L ${p(ix, iy)} Z`;\n case 'bottom':\n return `M ${p(x, y1)} L ${p(x1, y1)} L ${p(ix1, iy1)} L ${p(ix, iy1)} Z`;\n case 'left':\n return `M ${p(x, y)} L ${p(ix, iy)} L ${p(ix, iy1)} L ${p(x, y1)} Z`;\n case 'right':\n return `M ${p(x1, y)} L ${p(ix1, iy)} L ${p(ix1, iy1)} L ${p(x1, y1)} Z`;\n default:\n return '';\n }\n}\n\n/* ------------------------------------------------------------------ */\n/* Viewport layout helpers */\n/* */\n/* Image-based rendering: every photoreal render shares a fixed canvas */\n/* (375×808 facial, 328×328 occlusal), so relative tooth size is baked */\n/* into the artwork. The chart lays teeth out in UNIFORM slots and lets */\n/* each image carry its own anatomy. */\n/* ------------------------------------------------------------------ */\n\nexport const TOOTH_GAP = 5;\nexport const TOOTH_ROW_GAP = 40;\nexport const TOOTH_HIT_PAD = 4;\nexport const TOOTH_SLOT_WIDTH = 36;\n/** Facial-view slot block-size; the plan/occlusal view uses a shorter slot. */\nexport const TOOTH_SLOT_HEIGHT = 62;\n\n/**\n * Resolve a tooth render URL. `view` follows the arch (`top` = upper,\n * `bottom` = lower); `symbol` selects the artwork layer (`default` is the\n * healthy tooth; condition/finding overlays use their symbol folder).\n */\nexport function toothImageUrl(\n meta: ToothMeta,\n assetBaseUrl: string,\n symbol = 'default',\n projection: 'side' | 'plan' = 'side',\n): string {\n const view = meta.arch === 'upper' ? 'top' : 'bottom';\n return `${assetBaseUrl}/${projection}/${symbol}/${view}/${assetFdi(meta)}.webp`;\n}\n\nexport interface PositionedTooth {\n fdi: FdiId;\n meta: ToothMeta;\n x: number;\n y: number;\n}\n\n/**\n * Produce the positioned list of teeth for a given dentition. The layout\n * is a two-row grid reading viewer-left-to-right across the upper arch,\n * then viewer-left-to-right across the lower arch. Clinical convention\n * is preserved: the patient's right sits on the viewer's left in both\n * arches, regardless of document direction.\n */\nexport function layoutTeeth(\n dentition: Dentition,\n slotHeight: number = TOOTH_SLOT_HEIGHT,\n options: { arch?: Arch } = {},\n): {\n teeth: PositionedTooth[];\n width: number;\n height: number;\n} {\n const ids: FdiId[] =\n dentition === 'primary'\n ? PRIMARY_TEETH\n : dentition === 'mixed'\n ? [...PERMANENT_TEETH, ...PRIMARY_TEETH]\n : PERMANENT_TEETH;\n\n // Upper / lower partition is inferred from meta.arch.\n const upper: FdiId[] = [];\n const lower: FdiId[] = [];\n for (const id of ids) {\n const meta = FDI_TO_META[id];\n if (meta.arch === 'upper') upper.push(id);\n else lower.push(id);\n }\n\n // Uniform slots — relative tooth size is baked into each render, so every\n // tooth occupies the same slot and the artwork carries the anatomy.\n const slot = TOOTH_SLOT_WIDTH + TOOTH_GAP;\n // Both arches carry the same count in every dentition (permanent 16/16,\n // primary 10/10, mixed 26/26), so the row width is symmetric — a single-arch\n // layout therefore aligns column-for-column with its opposite arch, which the\n // `both` view relies on to stack the four rows.\n const maxCount = Math.max(upper.length, lower.length);\n const maxRowWidth = maxCount > 0 ? maxCount * slot - TOOTH_GAP : 0;\n\n function positionRow(row: FdiId[], yOffset: number): PositionedTooth[] {\n const rowWidth = row.length > 0 ? row.length * slot - TOOTH_GAP : 0;\n const startX = (maxRowWidth - rowWidth) / 2;\n return row.map((id, i) => {\n const meta = FDI_TO_META[id];\n return {\n fdi: id,\n meta,\n x: startX + i * slot,\n y: yOffset,\n };\n });\n }\n\n // Single-arch layout (the `both` view renders one arch per SVG so a tooth's\n // FDI id stays unique within each SVG): a single row at y = 0.\n if (options.arch) {\n const row = options.arch === 'upper' ? upper : lower;\n const teeth = positionRow(row, 0);\n return { teeth, width: maxRowWidth, height: slotHeight };\n }\n\n const upperRow = positionRow(upper, 0);\n const lowerRow = positionRow(lower, slotHeight + TOOTH_ROW_GAP);\n\n const width = maxRowWidth > 0 ? maxRowWidth : maxCount * slot;\n const height = slotHeight * 2 + TOOTH_ROW_GAP;\n\n return { teeth: [...upperRow, ...lowerRow], width, height };\n}\n\n/* ------------------------------------------------------------------ */\n/* Special elements — quadrants, sextants, arches, whole-mouth. */\n/* */\n/* The platform odontogram appends selectable \"abstract elements\" to */\n/* the inline-end of each arch row (see platform */\n/* `AlfaDocs/Domain/ToothScheme/Elements.php`): quadrants (Q1–Q4), */\n/* arches (AS / AI), a whole-mouth General, and sextants (S1–S6). */\n/* They let a clinician record a finding / treatment that spans a */\n/* region rather than a single tooth (e.g. a denture on the upper */\n/* arch, scaling on a sextant). Quadrant / arch / general sit on the */\n/* facial (side) rows; sextants sit on the occlusal (plan) rows, */\n/* column-aligned beneath / above their quadrant — matching the */\n/* platform's QuarterWithSextant / JawWithSextant pairing. */\n/* ------------------------------------------------------------------ */\n\nexport type SpecialElementKind = 'quadrant' | 'sextant' | 'arch' | 'general';\n\nexport interface SpecialElement {\n /**\n * Stable id — a chart key and the value emitted by `onSelect`. Permanent:\n * `Q1`–`Q4`, `S1`–`S6`, `AS`, `AI`, `general`. Primary mirrors with an `m`\n * prefix: `mQ1`…, `mS1`…, `mAS`, `mAI`, `mGeneral`.\n */\n id: string;\n kind: SpecialElementKind;\n arch: Arch;\n dentition: 'permanent' | 'primary';\n /** i18n token under `toothScheme.special.*` for the accessible name. */\n labelKey: 'quadrant' | 'sextant' | 'archUpper' | 'archLower' | 'general';\n /** Interpolated into the `{{n}}` of quadrant / sextant labels (e.g. `1`). */\n labelIndex?: number;\n /** Short code shown in the slot, e.g. `Q1`, `S3`, `AS`, `Gen.`. */\n code: string;\n /**\n * Member teeth (FDI) — informational for consumers that highlight or expand a\n * region. Selection itself keys on `id`; the platform expands membership\n * server-side. Sextants follow the WHO/CPITN convention.\n */\n teeth: FdiId[];\n /**\n * Column within the special-element block. Quadrant and its paired sextant\n * share a column so they stack (Q1↕S1, Q2↕S2, AS↕S3 / S4↕Q4, S5↕Q3, S6↕AI).\n */\n column: number;\n /** Row the element attaches to: side (quadrant/arch/general) or plan (sextant). */\n row: Projection;\n}\n\nconst teethInQuadrant = (q: Quadrant): FdiId[] =>\n [...PERMANENT_TEETH, ...PRIMARY_TEETH].filter(\n (id) => FDI_TO_META[id].quadrant === q,\n );\n\nconst PERMANENT_SPECIALS: SpecialElement[] = [\n // Upper facial (side) row — Q1, Q2, AS, Gen.\n {\n id: 'Q1',\n kind: 'quadrant',\n arch: 'upper',\n dentition: 'permanent',\n labelKey: 'quadrant',\n labelIndex: 1,\n code: 'Q1',\n teeth: teethInQuadrant(1),\n column: 0,\n row: 'side',\n },\n {\n id: 'Q2',\n kind: 'quadrant',\n arch: 'upper',\n dentition: 'permanent',\n labelKey: 'quadrant',\n labelIndex: 2,\n code: 'Q2',\n teeth: teethInQuadrant(2),\n column: 1,\n row: 'side',\n },\n {\n id: 'AS',\n kind: 'arch',\n arch: 'upper',\n dentition: 'permanent',\n labelKey: 'archUpper',\n code: 'AS',\n teeth: [...teethInQuadrant(1), ...teethInQuadrant(2)],\n column: 2,\n row: 'side',\n },\n {\n id: 'general',\n kind: 'general',\n arch: 'upper',\n dentition: 'permanent',\n labelKey: 'general',\n code: 'Gen.',\n teeth: PERMANENT_TEETH,\n column: 3,\n row: 'side',\n },\n // Upper occlusal (plan) row — S1, S2, S3 (WHO sextants).\n {\n id: 'S1',\n kind: 'sextant',\n arch: 'upper',\n dentition: 'permanent',\n labelKey: 'sextant',\n labelIndex: 1,\n code: 'S1',\n teeth: ['18', '17', '16', '15', '14'],\n column: 0,\n row: 'plan',\n },\n {\n id: 'S2',\n kind: 'sextant',\n arch: 'upper',\n dentition: 'permanent',\n labelKey: 'sextant',\n labelIndex: 2,\n code: 'S2',\n teeth: ['13', '12', '11', '21', '22', '23'],\n column: 1,\n row: 'plan',\n },\n {\n id: 'S3',\n kind: 'sextant',\n arch: 'upper',\n dentition: 'permanent',\n labelKey: 'sextant',\n labelIndex: 3,\n code: 'S3',\n teeth: ['24', '25', '26', '27', '28'],\n column: 2,\n row: 'plan',\n },\n // Lower occlusal (plan) row — S4, S5, S6.\n {\n id: 'S4',\n kind: 'sextant',\n arch: 'lower',\n dentition: 'permanent',\n labelKey: 'sextant',\n labelIndex: 4,\n code: 'S4',\n teeth: ['34', '35', '36', '37', '38'],\n column: 0,\n row: 'plan',\n },\n {\n id: 'S5',\n kind: 'sextant',\n arch: 'lower',\n dentition: 'permanent',\n labelKey: 'sextant',\n labelIndex: 5,\n code: 'S5',\n teeth: ['43', '42', '41', '31', '32', '33'],\n column: 1,\n row: 'plan',\n },\n {\n id: 'S6',\n kind: 'sextant',\n arch: 'lower',\n dentition: 'permanent',\n labelKey: 'sextant',\n labelIndex: 6,\n code: 'S6',\n teeth: ['44', '45', '46', '47', '48'],\n column: 2,\n row: 'plan',\n },\n // Lower facial (side) row — Q4, Q3, AI.\n {\n id: 'Q4',\n kind: 'quadrant',\n arch: 'lower',\n dentition: 'permanent',\n labelKey: 'quadrant',\n labelIndex: 4,\n code: 'Q4',\n teeth: teethInQuadrant(4),\n column: 0,\n row: 'side',\n },\n {\n id: 'Q3',\n kind: 'quadrant',\n arch: 'lower',\n dentition: 'permanent',\n labelKey: 'quadrant',\n labelIndex: 3,\n code: 'Q3',\n teeth: teethInQuadrant(3),\n column: 1,\n row: 'side',\n },\n {\n id: 'AI',\n kind: 'arch',\n arch: 'lower',\n dentition: 'permanent',\n labelKey: 'archLower',\n code: 'AI',\n teeth: [...teethInQuadrant(3), ...teethInQuadrant(4)],\n column: 2,\n row: 'side',\n },\n];\n\nconst PRIMARY_SPECIALS: SpecialElement[] = [\n // Upper facial (side) row.\n {\n id: 'mQ1',\n kind: 'quadrant',\n arch: 'upper',\n dentition: 'primary',\n labelKey: 'quadrant',\n labelIndex: 1,\n code: 'Q1',\n teeth: teethInQuadrant(5),\n column: 0,\n row: 'side',\n },\n {\n id: 'mQ2',\n kind: 'quadrant',\n arch: 'upper',\n dentition: 'primary',\n labelKey: 'quadrant',\n labelIndex: 2,\n code: 'Q2',\n teeth: teethInQuadrant(6),\n column: 1,\n row: 'side',\n },\n {\n id: 'mAS',\n kind: 'arch',\n arch: 'upper',\n dentition: 'primary',\n labelKey: 'archUpper',\n code: 'AS',\n teeth: [...teethInQuadrant(5), ...teethInQuadrant(6)],\n column: 2,\n row: 'side',\n },\n {\n id: 'mGeneral',\n kind: 'general',\n arch: 'upper',\n dentition: 'primary',\n labelKey: 'general',\n code: 'Gen.',\n teeth: PRIMARY_TEETH,\n column: 3,\n row: 'side',\n },\n // Upper occlusal (plan) row.\n {\n id: 'mS1',\n kind: 'sextant',\n arch: 'upper',\n dentition: 'primary',\n labelKey: 'sextant',\n labelIndex: 1,\n code: 'S1',\n teeth: ['55', '54'],\n column: 0,\n row: 'plan',\n },\n {\n id: 'mS2',\n kind: 'sextant',\n arch: 'upper',\n dentition: 'primary',\n labelKey: 'sextant',\n labelIndex: 2,\n code: 'S2',\n teeth: ['53', '52', '51', '61', '62', '63'],\n column: 1,\n row: 'plan',\n },\n {\n id: 'mS3',\n kind: 'sextant',\n arch: 'upper',\n dentition: 'primary',\n labelKey: 'sextant',\n labelIndex: 3,\n code: 'S3',\n teeth: ['64', '65'],\n column: 2,\n row: 'plan',\n },\n // Lower occlusal (plan) row.\n {\n id: 'mS4',\n kind: 'sextant',\n arch: 'lower',\n dentition: 'primary',\n labelKey: 'sextant',\n labelIndex: 4,\n code: 'S4',\n teeth: ['74', '75'],\n column: 0,\n row: 'plan',\n },\n {\n id: 'mS5',\n kind: 'sextant',\n arch: 'lower',\n dentition: 'primary',\n labelKey: 'sextant',\n labelIndex: 5,\n code: 'S5',\n teeth: ['73', '72', '71', '81', '82', '83'],\n column: 1,\n row: 'plan',\n },\n {\n id: 'mS6',\n kind: 'sextant',\n arch: 'lower',\n dentition: 'primary',\n labelKey: 'sextant',\n labelIndex: 6,\n code: 'S6',\n teeth: ['84', '85'],\n column: 2,\n row: 'plan',\n },\n // Lower facial (side) row.\n {\n id: 'mQ4',\n kind: 'quadrant',\n arch: 'lower',\n dentition: 'primary',\n labelKey: 'quadrant',\n labelIndex: 4,\n code: 'Q4',\n teeth: teethInQuadrant(8),\n column: 0,\n row: 'side',\n },\n {\n id: 'mQ3',\n kind: 'quadrant',\n arch: 'lower',\n dentition: 'primary',\n labelKey: 'quadrant',\n labelIndex: 3,\n code: 'Q3',\n teeth: teethInQuadrant(7),\n column: 1,\n row: 'side',\n },\n {\n id: 'mAI',\n kind: 'arch',\n arch: 'lower',\n dentition: 'primary',\n labelKey: 'archLower',\n code: 'AI',\n teeth: [...teethInQuadrant(7), ...teethInQuadrant(8)],\n column: 2,\n row: 'side',\n },\n];\n\n/** Every special element, flat — permanent then primary. */\nexport const SPECIAL_ELEMENTS: SpecialElement[] = [\n ...PERMANENT_SPECIALS,\n ...PRIMARY_SPECIALS,\n];\n\n/** Lookup a special element by its id (`Q1`, `mS3`, `AS`, `general`…). */\nexport const SPECIAL_BY_ID: Record<string, SpecialElement> =\n SPECIAL_ELEMENTS.reduce<Record<string, SpecialElement>>((acc, el) => {\n acc[el.id] = el;\n return acc;\n }, {});\n\n/** True when an id names a special element rather than a tooth. */\nexport function isSpecialElement(id: string): boolean {\n return id in SPECIAL_BY_ID;\n}\n\n/**\n * The special-element SLOTS that attach to a given row, ordered by column.\n * Quadrant / arch live on the `side` row; sextants on the `plan` row. The\n * whole-mouth `general` element is excluded — it renders as a switch beneath the\n * chart (see `generalElement`), not as a column. Mixed dentition reuses the\n * permanent set (the platform has no mixed view).\n */\nexport function specialsForRow(\n dentition: Dentition,\n arch: Arch,\n projection: Projection,\n): SpecialElement[] {\n const set = dentition === 'primary' ? PRIMARY_SPECIALS : PERMANENT_SPECIALS;\n return set\n .filter(\n (el) =>\n el.arch === arch && el.row === projection && el.kind !== 'general',\n )\n .sort((a, b) => a.column - b.column);\n}\n\n/**\n * The whole-mouth `general` element for a dentition. It is rendered as a switch\n * beneath the chart rather than as a cramped icon column (the platform's vertical\n * \"GENERALE\" glyph is illegible at column width).\n */\nexport function generalElement(dentition: Dentition): SpecialElement {\n const set = dentition === 'primary' ? PRIMARY_SPECIALS : PERMANENT_SPECIALS;\n // Every dentition set defines exactly one general element.\n return set.find((el) => el.kind === 'general') as SpecialElement;\n}\n\n/** A special-element slot placed in an SVG, to the inline-end of the teeth. */\nexport interface PositionedSpecial {\n element: SpecialElement;\n x: number;\n y: number;\n}\n\n/** Special-element slot block-size — matches a tooth slot so icons read clearly. */\nexport const SPECIAL_SLOT_WIDTH = 36;\n\n/**\n * Resolve the photoreal icon URL for a special element, or `null` when it has\n * none (sextants are label-only — the platform ships no sextant icon). Icons\n * live in the `global/` sibling of the `teeth/` render folder; quadrants and\n * arches reuse their code as the file stem (`Q1`, `AS`, …), general resolves to\n * `general`, and primary elements reuse the permanent art.\n */\nexport function specialIconUrl(\n element: SpecialElement,\n assetBaseUrl: string,\n): string | null {\n const stem =\n element.kind === 'quadrant' || element.kind === 'arch'\n ? element.code\n : element.kind === 'general'\n ? 'general'\n : null;\n if (!stem) return null;\n // Icons sit in the `global/` sibling of the `teeth/` render folder; tolerate a\n // trailing slash on the configured base.\n const base = /\\/teeth\\/?$/.test(assetBaseUrl)\n ? assetBaseUrl.replace(/\\/teeth\\/?$/, '/global')\n : `${assetBaseUrl}/global`;\n return `${base}/${stem}.webp`;\n}\n/** Gap between the last tooth and the first special-element column. */\nexport const SPECIAL_GAP = 8;\n/** Column pitch within the special-element block. */\nexport const SPECIAL_PITCH = SPECIAL_SLOT_WIDTH + TOOTH_GAP;\n\n/**\n * Place a row's special elements after the teeth (inline-end) and report the\n * total row width. Columns are derived from `element.column`, so a quadrant and\n * its paired sextant share an x and stack across the two rows.\n */\nexport function layoutSpecials(\n specials: SpecialElement[],\n teethWidth: number,\n): { specials: PositionedSpecial[]; width: number } {\n if (specials.length === 0) return { specials: [], width: teethWidth };\n const startX = teethWidth + SPECIAL_GAP;\n const positioned = specials.map((element) => ({\n element,\n x: startX + element.column * SPECIAL_PITCH,\n y: 0,\n }));\n const maxColumn = Math.max(...specials.map((el) => el.column));\n const width = startX + (maxColumn + 1) * SPECIAL_PITCH - TOOTH_GAP;\n return { specials: positioned, width };\n}\n\n/* ------------------------------------------------------------------ */\n/* Labels by numbering */\n/* ------------------------------------------------------------------ */\n\nexport function labelFor(id: FdiId, numbering: Numbering): string {\n switch (numbering) {\n case 'universal':\n return FDI_TO_UNIVERSAL[id] ?? id;\n case 'palmer':\n return FDI_TO_PALMER[id] ?? id;\n case 'fdi':\n default:\n return id;\n }\n}\n\n/* ------------------------------------------------------------------ */\n/* Chart helpers */\n/* ------------------------------------------------------------------ */\n\nexport function emptyChart(): ToothChart {\n return {};\n}\n\nexport function chartFromConditions(\n conditions: Partial<Record<FdiId, ToothCondition[]>>,\n): ToothChart {\n const chart: ToothChart = {};\n for (const [id, list] of Object.entries(conditions)) {\n if (!list || list.length === 0) continue;\n chart[id] = { conditions: list, surfaces: [] };\n }\n return chart;\n}\n\n/**\n * Build a chart from the richer per-symbol shape. Each entry also projects its\n * built-in conditions and surfaces back onto the legacy pair so readers that\n * predate `symbols` (the first-condition `data-condition`, the periodontal\n * chart, the care-plan card) still see the tooth. Registry ids are omitted from\n * that projection — a legacy reader does not know the custom vocabulary.\n */\nexport function chartFromSymbols(\n symbols: Partial<Record<FdiId, ToothSymbol[]>>,\n): ToothChart {\n const chart: ToothChart = {};\n for (const [id, list] of Object.entries(symbols)) {\n if (!list || list.length === 0) continue;\n const conditions: ToothCondition[] = [];\n const surfaces: Surface[] = [];\n for (const symbol of list) {\n if (\n isBuiltInCondition(symbol.condition) &&\n !conditions.includes(symbol.condition)\n )\n conditions.push(symbol.condition);\n for (const surface of symbol.surfaces ?? [])\n if (!surfaces.includes(surface)) surfaces.push(surface);\n }\n chart[id] = { conditions, surfaces, symbols: list };\n }\n return chart;\n}\n"],"names":["PERMANENT_TEETH","PRIMARY_TEETH","permanentAnatomy","positionInQuadrant","primaryAnatomy","palmerCode","quadrant","position","normalised","permanentUniversal","primaryUniversal","letters","index","buildMeta","fdi","isPrimary","anatomy","arch","side","universal","palmer","FDI_TO_META","acc","FDI_TO_UNIVERSAL","meta","FDI_TO_PALMER","assetFdi","PRIMARY_TOOTH_SCALE","rootsForTooth","rootScreenSide","root","q","mesialIsRight","rootFileStem","CONDITION_TOKENS","CONDITION_COLORS","condition","token","TOOTH_CONDITIONS","isBuiltInCondition","id","CONDITION_PART","INTRINSIC_CONDITIONS","INTRINSIC","STATUS_TOKENS","STATUS_COLORS","STATUS_OPACITY","statusLabelKey","status","CONDITION_SIDE_SYMBOL","markerSide","target","fallback","symbolUrl","projection","assetBaseUrl","spec","view","base","stem","roots","conditionSymbolUrl","SURFACE_ASSET_NAME","CONDITION_PLAN_FOLDER","surfaceSymbolUrl","folder","surface","CONDITION_PLAN_SYMBOL","planSymbolUrl","PLAN_CANVAS","PLAN_CANVAS_OVERRIDES","planOverlayScale","url","key","canvas","FALLBACK_CONDITION_TOKEN","conditionLabelKey","conditionDefinition","registry","entry","conditionLabel","custom","conditionColor","symbolColor","symbol","conditionSideSymbol","_a","conditionPlanSymbol","conditionPlanFolder","conditionPart","initialsFrom","words","word","conditionGlyph","glyph","conditionHasArtwork","symbolsForTooth","state","part","zonesForTooth","zoneSurface","zone","leftIsDistal","r2","n","zonePath","rect","hasCenter","x","y","w","h","x1","y1","p","px","py","cx","cy","ix","iy","ix1","iy1","TOOTH_GAP","TOOTH_ROW_GAP","TOOTH_HIT_PAD","TOOTH_SLOT_WIDTH","TOOTH_SLOT_HEIGHT","toothImageUrl","layoutTeeth","dentition","slotHeight","options","ids","upper","lower","slot","maxCount","maxRowWidth","positionRow","row","yOffset","rowWidth","startX","i","upperRow","lowerRow","width","height","teethInQuadrant","PERMANENT_SPECIALS","PRIMARY_SPECIALS","SPECIAL_ELEMENTS","SPECIAL_BY_ID","el","isSpecialElement","specialsForRow","a","b","generalElement","SPECIAL_SLOT_WIDTH","specialIconUrl","element","SPECIAL_GAP","SPECIAL_PITCH","layoutSpecials","specials","teethWidth","positioned","maxColumn","labelFor","numbering","emptyChart","chartFromConditions","conditions","chart","list","chartFromSymbols","symbols","surfaces"],"mappings":"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"}
|