← Files LegalQuants LitigationARCHIVED FILE

skills/legaldesign/assets/method-map.html

437 KB · Oct 2, 2026 · 00:33 UTC

↓ Download file

<!doctype html>
<html lang="en" data-theme="light" data-template-id="legaldesign-method-template" data-document-title-placeholder="[Matter] · LegalDesign template"><head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>How LegalDesign works</title>
<script id="legaldesign-state" type="application/json">{"schema":"legaldesign.state.v1","artifactId":"legaldesign-method-map","savedAt":"2026-09-04T00:00:00.000Z","brief":{"reader":"A lawyer opening the plugin for the first time","action":"Run /legaldesign on their own memo","purpose":"understand","message":"The skill identifies the audience and core relationship, chooses a representation, and encodes it with fixed design components. It produces two complete approaches and client or template exports.","spine":"sequence","situation":"laptop","form":"one-page","sources":[{"id":"skill","label":"Canonical skill instructions","status":"supplied"},{"id":"method","label":"Canonical design method","status":"supplied"},{"id":"design","label":"Canonical palette authority","status":"supplied"}],"assumptions":["The reader has installed the plugin and has a memo to hand."],"gaps":[],"run":{"questions_asked":[],"note":"This is a maintained component reference, not a record of user answers. Its audience and purpose are supplied by the evaluation brief."},"title":"How LegalDesign works"},"style":{"source":"loxoto","ref":null},"units":{"u-title":{"kind":"text","role":"title","selected":"a","claim":"LegalDesign uses audience-led design reasoning and a fixed component system.","relationship":"sequence","candidates":["descriptive text"],"variants":{"a":{"axis":"framing","html":"How LegalDesign works","why":"A concise explanation of the current rule; this legacy component variant is not a separate whole-page architecture."},"b":{"axis":"framing","html":"Designing a legal explanation","why":"A concise explanation of the current rule; this legacy component variant is not a separate whole-page architecture."}},"edits":{"a":null,"b":null},"placeholder":"[Describe the subject, rule or next action.]"},"u-answer":{"kind":"text","role":"answer","selected":"a","claim":"The skill identifies the audience and core relationship, chooses a representation, and encodes it with fixed design components. It produces two complete approaches and client or template exports.","relationship":"sequence","candidates":["descriptive text"],"variants":{"a":{"axis":"framing","html":"The skill uses the audience and source material to choose a form and encode its meaning. It builds two complete approaches with the same design components, then checks the result.","why":"A concise explanation of the current rule; this legacy component variant is not a separate whole-page architecture."},"b":{"axis":"framing","html":"Compare two complete approaches, edit the one you prefer, and export it for the reader or as a reusable template. The source material controls the meaning.","why":"A concise explanation of the current rule; this legacy component variant is not a separate whole-page architecture."}},"edits":{"a":null,"b":null},"placeholder":"[Describe the subject, rule or next action.]"},"u-figure":{"kind":"figure","selected":"a","claim":"Three design decisions","relationship":"sequence","candidates":["flow","numbered rows"],"variants":{"a":{"axis":"framing","component":"flow","params":{"stages":[{"title":"Audience + relationship","sub":"Reader, purpose and facts","detail":"src-relationship"},{"title":"Representation","sub":"Diagram, table or text","detail":"src-form"},{"title":"Visual encoding","sub":"Position, order and scale","detail":"src-brief"}],"edges":["guides the choice","defines the mapping"]},"why":"The three connected decisions show how meaning determines representation and then geometry."},"b":{"axis":"framing","component":"flow","params":{"stages":[{"title":"Understand the task","sub":"What must this reader know?","detail":"src-relationship"},{"title":"Choose the form","sub":"Which form makes it clear?","detail":"src-form"},{"title":"Show the relationship","sub":"What does the geometry mean?","detail":"src-brief"}],"edges":["choose for that task","encode its meaning"]},"why":"The same design reasoning expressed as direct questions; retained as a legacy component alternative, not a second page architecture."}},"edits":{},"placeholder":"[Name each design decision and what it determines.]","source_note":"Design method: audience, form and encoding."},"u-card-questions":{"kind":"card","selected":"a","claim":"Intake asks at most three unanswered questions about the audience, purpose, sources or palette.","relationship":"priority","candidates":["concise component card","direct explanation"],"variants":{"a":{"axis":"framing","html":"\u003cp>Up to three unanswered questions clarify the reader, purpose, sources or first-use colors. It uses structured choices when available, otherwise concise chat.\u003c/p>","why":"A concise explanation of the current rule; this legacy component variant is not a separate whole-page architecture.","title":"Intake"},"b":{"axis":"framing","html":"\u003cp>The record answers what it can first. You can answer, skip, or ask the agent to proceed with stated assumptions.\u003c/p>","why":"A concise explanation of the current rule; this legacy component variant is not a separate whole-page architecture.","title":"Reader and purpose"}},"edits":{"a":null,"b":null},"placeholder":"[Describe the subject, rule or next action.]","placeholder_title":"[Name the topic.]"},"u-card-form":{"kind":"card","selected":"a","claim":"Representation follows the audience and relationship, using fixed design components rather than a template layout.","relationship":"hierarchy","candidates":["concise component card","direct explanation"],"variants":{"a":{"axis":"framing","html":"\u003cp>Compare plausible forms, including text or a table. Choose the one that expresses the relationship, then specify what position, order, scale or enclosure means.\u003c/p>","why":"A concise explanation of the current rule; this legacy component variant is not a separate whole-page architecture.","title":"Design decisions"},"b":{"axis":"framing","html":"\u003cp>Reuse the existing typography, cards, diagrams and popups. Templates show those components; they do not dictate the page layout.\u003c/p>","why":"A concise explanation of the current rule; this legacy component variant is not a separate whole-page architecture.","title":"Component selection"}},"edits":{"a":null,"b":null},"placeholder":"[Describe the subject, rule or next action.]","placeholder_title":"[Name the topic.]"},"u-card-ab":{"kind":"card","selected":"a","claim":"New outputs contain two complete approaches, not separate A/B choices on every element.","relationship":"comparison","candidates":["concise component card","direct explanation"],"variants":{"a":{"axis":"framing","html":"\u003cp>A and B organize the same material differently. One global selector switches the complete approach; individual cards and figures have no A/B controls.\u003c/p>","why":"A concise explanation of the current rule; this legacy component variant is not a separate whole-page architecture.","title":"Two complete approaches"},"b":{"axis":"framing","html":"\u003cp>Compare different information structures or reading paths. Changing color or rewording the same cards does not create a second approach.\u003c/p>","why":"A concise explanation of the current rule; this legacy component variant is not a separate whole-page architecture.","title":"Compare page structures"}},"edits":{"a":null,"b":null},"placeholder":"[Describe the subject, rule or next action.]","placeholder_title":"[Name the topic.]"},"u-card-exports":{"kind":"card","selected":"a","claim":"Client export keeps the selected approach; template export retains both structures with descriptive placeholders.","relationship":"comparison","candidates":["concise component card","direct explanation"],"variants":{"a":{"axis":"framing","html":"\u003cp>Export HTML keeps the selected approach, edits and intended interactions. Export template keeps both structures with descriptive placeholders instead of matter content.\u003c/p>","why":"A concise explanation of the current rule; this legacy component variant is not a separate whole-page architecture.","title":"Export options"},"b":{"axis":"framing","html":"\u003cp>Use HTML for delivery. Use a template to reuse the design without private names, source images, URLs or saved responses.\u003c/p>","why":"A concise explanation of the current rule; this legacy component variant is not a separate whole-page architecture.","title":"Deliver or reuse"}},"edits":{"a":null,"b":null},"placeholder":"[Describe the subject, rule or next action.]","placeholder_title":"[Name the topic.]"},"u-next":{"kind":"text","role":"action","selected":"a","claim":"Start with completed work and its audience; first-use firm branding changes colors only.","relationship":"sequence","candidates":["descriptive text"],"variants":{"a":{"axis":"framing","html":"Attach the completed work and name its audience. The output is one responsive page or a slide sequence, with explanations in popups and links to original sources.","why":"A concise explanation of the current rule; this legacy component variant is not a separate whole-page architecture."},"b":{"axis":"framing","html":"Choose the default colors or your firm’s palette once. Accessible color mapping is saved in DESIGN.md; typography, geometry and component behavior stay fixed.","why":"A concise explanation of the current rule; this legacy component variant is not a separate whole-page architecture."}},"edits":{"a":null,"b":null},"placeholder":"[Describe the subject, rule or next action.]"},"u-scope":{"kind":"text","role":"scope","selected":"a","claim":"This component reference describes the current skill using its canonical instructions.","relationship":"containment","candidates":["descriptive text"],"variants":{"a":{"axis":"framing","html":"This maintained component reference describes the current skill. New artifacts use two complete approaches; the saved variants here preserve legacy reference compatibility.","why":"A concise explanation of the current rule; this legacy component variant is not a separate whole-page architecture."}},"edits":{"a":null},"placeholder":"[Describe the subject, rule or next action.]"}},"evidence":{"src-questions":{"cite":"references/method.md · Intake: at most three questions","locator":"references/method.md · Intake: at most three questions","excerpt":"Ask only unresolved questions, together, using an exposed and permitted structured user-input tool where available.","link":null,"image":{"status":"unavailable"},"status":"supplied","provenance":"Canonical skill text, read 4 September 2026. No public original link is supplied.","popup":{"type":"source","title":"Questions only where information is missing","lede":"The skill reads the context first, then asks only what it needs to understand the audience, task or source material.","sections":[{"heading":"Current rule","body":"Ask only unresolved questions, together, using an exposed and permitted structured user-input tool where available."}]}},"src-brief":{"cite":"references/method.md · How does the geometry encode it?","locator":"references/method.md · How does the geometry encode it?","excerpt":"Specify the technical mapping: which order expresses time or dependency, which enclosure means membership, which connection means responsibility, which alignment permits comparison, or which axis/length encodes a measured value.","link":null,"image":{"status":"unavailable"},"status":"supplied","provenance":"Canonical skill text, read 4 September 2026. No public original link is supplied.","popup":{"type":"source","title":"What the geometry means","lede":"Position and connectors must carry a specific meaning. A decorative outline or arrow does not explain a relationship.","sections":[{"heading":"Current rule","body":"Specify the technical mapping: which order expresses time or dependency, which enclosure means membership, which connection means responsibility, which alignment permits comparison, or which axis/length encodes a measured value."}]}},"src-relationship":{"cite":"references/method.md · What must this reader understand?","locator":"references/method.md · What must this reader understand?","excerpt":"Identify which facts establish it and why it matters to this audience's action.","link":null,"image":{"status":"unavailable"},"status":"supplied","provenance":"Canonical skill text, read 4 September 2026. No public original link is supplied.","popup":{"type":"source","title":"Audience and the core relationship","lede":"Identify what the reader must understand or do, and which relationship in the actual material supports that task.","sections":[{"heading":"Current rule","body":"Identify which facts establish it and why it matters to this audience's action."}]}},"src-form":{"cite":"references/method.md · Which representation communicates it best?","locator":"references/method.md · Which representation communicates it best?","excerpt":"Consider two or three plausible forms, including a plain sentence, row, or table.","link":null,"image":{"status":"unavailable"},"status":"supplied","provenance":"Canonical skill text, read 4 September 2026. No public original link is supplied.","popup":{"type":"source","title":"Choosing a representation","lede":"A diagram is not automatic. Compare plausible forms and use the one that communicates the supported relationship most directly.","sections":[{"heading":"Current rule","body":"Consider two or three plausible forms, including a plain sentence, row, or table."}]}},"src-treatments":{"cite":"SKILL.md · Deliverable contract","locator":"SKILL.md · Deliverable contract","excerpt":"Each approach has its own complete structure and independent edits; there are no per-element A/B selectors.","link":null,"image":{"status":"unavailable"},"status":"supplied","provenance":"Canonical skill text, read 4 September 2026. No public original link is supplied.","popup":{"type":"source","title":"Two complete approaches","lede":"New artifacts compare full information structures with one global selector. The same material and legal meaning are preserved in both.","sections":[{"heading":"Current rule","body":"Each approach has its own complete structure and independent edits; there are no per-element A/B selectors."}]}},"src-exports":{"cite":"SKILL.md · Deliverable contract","locator":"SKILL.md · Deliverable contract","excerpt":"Export HTML retains only the selected approach and intended reader interactions, without authoring chrome or rationales.","link":null,"image":{"status":"unavailable"},"status":"supplied","provenance":"Canonical skill text, read 4 September 2026. No public original link is supplied.","popup":{"type":"source","title":"Client and template exports","lede":"The client file retains the selected approach. The reusable template retains both structures and descriptive placeholders without private matter.","sections":[{"heading":"Current rule","body":"Export HTML retains only the selected approach and intended reader interactions, without authoring chrome or rationales."}]}}},"review":{"decisions":{},"theme":"light","location":null},"history":[]}</script>
<!-- legaldesign:runtime -->
<style id="ld-tokens">
:root,
[data-theme="light"] {
  color-scheme: light;
  --bg: #fbfbfb;
  --card: #ffffff;
  --card-strong: #ffffff;
  --ink: #111111;
  --muted: #565656;
  --faint: #6f6f6f;
  --line: rgba(17, 17, 17, 0.13);
  --line-strong: rgba(17, 17, 17, 0.3);
  --red: #c92014;
  --red-strong: #a01a10;
  --red-wash: rgba(201, 32, 20, 0.055);
  --insert: #2456d6;
  --interaction: #767676;
  --detail-feedback: var(--interaction);
  --tint: #f3f3f3;
  --scrim: rgba(17, 17, 17, 0.52);
  --shadow-soft:
    0 1px 3px rgba(17, 17, 17, 0.08), 0 6px 18px rgba(17, 17, 17, 0.05);
  --shadow: 0 22px 60px rgba(17, 17, 17, 0.18);
  --sans: "Helvetica Neue", Helvetica, Arial, sans-serif;
  --serif: Charter, "Iowan Old Style", Georgia, serif;
  --label: var(--sans);
  /* One authored reading scale. Window resizing changes layout, not type. */
  --ld-type-title: 28px;
  --ld-type-section: 20px;
  --ld-type-body: 16px;
  --ld-type-caption: 12px;
  --ld-space: 8px;
  --ld-content-gap: 16px;
  --ld-content-padding: 24px;
}

[data-theme="dark"] {
  color-scheme: dark;
  --bg: #000000;
  --card: #111111;
  --card-strong: #171717;
  --ink: #f2f2f0;
  --muted: #b4b4b0;
  --faint: #8d8d89;
  --line: rgba(242, 242, 240, 0.14);
  --line-strong: rgba(242, 242, 240, 0.32);
  --red: #ff5a4e;
  --red-strong: #ff7d73;
  --red-wash: rgba(255, 90, 78, 0.12);
  --insert: #6fa0ff;
  --interaction: #a0a09c;
  --tint: #161616;
  --scrim: rgba(0, 0, 0, 0.68);
  --shadow-soft: 0 1px 3px rgba(0, 0, 0, 0.5), 0 6px 18px rgba(0, 0, 0, 0.35);
  --shadow: 0 22px 60px rgba(0, 0, 0, 0.62);
}

* {
  box-sizing: border-box;
}
html,
body {
  background: var(--bg);
}
html {
  min-height: 100%;
  color: var(--ink);
  font-family: var(--sans);
}
body {
  min-height: 100%;
  margin: 0;
  color: var(--ink);
  font-family: var(--sans);
  -webkit-font-smoothing: antialiased;
}
button,
input,
textarea,
select {
  color: inherit;
  font: inherit;
}
button,
select {
  cursor: pointer;
}
button:disabled,
select:disabled {
  cursor: default;
  opacity: 0.42;
}
a {
  color: var(--red);
  text-decoration: underline;
  text-underline-offset: 0.12em;
}
a:hover {
  text-decoration-thickness: 2px;
}
html :focus-visible,
html #ld-page-approach button:focus-visible {
  outline: 2px solid var(--interaction);
  outline-offset: 2px;
}
[data-detail]:focus-visible,
[data-evidence]:focus-visible {
  outline: 2px solid var(--detail-feedback);
  outline-offset: 2px;
}
svg [data-detail]:focus-visible {
  outline: none;
}
/* Text-led chart rows and comparison headers have no leading card to stroke.
   Their keyboard focus belongs to the single group, never to each child. */
html:not([data-input-modality="pointer"])
  svg
  [data-detail]:focus-visible:not(
    :has(> :is(rect, path, circle, ellipse, polygon, polyline):first-child)
  ) {
  outline: 2px solid var(--detail-feedback);
  outline-offset: 3px;
}
html:not([data-input-modality="pointer"])
  svg
  [data-detail]:focus-visible
  > :is(rect, path, circle, ellipse, polygon, polyline):first-child {
  stroke: var(--detail-feedback);
  stroke-width: 2px;
}
.ld-control:focus:not(:focus-visible),
.ld-tool:focus:not(:focus-visible),
.ld-ab button:focus:not(:focus-visible),
.ld-why-toggle:focus:not(:focus-visible),
.ld-popup-close:focus:not(:focus-visible) {
  outline: none;
}
[hidden] {
  display: none !important;
}
.doc-text {
  font-family: var(--serif);
}

.ld-bar {
  position: sticky;
  top: 0;
  z-index: 40;
  display: flex;
  min-height: 44px;
  align-items: center;
  gap: 10px;
  padding: 5px 10px;
  border-bottom: 1px solid var(--line);
  background: var(--card);
}
.ld-matter {
  min-width: 0;
  flex: 1 1 auto;
  margin: 0;
  overflow: hidden;
  color: var(--muted);
  font-size: 12px;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ld-controls,
.ld-editor-tools {
  display: flex;
  align-items: center;
  gap: 2px;
}
.ld-controls {
  max-width: calc(100vw - 220px);
  overflow-x: auto;
  border: 1px solid var(--line-strong);
  background: var(--card);
  padding: 2px;
  scrollbar-width: none;
}
.ld-controls::-webkit-scrollbar {
  display: none;
}
.ld-control,
.ld-ab button,
.ld-why-toggle,
.ld-popup-close {
  min-height: 27px;
  border: 0;
  border-radius: 2px;
  background: transparent;
  padding: 5px 9px;
  font-size: 11.5px;
  font-weight: 600;
  line-height: 1;
  white-space: nowrap;
}
.ld-tool,
.ld-palette {
  min-width: 27px;
  min-height: 27px;
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  background: var(--card);
  padding: 0 8px;
  color: var(--ink);
  font-size: 10px;
  font-weight: 600;
  line-height: 1;
  white-space: nowrap;
}
.ld-control:hover,
.ld-tool:hover,
.ld-ab button:hover,
.ld-why-toggle:hover {
  background: var(--tint);
  color: var(--ink);
}
.ld-control[aria-pressed="true"],
.ld-ab button[aria-pressed="true"] {
  background: var(--tint);
  color: var(--ink);
}
#theme-toggle {
  display: inline-grid;
  width: 30px;
  min-width: 30px;
  place-items: center;
  padding: 0;
}
.ld-theme-icon {
  width: 15px;
  height: 15px;
}
.ld-theme-sun {
  display: none;
}
html[data-theme="dark"] .ld-theme-moon {
  display: none;
}
html[data-theme="dark"] .ld-theme-sun {
  display: block;
}
.ld-tool[aria-pressed="true"] {
  border-color: var(--interaction);
  color: var(--interaction);
}
.ld-editor-tools {
  display: none;
}
html[data-mode="edit"] .ld-editor-tools {
  display: flex;
}
.ld-overflow-wrap {
  position: relative;
  display: contents;
}
#editor-overflow-toggle {
  display: none;
}
.ld-overflow-menu {
  display: contents;
}
.ld-palette {
  max-width: 88px;
  appearance: auto;
}
.ld-form-nav {
  position: sticky;
  top: 56px;
  z-index: 30;
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 24px;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: var(--card);
  padding: 8px;
}
.ld-form-nav [data-walkthrough-status] {
  min-width: 64px;
  flex: 1 1 auto;
  color: var(--muted);
  font-size: 12px;
  text-align: center;
}
.ld-form-nav [data-report-section] {
  overflow: hidden;
  text-overflow: ellipsis;
}
.ld-form-nav [aria-current="true"] {
  border-color: var(--interaction);
}

.ld-unit {
  position: relative;
}
.ld-unit-tools {
  position: absolute;
  top: 0;
  right: 0;
  z-index: 70;
  display: none;
  width: max-content;
  align-items: center;
  gap: 3px;
  margin: 0;
  transform: translateY(calc(-100% - 4px));
  box-shadow: none;
}
html[data-mode="edit"]
  .ld-unit:is(:hover, :focus-within, .ld-selected)
  > .ld-unit-tools,
html[data-mode="edit"] .ld-unit:has(.ld-selected) > .ld-unit-tools {
  display: flex;
}
.ld-ab {
  display: inline-flex;
  align-items: center;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: var(--card);
  padding: 2px;
}
.ld-ab::before {
  content: "‹";
  padding: 0 3px;
  color: var(--faint);
}
.ld-ab::after {
  content: "›";
  padding: 0 3px;
  color: var(--faint);
}
.ld-ab button {
  min-width: 27px;
  min-height: 25px;
  border: 0;
  padding: 3px 6px;
  font-size: 10px;
}
.ld-ab button + button::before {
  content: "·";
  margin-right: 8px;
  color: var(--faint);
}
.ld-unit-tools .ld-why-toggle {
  min-height: 27px;
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  background: var(--card);
  padding: 3px 8px;
  font-size: 10px;
}
.ld-why {
  margin: 8px 0 0;
  border-top: 1px solid var(--line);
  padding: 9px 0 0;
  color: var(--muted);
  font-size: 12px;
  line-height: 1.45;
}
.ld-review,
.ld-sheet-open {
  display: none !important;
}

[data-editable][contenteditable="true"] {
  cursor: text;
}
#ld-editor-layer {
  position: fixed;
  inset: 0;
  z-index: 140;
  pointer-events: none;
}
#ld-selection-box {
  position: fixed;
  display: none;
  border: 1px solid var(--interaction);
  border-radius: 2px;
}
.ld-handle {
  position: absolute;
  width: 10px;
  height: 10px;
  border: 1px solid var(--interaction);
  border-radius: 2px;
  background: var(--card);
  pointer-events: auto;
}
/* Text labels can be shorter than a handle. Keep handles outside their ink
   bounds so selecting a label never blocks the second click used to edit it. */
#ld-selection-box[data-diagram-label-selection]
.ld-handle:is([data-handle="nw"], [data-handle="n"], [data-handle="ne"]) {
  top: -12px;
}
#ld-selection-box[data-diagram-label-selection]
  .ld-handle:is([data-handle="sw"], [data-handle="s"], [data-handle="se"]) {
  bottom: -12px;
}
#ld-selection-box[data-diagram-label-selection]
  .ld-handle:is([data-handle="nw"], [data-handle="w"], [data-handle="sw"]) {
  left: -12px;
}
#ld-selection-box[data-diagram-label-selection]
  .ld-handle:is([data-handle="ne"], [data-handle="e"], [data-handle="se"]) {
  right: -12px;
}
.ld-handle[data-handle="nw"] {
  left: -5px;
  top: -5px;
  cursor: nwse-resize;
}
.ld-handle[data-handle="n"] {
  left: calc(50% - 5px);
  top: -5px;
  cursor: ns-resize;
}
.ld-handle[data-handle="ne"] {
  right: -5px;
  top: -5px;
  cursor: nesw-resize;
}
.ld-handle[data-handle="e"] {
  right: -5px;
  top: calc(50% - 5px);
  cursor: ew-resize;
}
.ld-handle[data-handle="se"] {
  right: -5px;
  bottom: -5px;
  cursor: nwse-resize;
}
.ld-handle[data-handle="s"] {
  left: calc(50% - 5px);
  bottom: -5px;
  cursor: ns-resize;
}
.ld-handle[data-handle="sw"] {
  left: -5px;
  bottom: -5px;
  cursor: nesw-resize;
}
.ld-handle[data-handle="w"] {
  left: -5px;
  top: calc(50% - 5px);
  cursor: ew-resize;
}
#ld-marquee {
  position: fixed;
  display: none;
  border: 1px dashed var(--interaction);
  border-radius: 2px;
  background: color-mix(in srgb, var(--interaction) 7%, transparent);
}

[data-fill-token="card"] {
  --ld-object-fill: var(--card);
  background: var(--card) !important;
}
[data-fill-token="card-strong"] {
  --ld-object-fill: var(--card-strong);
  background: var(--card-strong) !important;
}
[data-fill-token="tint"] {
  --ld-object-fill: var(--tint);
  background: var(--tint) !important;
}
[data-fill-token="bg"] {
  --ld-object-fill: var(--bg);
  background: var(--bg) !important;
}
[data-fill-token="none"] {
  --ld-object-fill: none;
  background: transparent !important;
}
[data-text-token="ink"] {
  --ld-object-text: var(--ink);
  color: var(--ink) !important;
}
[data-text-token="muted"] {
  --ld-object-text: var(--muted);
  color: var(--muted) !important;
}
[data-text-token="faint"] {
  --ld-object-text: var(--faint);
  color: var(--faint) !important;
}
[data-text-token="red"] {
  --ld-object-text: var(--red);
  color: var(--red) !important;
}
[data-text-token="red-strong"] {
  --ld-object-text: var(--red-strong);
  color: var(--red-strong) !important;
}
/* Object paint follows the selected object in HTML and SVG alike. */
svg [data-fill-token] > :is(rect, circle, ellipse, polygon) {
  fill: var(--ld-object-fill) !important;
}
[data-text-token] :is(h1, h2, h3, h4, p, span, li),
[data-text-token]:is(text, tspan),
[data-text-token] :is(text, tspan) {
  color: var(--ld-object-text) !important;
  fill: var(--ld-object-text) !important;
}
html[data-mode="edit"] .ld-bar {
  z-index: 150;
}
html[data-mode="edit"] .ld-scrim {
  padding-top: 70px;
}
html[data-mode="edit"] .ld-scrim .pop {
  max-height: calc(100dvh - 92px);
}
html[data-mode="edit"] [data-diagram-object] {
  cursor: move;
}
.ld-added-text {
  min-height: 28px;
  padding: 6px;
  font-size: 18px;
  line-height: 1.4;
  color: var(--ink);
  overflow-wrap: anywhere;
}
#ld-color-palette {
  position: fixed;
  z-index: 200;
  width: 232px;
  padding: 16px;
  background: var(--card);
  color: var(--ink);
  border: 1px solid var(--line-strong);
  border-radius: 10px;
  box-shadow: var(--shadow);
}
#ld-color-palette strong {
  display: block;
  font-size: 13px;
  margin-bottom: 12px;
}
#ld-color-palette small {
  display: block;
  margin-top: 12px;
  font-size: 11px;
  color: var(--muted);
}
.ld-swatch-grid {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 8px;
}
.ld-color-swatch {
  width: 32px;
  height: 32px;
  border: 1px solid var(--line-strong);
  border-radius: 4px;
  padding: 0;
}
.ld-color-swatch:hover,
.ld-color-swatch[aria-pressed="true"] {
  outline: 2px solid var(--interaction);
  outline-offset: 2px;
}
.ld-popup-authoring {
  display: none;
}
html[data-mode="edit"] .ld-popup-authoring {
  display: flex;
  margin-top: 20px;
  gap: 8px;
}
/* Titles fill their own frame. The close button owns the top-right corner,
   not a permanent empty strip beside the whole popup's content. */
html .pop h2,
html .pop .ld-popup-lede {
  max-width: none;
  width: 100%;
  margin-right: 0;
  text-wrap: wrap;
}
html .pop h2 {
  padding-right: 32px;
}
html .ld-composed-unit[data-role="summary"] {
  max-width: none;
}
html .ld-composed-unit[data-role="title"] h1 {
  width: 100%;
  text-wrap: wrap;
}
html .sb-title {
  padding-right: 0;
}
html .sb-title h1,
html .sb-title .sb-sub,
html .ld-title h1,
html .subtitle {
  width: 100%;
  max-width: none;
  text-wrap: wrap;
}
html .ld-figure-title {
  max-width: none;
  margin-right: 0;
}
html[data-theme="dark"] .ld-composed-unit[data-kind="figure"] {
  background: var(--bg);
}
/* Dark document cards sit slightly above the pure-black page. Structure uses a neutral outline;
   semantic emphasis changes the outline, never the whole fill. */
html[data-theme="dark"]
:is(.ld-card, .sb-card, .ld-composed-unit[data-kind="card"]):not(
  [data-fill-token]
) {
  --ink: #ffffff;
  --muted: #d0d0d0;
  --faint: #b4b4b4;
  color: var(--ink);
  background: var(--card);
  border: 1.5px solid rgba(255, 255, 255, 0.58);
  box-shadow: none;
}
html[data-theme="dark"]
  :is(.ld-card, .sb-card, .ld-composed-unit[data-kind="card"]):not(
    [data-fill-token]
  ):is(.accent, .open, [data-accent-role]) {
  background: var(--card);
  border-color: var(--red);
}
html[data-theme="dark"]
  :is(.ld-card, .sb-card, .ld-composed-unit[data-kind="card"]):not(
    [data-fill-token]
  )
  :is(a, .cite, .tag, .action) {
  color: var(--red);
}
html[data-theme="dark"]
  :is(.ld-card, .sb-card, .ld-composed-unit[data-kind="card"]):not(
    [data-fill-token]
  ):is(.accent, .open, [data-accent-role])
  :is(a, .cite, .tag, .action) {
  color: var(--red);
}
html[data-theme="dark"] :is([data-kind="text"], [data-editor-object="text"]) {
  border: 0;
  box-shadow: none;
}
html[data-theme="dark"]
  .ld-composed-unit[data-kind="card"]:not([data-detail]):not(
    [data-accent-role]
  ):not(.accent):not(.open) {
  border: 0;
  box-shadow: none;
}
.ld-scrim {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: auto;
  padding: 22px;
  background: var(--scrim);
}
.pop {
  position: relative;
  width: min(720px, 100%);
  max-height: calc(100dvh - 44px);
  overflow: auto;
  border: 1px solid var(--line-strong);
  border-radius: 5px;
  background: var(--card);
  box-shadow: var(--shadow);
  padding: 25px;
}
.pop:has(.exhibit-shot) {
  width: min(880px, 100%);
}
.pop .ld-popup-section p {
  max-width: 72ch;
}
html[data-theme="dark"] .pop :is(.ld-popup-lede, .ld-popup-section p, .desc) {
  color: var(--ink);
}
.pop[data-scroll-more] {
  box-shadow:
    inset 0 -16px 12px -14px var(--ink),
    var(--shadow);
}
.ld-popup-close {
  position: absolute;
  top: 10px;
  right: 10px;
  width: 38px;
  padding: 0;
  font-size: 22px;
}
.pop .tag {
  margin: 0 48px 7px 0;
  color: var(--red);
  font-size: 11px;
  letter-spacing: 0.04em;
}
.pop h2 {
  margin: 0 48px 8px 0;
  font-size: 22px;
  line-height: 1.15;
}
.pop .desc {
  margin: 0 0 16px;
  color: var(--muted);
  font-size: 13px;
  line-height: 1.5;
}
.doc {
  overflow: hidden;
  border: 1px solid var(--line-strong);
  border-radius: 4px;
  background: var(--card-strong);
}
.doc-text {
  padding: 16px 18px;
  font-size: 14px;
  line-height: 1.6;
}
.doc blockquote.doc-text {
  margin: 0;
  font-family: Charter, "Bitstream Charter", "Sitka Text", Cambria, serif;
  white-space: pre-wrap;
}
.ld-source-excerpt > summary {
  padding: 12px 0;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 0.15em;
}
.doc details p {
  overflow-wrap: anywhere;
}
.doc-cl,
.doc-foot {
  margin: 0;
  border-top: 1px solid var(--line);
  padding: 9px 18px;
  color: var(--muted);
  font-family: var(--sans);
  font-size: 11px;
  line-height: 1.4;
}
.doc-foot {
  background: var(--tint);
}
.exhibit-shot {
  display: block;
  width: auto;
  max-width: 100%;
  height: auto;
  border-top: 1px solid var(--line);
}
.pop .out {
  margin: 15px 0 0;
  font-size: 13px;
}
.ld-detail {
  cursor: pointer;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}
/* Popup affordances follow the actual target, including user-added text. A
   card's child labels do not become links merely because the card opens detail. */
html :is([data-detail], [data-evidence]) {
  cursor: pointer;
}
html
  :is(p, span, h1, h2, h3, h4, h5, h6, em, strong, a, text, tspan):is(
    [data-detail],
    [data-evidence]
  ):not(.ld-card, .sb-card) {
  text-decoration-line: underline;
  text-underline-offset: 0.15em;
  text-decoration-thickness: 1px;
}
html[data-mode="edit"] [data-editable][contenteditable="true"] {
  cursor: text;
}
html
  .ld-composed-unit[data-kind="text"]:is([data-detail], [data-evidence])
  > .ld-variant
  > [data-variant-body],
html .ld-reader-popup-text {
  text-decoration-line: underline;
  text-underline-offset: 0.15em;
  text-decoration-thickness: 1px;
}
.ld-inline-detail {
  display: inline;
  border: 0;
  border-radius: 0;
  padding: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  text-align: inherit;
  white-space: normal;
  text-decoration: underline;
  text-underline-offset: 0.15em;
  text-decoration-thickness: 1px;
}
.ld-inline-detail:hover {
  color: var(--detail-feedback);
}
.ld-detail-linkline {
  margin-top: 6px;
  font-size: 0.85em;
}
html
  :is(.ld-card, .sb-card, .ld-composed-unit[data-kind="card"]):is(
    [data-detail],
    [data-evidence]
  ):is(:hover, :focus-visible) {
  border-color: var(--detail-feedback);
}
.ld-detail text {
  -webkit-user-select: none;
  user-select: none;
}
html:not([data-mode="edit"]) .ld-detail text {
  pointer-events: none;
}
.ld-detail:hover > :is(.box, .band, .leaf, rect):first-child,
html:not([data-input-modality="pointer"])
  .ld-detail:focus-visible
  > :is(.box, .band, .leaf, rect):first-child {
  stroke: var(--detail-feedback) !important;
  stroke-width: 1.5 !important;
}
.ld-restore,
.ld-save-banner {
  position: sticky;
  top: 48px;
  z-index: 35;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  border-bottom: 1px solid var(--line-strong);
  background: var(--card);
  padding: 8px 12px;
  color: var(--ink);
  font-size: 12px;
}
.ld-restore button {
  min-height: 36px;
  border: 1px solid var(--line-strong);
  border-radius: 4px;
  background: var(--card);
  padding: 6px 10px;
}
.ld-save-banner {
  color: var(--muted);
  flex-wrap: wrap;
}
html[data-fixed-pages] .ld-save-banner {
  position: fixed;
  top: var(--ld-result-top, 64px);
  right: 12px;
  left: 12px;
}
.ld-save-banner .ld-export-preview {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 8px;
  color: var(--ink);
  text-decoration: underline;
  flex-shrink: 0;
}
@media (max-width: 820px) {
  /* The editor bar can wrap to several rows on touch-sized screens. Context
     controls reserve their own space inside the selected unit, so even the
     first title's controls remain reachable below that bar at scrollY=0. */
  .ld-unit-tools {
    position: relative;
    top: auto;
    right: auto;
    max-width: 100%;
    margin: 0 0 8px auto;
    transform: none;
  }
  .ld-matter {
    display: none;
  }
  .ld-bar {
    overflow: visible;
  }
  .ld-controls {
    flex-wrap: wrap;
    margin-left: auto;
    max-width: 100%;
    overflow: visible;
  }
  html[data-mode="edit"] .ld-controls {
    max-width: 100%;
  }
  .ld-overflow-menu {
    display: none;
    position: absolute;
    top: 44px;
    right: 8px;
    z-index: 60;
    flex-wrap: wrap;
    width: min(330px, calc(100vw - 16px));
    border: 1px solid var(--line-strong);
    border-radius: 4px;
    background: var(--card);
    padding: 7px;
  }
  html[data-mode="edit"] #editor-overflow-toggle {
    display: inline-flex;
  }
  html[data-editor-menu="open"] .ld-overflow-menu {
    display: flex;
  }
}

@media (max-width: 480px) {
  #theme-toggle {
    width: 44px;
    min-width: 44px;
  }
  .ld-bar {
    flex-wrap: wrap;
    padding-right: max(6px, env(safe-area-inset-right));
    padding-left: max(6px, env(safe-area-inset-left));
  }
  html[data-mode="edit"] .ld-bar {
    flex-wrap: wrap;
  }
  .ld-editor-tools {
    flex: 0 0 100%;
    min-width: 0;
    max-width: 100%;
    overflow-x: auto;
  }
  .ld-editor-tools .ld-tool,
  .ld-editor-tools select {
    min-width: 44px;
    min-height: 44px;
  }
  .ld-unit-tools .ld-ab button,
  .ld-unit-tools .ld-why-toggle {
    min-width: 44px;
    min-height: 44px;
  }
  .ld-controls {
    gap: 3px;
  }
  .ld-control {
    min-width: 44px;
    min-height: 44px;
    padding: 6px 7px;
    font-size: 11px;
  }
  .ld-form-nav {
    top: 54px;
    overflow-x: auto;
  }
  .ld-scrim {
    align-items: flex-end;
    padding: 0;
  }
  .pop {
    width: 100%;
    max-height: 88dvh;
    border-right: 0;
    border-bottom: 0;
    border-left: 0;
    border-radius: 5px 5px 0 0;
    padding: 20px 14px max(16px, env(safe-area-inset-bottom));
  }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    scroll-behavior: auto !important;
    transition: none !important;
    animation: none !important;
  }
}

/* 1200×900 is an authoring reference. The visible page uses the available
   window dimensions independently, with no fixed-ratio letterboxing.
   Popup detail retains ordinary viewport-sized text and its own scrolling. */
html[data-fixed-pages],
html[data-fixed-pages] body {
  width: 100%;
  height: 100%;
  min-height: 0;
  overflow: hidden;
}
html[data-fixed-pages] .ld-fixed-page {
  position: fixed !important;
  inset: auto !important;
  left: var(--ld-page-left, 0px) !important;
  top: var(--ld-page-top, 100px) !important;
  width: var(--ld-page-width, 1200px) !important;
  height: var(--ld-page-height, 900px) !important;
  min-width: 0 !important;
  max-width: none !important;
  min-height: 0 !important;
  max-height: none !important;
  margin: 0 !important;
  padding: 44px 48px !important;
  box-sizing: border-box !important;
  transform: scale(var(--ld-page-scale, 1));
  transform-origin: top left;
  overflow: visible !important;
  background: var(--bg);
  font-size: 18px;
  line-height: 1.45;
}
html[data-fixed-pages] .ld-composed-main {
  width: auto !important;
  max-width: none !important;
  margin: 0 !important;
  padding: 0 !important;
}
html[data-fixed-pages][data-stable-type] .ld-fixed-page {
  padding: var(--ld-content-padding) !important;
  font-size: var(--ld-type-body);
}
html[data-fixed-pages][data-stable-type]
  .ld-fixed-page
  :is(h1, .sb-title h1, .ld-title h1) {
  font-size: var(--ld-type-title);
  font-weight: 600;
  margin: 0;
}
html[data-fixed-pages][data-stable-type]
  .ld-fixed-page
  :is(h2, h3, .ld-figure-title) {
  font-size: var(--ld-type-section);
  font-weight: 600;
  margin-top: 0;
}
html[data-fixed-pages][data-stable-type]
  .ld-fixed-page
  .ld-composed-grid
  > :first-child
  :is(h1, h2) {
  font-size: var(--ld-type-title);
  line-height: 1.15;
  letter-spacing: -0.018em;
  color: var(--ink);
}
html[data-fixed-pages][data-stable-type]
  .ld-fixed-page
  :is([data-variant-body], .ld-composed-unit[data-role="summary"]) {
  font-size: var(--ld-type-body);
  line-height: 1.5;
}
html[data-fixed-pages][data-stable-type] .ld-fixed-page .ld-composed-grid {
  gap: var(--ld-content-gap);
  align-content: start;
}
@media (min-width: 761px) {
  /* A short slide still needs room for Contents, one reachable index row,
     and its pager. The rail and content frame retain the same bottom edge. */
  html[data-fixed-pages][data-stable-type][data-has-slide-index="true"]
    .ld-fixed-page
    .ld-composed-grid {
    min-height: 192px;
  }
}
html[data-fixed-pages][data-stable-type]
  .ld-fixed-page
  .ld-composed-unit:is(
    [data-kind="card"],
    [data-kind="decision"],
    [data-kind="figure"]
  ) {
  padding: calc(var(--ld-space) * 2.5);
}
html[data-fixed-pages][data-stable-type]
  .ld-fixed-page
  .ld-composed-unit[data-role="scope"]
  [data-variant-body] {
  font-size: var(--ld-type-caption);
}
html[data-fixed-pages][data-stable-type]
  .ld-fixed-page
  [data-variant-body]
  > :last-child {
  margin-bottom: 0;
}
html[data-fixed-pages][data-stable-type] .ld-fixed-page .ld-diagram svg {
  width: min(100%, 880px);
  max-height: none;
}
html[data-fixed-pages][data-stable-type]
  .ld-fixed-page
  .ld-diagram
  svg.narrow-viz {
  width: min(100%, 400px);
}
html[data-fixed-pages] #ld-page-approach {
  position: fixed;
  top: var(--ld-approach-top, 56px);
  left: var(--ld-page-left, 12px);
  z-index: 36;
  margin: 0;
  width: max-content;
  max-width: calc(100vw - 24px);
  flex-wrap: nowrap;
}
html[data-fixed-pages] .ld-fixed-page .ld-composed-grid {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: 20px;
  align-items: start;
}
html[data-fixed-pages] .ld-fixed-page .ld-composed-grid > .ld-composed-unit {
  grid-column: var(--ld-column) / span var(--ld-span) !important;
  grid-row: var(--ld-row) / span var(--ld-row-span, 1) !important;
}
html[data-fixed-pages]
  .ld-fixed-page
  .ld-composed-section
  + .ld-composed-section {
  margin-top: 20px;
}
html[data-fixed-pages] .ld-fixed-page :is(h1, .sb-title h1, .ld-title h1) {
  font-size: 32px;
  line-height: 1.15;
  letter-spacing: -0.018em;
  margin-top: 0;
}
html[data-fixed-pages] .ld-fixed-page :is(h2, .ld-figure-title) {
  font-size: 22px;
  line-height: 1.25;
}
html[data-fixed-pages]
  .ld-fixed-page
  :is([data-variant-body], .ld-composed-unit[data-role="summary"]) {
  font-size: 18px;
  line-height: 1.45;
}
html[data-fixed-pages] .ld-fixed-page .ld-composed-unit[data-kind="figure"] {
  padding: 20px;
  margin: 0;
  border-radius: 6px;
}
html[data-fixed-pages] .ld-fixed-page :is(.ld-diagram, .ld-diagram svg) {
  min-height: 0;
}
html[data-fixed-pages] .ld-fixed-page .ld-diagram svg {
  width: min(100%, 800px);
  max-width: 100%;
  height: auto;
  max-height: calc(var(--ld-page-height, 900px) * 0.52);
}
html[data-fixed-pages][data-composed-shell] .ld-fixed-page .ld-diagram svg {
  width: min(100%, 1000px);
}
/* Portrait source rails occupy their spanning column, not the generic
   landscape-figure height cap. Explicit editor sizes still take precedence. */
html[data-fixed-pages][data-composed-shell]
  .ld-fixed-page
  .ld-diagram
  svg.timeline-vertical {
  width: min(100%, 470px);
  max-height: none;
  margin-inline: 0;
}
/* Keep the approved compact composition. On wide windows, portrait rails and
   prose cards stop growing once their content reaches a useful reading width.
   Inline editor widths/max-width overrides remain authoritative. */
html[data-fixed-pages][data-composed-shell]
  .ld-fixed-page
  .ld-composed-unit[data-kind="figure"]:has(svg.timeline-vertical) {
  width: 100%;
  max-width: 510px;
  justify-self: start;
}
/* A spanning portrait rail ties adjacent narrative rows into one reading group.
   Its tighter vertical rhythm also leaves room for the dark-theme card edges. */
html[data-fixed-pages][data-composed-shell]
  .ld-fixed-page
  .ld-composed-grid:has(svg.timeline-vertical) {
  row-gap: 16px;
}
@media (min-width: 1600px) and (min-aspect-ratio: 8 / 5) {
  html[data-fixed-pages][data-composed-shell]
    .ld-fixed-page
    .ld-composed-unit[data-kind="card"] {
    font-size: 18px;
    width: 100%;
    max-width: calc(72ch + 40px);
    justify-self: start;
  }
  html[data-fixed-pages][data-composed-shell]
    .ld-fixed-page
    .ld-composed-unit[data-kind="card"]
    [data-variant-body] {
    max-width: 72ch;
  }
}
html[data-fixed-pages] .ld-composed-unit[data-kind="card"][data-role="action"] {
  padding: 20px;
  border-top: 2px solid var(--red);
}
/* Issue briefs have a fixed anatomy, not LLM-authored row geometry. Both
   columns fill their track; native type and saved object sizes stay intact. */
.ld-issue-body {
  grid-column: 1 / -1;
  grid-row: var(--ld-row, 2);
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--ld-content-gap, 16px);
  min-width: 0;
  align-items: start;
}
.ld-issue-analysis,
.ld-issue-support {
  display: grid;
  gap: var(--ld-content-gap, 16px);
  min-width: 0;
  align-content: start;
}
html[data-fixed-pages][data-composed-shell] .ld-issue-body .ld-composed-unit {
  grid-column: auto !important;
  grid-row: auto !important;
  width: 100%;
  max-width: 100%;
  min-width: 0;
  justify-self: stretch;
  margin: 0;
}
html[data-fixed-pages][data-composed-shell]
  .ld-issue-analysis
  > .ld-composed-unit[data-kind="card"] {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 6px;
  box-shadow: var(--card-shadow, 0 2px 8px #0000000c);
}
html[data-fixed-pages][data-composed-shell]
  .ld-issue-analysis
  > .ld-composed-unit[data-role="action"] {
  border-top: 2px solid var(--red);
}
html[data-theme="dark"][data-fixed-pages][data-composed-shell]
  .ld-issue-analysis
  > .ld-composed-unit[data-kind="card"] {
  border-color: var(--line-strong);
}
html[data-theme="dark"][data-fixed-pages][data-composed-shell]
  .ld-issue-analysis
  > .ld-composed-unit[data-role="action"] {
  border-top-color: var(--red);
}
html[data-fixed-pages][data-composed-shell]
  .ld-issue-analysis
  > .ld-composed-unit[data-kind="card"]:is([data-detail], [data-evidence]):is(
    :hover,
    :focus-visible
  ) {
  border-color: var(--detail-feedback);
}
html[data-fixed-pages][data-composed-shell]
  .ld-issue-analysis
  > .ld-composed-unit[data-role="action"]:is(:hover, :focus-visible) {
  border-top-color: var(--red);
}
.ld-source-preview {
  margin: 0;
  min-width: 0;
  padding: 20px;
  background: var(--card);
  border: 1px solid var(--line-strong);
  border-radius: 6px;
}
.ld-source-preview :is(img, svg) {
  display: block;
  width: auto;
  max-width: 100%;
  height: auto;
  margin-inline: auto;
}
.ld-source-preview blockquote {
  margin: 0;
  padding: 0;
  font-family: Charter, "Bitstream Charter", Georgia, serif;
  font-size: 18px;
  line-height: 1.45;
  color: var(--ink);
}
.ld-source-preview :is(figcaption, .ld-source-caption) {
  display: block;
  margin-top: 12px;
  font-size: var(--ld-type-caption, 12px);
  line-height: 1.4;
  color: var(--muted);
}
.ld-page-reader .ld-issue-body {
  display: flex;
  flex-direction: column;
}
.ld-page-reader :is(.ld-issue-analysis, .ld-issue-support) {
  width: 100%;
}
html[data-fixed-pages] .ld-composed-unit[data-kind="table"] table {
  width: 100%;
  border-collapse: collapse;
  text-align: left;
}
html[data-fixed-pages] .ld-composed-unit[data-kind="table"] :is(th, td) {
  border-bottom: 1px solid var(--line);
  padding: 10px 12px;
  vertical-align: top;
  text-align: left;
}
html[data-fixed-pages] .ld-composed-unit[data-kind="table"] th {
  background: var(--tint);
  font-size: 15px;
  font-weight: 600;
}
html[data-fixed-pages] .ld-composed-unit[data-kind="table"] tr:last-child td {
  border-bottom: 0;
}
html[data-fixed-pages]
  .ld-composed-unit[data-role="scope"]
  [data-variant-body] {
  font-size: 13px;
  line-height: 1.4;
}
html[data-fixed-pages] .ld-fixed-page .ld-decision-inputs {
  flex-direction: row;
}
html[data-fixed-pages] .ld-reference-page > .ld-figure {
  width: 100%;
  margin-top: 24px;
  padding: 20px;
}
html[data-fixed-pages] .ld-reference-cards {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  grid-template-areas: none;
  gap: 20px;
  margin-top: 0;
  align-items: start;
}
html[data-fixed-pages] .ld-reference-cards > .ld-card {
  grid-area: auto;
  width: auto;
}
html[data-fixed-pages] .ld-reference-page .ld-card-copy {
  font-size: 16px;
  line-height: 1.4;
}
/* Legacy template compositions use intrinsic page width too; viewport media
   queries may change chrome and popup layout, never stack page columns. */
html[data-fixed-pages] .ld-fixed-page .finding-grid {
  grid-template-columns: minmax(0, 1.28fr) minmax(310px, 0.9fr);
}
html[data-fixed-pages] .ld-fixed-page .dash-head {
  grid-template-columns: minmax(180px, 0.65fr) minmax(0, 2fr);
  gap: 24px;
}
html[data-fixed-pages] .ld-fixed-page .dash-facts :has(> .df) {
  grid-template-columns: repeat(6, minmax(0, 1fr));
}
html[data-fixed-pages]
  .ld-fixed-page
  :is(.issue-cards, .area-register, .sb-cards, .sb-open-rows, .sb-decisions) {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}
html[data-fixed-pages] .ld-fixed-page .issue-row {
  min-width: 0;
}
html[data-fixed-pages] .ld-fixed-page .reg .rrow {
  grid-template-columns: 18px minmax(0, 1fr) auto;
}
html[data-fixed-pages] .ld-fixed-page .report-register .reg .rrow {
  padding-block: 5px;
}
html[data-fixed-pages] .ld-fixed-page .reg .stw {
  grid-column: auto;
}
html[data-fixed-pages] .ld-fixed-page .foot {
  position: absolute;
  bottom: 12px;
  left: 48px;
  right: 48px;
  flex-direction: row;
}
html[data-fixed-pages] .ld-fixed-page .sb-figure {
  padding: 20px;
}
html[data-fixed-pages] .ld-fixed-page .sb-figure .ld-diagram {
  width: min(920px, 100%);
}
html[data-fixed-pages] .ld-fixed-page .sb-body {
  margin-top: 24px;
}
html[data-fixed-pages] .ld-fixed-page .sb-foot {
  flex-direction: row;
  margin-top: 24px;
}
.ld-reference-evidence {
  margin-top: 14px;
  min-height: 40px;
}
html[data-fixed-pages] .ld-fixed-page .ld-unit-tools {
  position: absolute;
  inset: 0 0 auto auto;
  width: max-content;
  margin: 0;
  transform: scale(calc(1 / var(--ld-page-scale, 1))) translateY(-100%)
    translateY(var(--ld-context-offset, 0px));
  transform-origin: top right;
}
.ld-page-fit-error {
  position: fixed;
  z-index: 90;
  left: max(12px, var(--ld-page-left, 12px));
  right: 12px;
  bottom: 12px;
  padding: 14px 18px;
  border: 1px solid var(--red);
  border-radius: 6px;
  background: var(--bg);
  color: var(--ink);
  font: 14px / 1.45 var(--sans);
}
.ld-read-page {
  position: fixed;
  z-index: 40;
  right: 16px;
  bottom: 12px;
  min-height: 44px;
  width: 44px;
  padding: 0;
  display: grid;
  place-items: center;
  border-radius: 6px;
  background: var(--card);
  font-size: 14px;
}
html .pop.ld-page-reader {
  width: min(760px, 100%);
  max-height: calc(100dvh - 24px);
  overflow: auto;
  padding: 46px 22px 24px;
  font-size: 18px;
  line-height: 1.5;
}
.ld-page-reader .ld-reader-section {
  display: block;
  width: 100%;
  max-width: none;
  margin: 0 0 24px;
  padding: 0;
  background: transparent;
  border: 0;
  box-shadow: none;
}
.ld-page-reader .ld-reader-unit {
  display: block;
  width: 100%;
  margin: 0 0 22px;
  padding: 0;
}
.ld-page-reader
  :is(
    .ld-composed-grid,
    .ld-core,
    .finding-grid,
    .dash-head,
    .sb-cards,
    .sb-open-rows,
    .sb-decisions,
    .ld-reference-cards
  ) {
  display: block;
}
.ld-page-reader :is(.foot, .sb-foot, .ld-foot) {
  position: static;
  margin-top: 24px;
}
.ld-page-reader :is(h1, h2, h3) {
  font-size: 22px;
  line-height: 1.25;
  margin: 0 0 12px;
}
.ld-page-reader :is(p, li, span, td, th) {
  font-size: 18px;
  line-height: 1.5;
}
.ld-page-reader .ld-diagram {
  display: block;
  width: 100%;
  overflow: auto;
}
.ld-page-reader .ld-diagram svg {
  width: 100%;
  max-width: none;
  height: auto;
}
@media (max-width: 600px) {
  html .pop.ld-page-reader {
    padding-inline: 14px;
  }
}
@media print {
  html[data-fixed-pages],
  html[data-fixed-pages] body {
    overflow: visible;
    height: auto;
  }
  html[data-fixed-pages] .ld-fixed-page {
    position: relative !important;
    left: 0 !important;
    top: 0 !important;
    transform: none;
    break-after: page;
  }
}

/* Persistent section index: shared by composed and legacy walkthroughs. */
html[data-has-slide-index="true"] .ld-slide-index {
  position: fixed;
  inset: var(--ld-index-top, 72px) auto 0 var(--ld-index-left, 0px);
  z-index: 35;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  width: 206px;
  margin: 0;
  overflow: auto;
  border: 0;
  border-right: 1px solid var(--line);
  border-radius: 0;
  background: var(--bg);
  padding: 8px 12px 20px;
}
.ld-slide-index [data-index-list] {
  display: flex;
  flex-direction: column;
  flex: 1;
  gap: 3px;
}
.ld-slide-index [data-index-list][hidden] {
  display: none;
}
.ld-slide-index .ld-index-toggle {
  width: 100%;
  min-height: 44px;
  margin-bottom: 8px;
  border-bottom: 1px solid var(--line);
  text-align: left;
}
.ld-slide-index [data-report-section] {
  width: 100%;
  display: block;
  min-height: 28px;
  padding: 4px 6px;
  flex-shrink: 0;
  white-space: normal;
  text-align: left;
  overflow-wrap: anywhere;
  font-size: 12px;
  font-weight: 450;
  line-height: 1.35;
  border-color: transparent;
  background: transparent;
  color: var(--muted);
}
.ld-slide-index [data-report-section]:has(> .ld-index-number) {
  display: grid;
  grid-template-columns: 14px 1fr;
  gap: 6px;
}
.ld-index-group {
  border: 0;
  flex-shrink: 0;
  margin-top: 6px;
}
.ld-index-group > summary {
  display: flex;
  align-items: baseline;
  gap: 6px;
  padding: 5px 6px;
  color: var(--muted);
  font-size: 10px;
  font-weight: 600;
  line-height: 1.4;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  cursor: pointer;
  list-style: none;
}
.ld-index-group > summary::-webkit-details-marker {
  display: none;
}
.ld-index-group > summary::before {
  content: "›";
  width: 8px;
  flex-shrink: 0;
}
.ld-index-group[open] > summary::before {
  content: "⌄";
}
.ld-index-group:has([aria-current="true"]) > summary {
  color: var(--ink);
}
.ld-index-group > summary:focus-visible {
  outline: 2px solid var(--interaction);
  outline-offset: -2px;
}
.ld-index-group-items {
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.ld-slide-index[data-index-expanded="false"] .ld-index-pager {
  display: none;
}
@media (max-width: 760px), (pointer: coarse) {
  .ld-slide-index [data-report-section],
  .ld-index-group > summary {
    min-height: 44px;
  }
}
[data-section-target] {
  cursor: pointer;
}
[data-section-target]:hover {
  box-shadow:
    inset 0 0 0 1px var(--detail-feedback),
    var(--shadow-soft);
}
[data-section-target]:focus-visible {
  outline: 2px solid var(--detail-feedback);
  outline-offset: 3px;
}
.ld-overview-groups {
  grid-column: var(--ld-column, 1) / span var(--ld-span, 12);
  grid-row: var(--ld-row, auto);
  min-width: 0;
  align-self: start;
  border-top: 1px solid var(--line-strong);
}
.ld-overview-group {
  margin: 0;
  border-bottom: 1px solid var(--line-strong);
}
.ld-overview-group > summary {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 36px;
  padding: 7px 10px;
  color: var(--ink);
  cursor: pointer;
  list-style: none;
  font-size: 16px;
  font-weight: 600;
  line-height: 1.35;
}
.ld-overview-group > summary::-webkit-details-marker {
  display: none;
}
.ld-overview-group > summary::before {
  content: "+";
  display: inline-block;
  width: 14px;
  color: var(--muted);
  font-weight: 450;
}
.ld-overview-group[open] > summary::before {
  content: "−";
}
.ld-overview-group > summary:hover {
  background: var(--tint);
}
.ld-overview-group > summary:focus-visible {
  outline: 2px solid var(--interaction);
  outline-offset: -2px;
}
.ld-overview-group-count {
  margin-left: auto;
  color: var(--muted);
  font-size: 12px;
  font-weight: 450;
  white-space: nowrap;
}
.ld-overview-group-label {
  min-width: 0;
  overflow-wrap: anywhere;
}
.ld-overview-topic-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 8px;
  padding: 4px 10px 12px;
}
html[data-fixed-pages][data-stable-type]
  .ld-fixed-page
  .ld-overview-topic-grid
  > section.ld-composed-unit {
  grid-area: auto !important;
  /* The wide-page prose measure sets generic cards to justify-self:start.
     Topic tiles instead fill their track; explicit inline widths still win. */
  justify-self: stretch;
  width: auto;
  max-width: none;
  min-width: 0;
  padding: 10px 12px;
  background: var(--card);
  box-shadow: var(--shadow-soft);
  border: 1px solid var(--line);
}
html[data-fixed-pages][data-stable-type]
  .ld-fixed-page
  .ld-overview-topic-grid
  :is(h2, h3, p) {
  font-size: 16px;
  line-height: 1.35;
}
html[data-fixed-pages][data-stable-type]
  .ld-fixed-page
  .ld-overview-topic-grid
  :is(h2, h3) {
  margin: 0;
  font-weight: 600;
  color: var(--ink);
}
html[data-fixed-pages][data-stable-type]
  .ld-fixed-page
  .ld-overview-topic-grid
  p {
  margin-top: 4px;
  color: var(--muted);
}
.ld-page-reader .ld-overview-topic-grid {
  grid-template-columns: 1fr;
  padding: 4px 0 12px;
}
.ld-page-reader .ld-overview-group > summary {
  min-height: 44px;
  font-size: 18px;
}
.ld-page-reader .ld-overview-topic-grid :is(h2, h3, p) {
  font-size: 18px;
}
.ld-page-reader .ld-overview-topic-grid > section {
  padding: 12px;
  border: 1px solid var(--line);
  border-radius: 4px;
}
.ld-slide-index [data-report-section][aria-current="true"] {
  color: var(--ink);
  background: var(--tint);
  border-color: var(--line-strong);
  font-weight: 600;
}
.ld-index-number {
  color: var(--faint);
  font-size: 10px;
  padding-top: 2px;
}
[aria-current="true"] > .ld-index-number {
  color: var(--red);
}
.ld-slide-index [data-report-section]:hover {
  color: var(--ink);
  background: var(--card);
}
.ld-slide-index .ld-index-pager {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
  margin-top: auto;
  padding-top: 16px;
  border-top: 1px solid var(--line);
}
.ld-slide-index .ld-index-pager [data-walkthrough-status] {
  grid-column: 1 / -1;
  grid-row: 1;
  padding: 8px 0;
}
html[data-stable-type] .ld-slide-index {
  overflow: hidden;
}
html[data-stable-type] .ld-slide-index .ld-index-toggle {
  flex-shrink: 0;
}
html[data-stable-type] .ld-slide-index [data-index-list] {
  min-height: 0;
  overflow-y: auto;
}
html[data-stable-type] .ld-slide-index .ld-index-pager {
  flex-shrink: 0;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 4px;
  margin-top: 10px;
  padding-top: 8px;
}
html[data-stable-type]
  .ld-slide-index
  .ld-index-pager
  [data-walkthrough-status] {
  grid-column: 2;
  grid-row: 1;
  padding: 4px 0;
  font-size: 11px;
}
html[data-stable-type] .ld-slide-index [data-walkthrough-previous] {
  grid-column: 1;
  grid-row: 1;
}
html[data-stable-type] .ld-slide-index [data-walkthrough-next] {
  grid-column: 3;
  grid-row: 1;
}
@media (max-width: 760px) {
  html[data-stable-type]
    .ld-slide-index:not([data-index-expanded="true"])
    .ld-index-pager {
    display: none;
  }
}
html[data-has-slide-index="true"] .ld-composed-main,
html[data-has-slide-index="true"] .sb-stage {
  width: calc(100% - 250px);
  max-width: 1320px;
  margin-left: 226px;
  margin-right: 24px;
}
html[data-has-slide-index="true"] .sb-stage {
  padding-inline: 0;
}
.ld-slide-index .sb-navigation {
  display: flex;
  flex-wrap: wrap;
  margin-top: 16px;
  gap: 8px;
}
.ld-slide-index .sb-navigation .sb-pills {
  display: none;
}
.ld-slide-index .sb-navigation #position-label {
  display: block;
  order: -1;
  width: 100%;
  text-align: center;
  border: 0;
  background: transparent;
}
.ld-slide-index .sb-navigation button {
  flex: 1;
  min-height: 44px;
}
@media (max-width: 760px) {
  html[data-has-slide-index="true"] .ld-slide-index {
    left: 12px;
    width: 44px;
    overflow-x: hidden;
    background: var(--bg);
    border: 0;
    padding: 0;
  }
  html[data-has-slide-index="true"]
    .ld-slide-index[data-index-expanded="true"] {
    z-index: 38;
    width: min(280px, calc(100vw - 24px));
    padding: 12px;
    background: var(--card);
    border-radius: 12px;
    box-shadow: var(--shadow);
    bottom: auto;
    max-height: calc(100dvh - var(--ld-index-top, 72px) - 12px);
  }
  .ld-slide-index .ld-index-toggle {
    text-align: center;
    padding-inline: 4px;
  }
  html[data-has-slide-index="true"] .ld-composed-main,
  html[data-has-slide-index="true"] .sb-stage {
    width: calc(100% - 80px);
    margin-left: 68px;
    margin-right: 12px;
  }
}
@media print {
  html[data-has-slide-index="true"] .ld-slide-index {
    display: none;
  }
  html[data-has-slide-index="true"] .ld-composed-main,
  html[data-has-slide-index="true"] .sb-stage {
    width: 100%;
    max-width: none;
    margin: 0;
  }
}
</style>
<script id="ld-components">
(() => {
  window.LegalDesign = window.LegalDesign || {};
  var LD = window.LegalDesign;
  var REGISTRY = [
    "flow",
    "timeline",
    "hierarchy",
    "zones",
    "beforeAfter",
    "hub",
    "compareTwo",
    "funnel",
    "loop",
    "matrix",
    "figurePath",
    "stairSteps",
    "pyramid",
    "gears",
    "rungBars",
    "hairlineLine",
    "hairlineArea",
    "tickDonut",
    "tickRows",
    "pairedRungs",
    "stackedRungs",
    "plumbScatter",
    "rungWaterfall",
    "dotHeat",
    "tickGauge",
    "dumbbell",
    "treemap",
    "rungHistogram",
    "tickBox",
    "streamRibbon",
    "rangeBars",
  ];
  var C = Object.create(null);
  var NS = "http://www.w3.org/2000/svg";
  var ACCENT_ROLES = Object.freeze([
    "open",
    "incomplete",
    "uncovered",
    "recommendation",
    "action",
    "decision",
    "active",
  ]);

  function esc(t) {
    return String(t)
      .replace(/&/g, "&amp;")
      .replace(/</g, "&lt;")
      .replace(/>/g, "&gt;")
      .replace(/"/g, "&quot;")
      .replace(/'/g, "&#39;");
  }

  function known(value, name, allowed) {
    object(value, name);
    Object.keys(value).forEach((key) => {
      if (!allowed.includes(key))
        throw new TypeError(`${name}.${key} is an unknown field`);
    });
    return value;
  }

  function object(value, name = "data") {
    if (!value || typeof value !== "object" || Array.isArray(value))
      throw new TypeError(`${name} must be an object`);
    return value;
  }
  function text(value, name, optional = false) {
    if (optional && value == null) return "";
    if (typeof value !== "string" || (!optional && !value.trim()))
      throw new TypeError(
        `${name} must be ${optional ? "a string when supplied" : "a non-empty string"}`,
      );
    return value;
  }
  function list(value, name, min, max) {
    if (!Array.isArray(value) || value.length < min || value.length > max)
      throw new TypeError(
        `${name} must contain ${min === max ? min : `${min}–${max}`} items`,
      );
    return value;
  }
  function choice(value, name, choices, optional = false) {
    if (optional && value == null) return;
    if (!choices.includes(value))
      throw new TypeError(`${name} must be one of ${choices.join(", ")}`);
  }
  function strings(value, name, min, max = min) {
    list(value, name, min, max);
    value.forEach((v, i) => {
      text(v, `${name}[${i}]`);
    });
    return value;
  }
  function records(value, name, min, max, required, optional = []) {
    list(value, name, min, max);
    value.forEach((v, i) => {
      known(v, `${name}[${i}]`, required.concat(optional));
      required.forEach((k) => {
        text(v[k], `${name}[${i}].${k}`);
      });
      optional.forEach((k) => {
        if (k === "accentRole") {
          choice(v[k], `${name}[${i}].${k}`, ACCENT_ROLES, true);
        } else if (k !== "leaves") text(v[k], `${name}[${i}].${k}`, true);
      });
    });
    return value;
  }
  function checkOpts(opts) {
    known(opts, "opts", [
      "narrow",
      "compact",
      "density",
      "width",
      "theme",
      "ariaLabel",
    ]);
    if (opts.density != null && !["standard", "compact"].includes(opts.density))
      throw new TypeError("opts.density must be standard or compact");
    if (opts.narrow != null && typeof opts.narrow !== "boolean")
      throw new TypeError("opts.narrow must be boolean");
    if (opts.compact != null && typeof opts.compact !== "boolean")
      throw new TypeError("opts.compact must be boolean");
  }
  function detailWrap(markup, detail, hitbox) {
    // Text-only SVG groups have no painted shape. Labels intentionally ignore
    // pointer events in view mode, so give them an explicit transparent target.
    const hit =
      detail && hitbox
        ? `<rect class="detail-hit" x="${hitbox[0]}" y="${hitbox[1]}" width="${hitbox[2]}" height="${hitbox[3]}" fill="transparent" stroke="none" pointer-events="all"/>`
        : "";
    return typeof detail === "string" && detail
      ? `<g data-diagram-object class="ld-detail" role="button" tabindex="0" aria-haspopup="dialog" aria-controls="${esc(detail)}" data-detail="${esc(detail)}">${hit}${markup}</g>`
      : `<g data-diagram-object>${markup}</g>`;
  }
  function sentence(data) {
    const words = [];
    const visit = (value) => {
      if (typeof value === "string" && value.trim()) words.push(value.trim());
      else if (Array.isArray(value)) value.forEach(visit);
      else if (value && typeof value === "object")
        Object.keys(value)
          .filter((key) => key !== "detail")
          .forEach((key) => {
            visit(value[key]);
          });
    };
    visit(data);
    return `Diagram showing ${words.slice(0, 10).join(", ")}.`;
  }

  function lines(value, maxChars = 20, maxLines = 3) {
    const words = String(value).trim().split(/\s+/),
      out = [];
    words.forEach((word) => {
      if (!out.length || `${out.at(-1)} ${word}`.length > maxChars)
        out.push(word);
      else out[out.length - 1] += ` ${word}`;
    });
    if (out.length > maxLines)
      return [...out.slice(0, maxLines - 1), out.slice(maxLines - 1).join(" ")];
    return out;
  }
  function txt(value, x, y, width, cls = "", maxLines = 2, anchor = "middle") {
    if (value == null || value === "") return "";
    // Uppercase relationship labels include tracking, so budget their real
    // letter width instead of letting them intrude into adjacent cards.
    const chars = Math.max(
        5,
        Math.floor(width / (cls === "edge" ? 8.25 : 6.2)),
      ),
      rows = lines(value, chars, maxLines),
      first = y - (rows.length - 1) * 7;
    return `<text class="${cls}" x="${x}" y="${first}" text-anchor="${anchor}">${rows.map((row, i) => `<tspan x="${x}" dy="${i ? 14 : 0}">${esc(row)}</tspan>`).join("")}</text>`;
  }
  function svgMarkup(body, opts, viewBox, extraClass = "", data = {}) {
    checkOpts(opts);
    const aria = esc(
      typeof opts.ariaLabel === "string" && opts.ariaLabel
        ? opts.ariaLabel
        : sentence(data),
    );
    const width = opts.width == null ? "100%" : esc(opts.width);
    const theme = opts.theme == null ? "" : ` data-theme="${esc(opts.theme)}"`;
    const role = body.includes('data-detail="') ? "group" : "img";
    return `<svg xmlns="${NS}" class="library-viz diagram-viz${extraClass ? ` ${extraClass}` : ""}" viewBox="${viewBox}" width="${width}" role="${role}" aria-label="${aria}"${theme}>
    <style>
      .diagram-viz{display:block;color:var(--ink);overflow:visible}.diagram-viz text{font-family:var(--sans);font-size:14px;font-weight:600;fill:var(--ink);stroke:none}.diagram-viz .sub{font-size:12.25px;font-weight:400;fill:var(--muted)}.diagram-viz .zone,.diagram-viz .edge{font-family:var(--sans);font-size:12.25px;font-weight:650;letter-spacing:0;text-transform:none}.diagram-viz .edge{fill:var(--muted)}.diagram-viz .line,.diagram-viz .hair{fill:none;stroke:var(--line-strong);stroke-width:1;vector-effect:non-scaling-stroke}.diagram-viz .box{fill:var(--card);stroke:transparent;stroke-width:1.5;filter:drop-shadow(0 1px 2px rgba(0,0,0,.06)) drop-shadow(0 7px 16px rgba(0,0,0,.07));vector-effect:non-scaling-stroke}.diagram-viz .box.tint{fill:var(--tint);stroke:var(--line-strong)}.diagram-viz .box.accent{fill:var(--red-wash);stroke:var(--red)}.diagram-viz .tint{fill:var(--tint)}.diagram-viz .inkfill{fill:var(--ink);fill-opacity:.1;stroke:var(--ink);stroke-width:1;vector-effect:non-scaling-stroke}.diagram-viz .band{fill:var(--tint);stroke:var(--line-strong);stroke-width:1;vector-effect:non-scaling-stroke}.diagram-viz .edge-band{fill:var(--tint);stroke:none}.diagram-viz .open{stroke:var(--red);fill:var(--red-wash)}.diagram-viz .leaf{fill:var(--tint);stroke:transparent;stroke-width:1;vector-effect:non-scaling-stroke}.diagram-viz .ld-detail{cursor:pointer;outline:none}.diagram-viz .ld-detail text{user-select:none}
      .chart-viz text{font-size:11.75px}.chart-viz .chart-title{font-size:12.5px;font-weight:650;fill:var(--ink)}.chart-viz .axis-title{font-size:11.75px;font-weight:600;fill:var(--ink)}.chart-viz .value{font-size:12px;font-weight:500;fill:var(--ink)}.chart-viz .muted,.chart-viz .source{fill:var(--muted)}.chart-viz .source{font-size:11.75px}.chart-viz .quiet{fill:none;stroke:var(--muted);stroke-width:1;vector-effect:non-scaling-stroke}.chart-viz .faint{fill:none;stroke:currentColor;stroke-opacity:.18;stroke-width:1;vector-effect:non-scaling-stroke}.chart-viz .fill,.chart-viz .tone-1,.chart-viz .tone-2,.chart-viz .tone-3,.chart-viz .tone-4{stroke:currentColor;stroke-width:1;vector-effect:non-scaling-stroke}.chart-viz .label-pad{stroke:none}.chart-viz .fill,.chart-viz .tone-1{fill:color-mix(in srgb,currentColor 14%,var(--card))}.chart-viz .tone-2{fill:color-mix(in srgb,currentColor 28%,var(--card))}.chart-viz .tone-3{fill:color-mix(in srgb,currentColor 48%,var(--card))}.chart-viz .tone-4{fill:color-mix(in srgb,currentColor 72%,var(--card))}.chart-viz .open{stroke:var(--red);fill:var(--red-wash)}.chart-viz .open-line{stroke:var(--red)}
      .diagram-viz .ld-detail:hover>.box:first-child,.diagram-viz .ld-detail:hover>.leaf:first-child,html:not([data-input-modality="pointer"]) .diagram-viz .ld-detail:focus-visible>.box:first-child,html:not([data-input-modality="pointer"]) .diagram-viz .ld-detail:focus-visible>.leaf:first-child{stroke:var(--interaction)}
      :is(html[data-theme="dark"] .diagram-viz:not([data-theme="light"]),.diagram-viz[data-theme="dark"]){--ld-node-fill:var(--card,#111);--ld-node-ink:#fff;--ld-node-muted:#fff;--ld-node-accent-ink:#fff;--ld-node-outline:#999}
      :is(html[data-theme="dark"] .diagram-viz:not([data-theme="light"]),.diagram-viz[data-theme="dark"]):not(.chart-viz) :is(.box,.leaf,.band,.tint,.inkfill){fill:var(--ld-node-fill);fill-opacity:1;stroke:var(--ld-node-outline);stroke-width:2;filter:none}
      :is(html[data-theme="dark"] .diagram-viz:not([data-theme="light"]),.diagram-viz[data-theme="dark"]):not(.chart-viz) :is(.box,.inkfill)+circle.line{stroke:var(--ld-node-muted)}
      :is(html[data-theme="dark"] .diagram-viz:not([data-theme="light"]),.diagram-viz[data-theme="dark"]):not(.chart-viz) :is(.box,.band,.leaf).accent,:is(html[data-theme="dark"] .diagram-viz:not([data-theme="light"]),.diagram-viz[data-theme="dark"]):not(.chart-viz) .open{fill:var(--ld-node-fill);fill-opacity:1;stroke:var(--red)}
      :is(html[data-theme="dark"] .diagram-viz:not([data-theme="light"]),.diagram-viz[data-theme="dark"]):not(.chart-viz) text{fill:var(--ld-node-ink)}
      :is(html[data-theme="dark"] .diagram-viz:not([data-theme="light"]),.diagram-viz[data-theme="dark"]):not(.chart-viz) :is(.line,.hair){stroke:#fff;stroke-width:1.5}
      :is(html[data-theme="dark"] .diagram-viz:not([data-theme="light"]),.diagram-viz[data-theme="dark"]):not(.chart-viz) [data-diagram-object]:has(> :is(.box,.leaf,.band,.tint,.inkfill))>text,:is(html[data-theme="dark"] .diagram-viz:not([data-theme="light"]),.diagram-viz[data-theme="dark"]):not(.chart-viz) .surface-text{fill:var(--ld-node-ink)}
      :is(html[data-theme="dark"] .diagram-viz:not([data-theme="light"]),.diagram-viz[data-theme="dark"]):not(.chart-viz) [data-diagram-object]:has(> :is(.box,.leaf,.band,.tint))>text.sub{fill:var(--ld-node-muted)}
      :is(html[data-theme="dark"] .diagram-viz:not([data-theme="light"]),.diagram-viz[data-theme="dark"]):not(.chart-viz) [data-diagram-object]:has(> :is(.accent,.open))>text:is(.sub,:not(.sub)){fill:var(--ld-node-accent-ink)}
      html[data-theme="dark"] .chart-viz .fill.open,html[data-theme="dark"] .chart-viz .tone-1.open,.chart-viz[data-theme="dark"] .fill.open,.chart-viz[data-theme="dark"] .tone-1.open{fill:color-mix(in srgb,currentColor 14%,var(--card))}
      html[data-theme="dark"] .chart-viz .tone-2.open,.chart-viz[data-theme="dark"] .tone-2.open{fill:color-mix(in srgb,currentColor 28%,var(--card))}
      html[data-theme="dark"] .chart-viz .tone-3.open,.chart-viz[data-theme="dark"] .tone-3.open{fill:color-mix(in srgb,currentColor 48%,var(--card))}
      html[data-theme="dark"] .chart-viz .tone-4.open,.chart-viz[data-theme="dark"] .tone-4.open{fill:color-mix(in srgb,currentColor 72%,var(--card))}
    </style>${body}</svg>`;
  }
  function svg(body, opts, frame = "pane", data = {}) {
    const viewBox =
      frame === "wideTall"
        ? "0 0 880 420"
        : frame === "wide"
          ? "0 0 880 360"
          : "0 0 400 400";
    return svgMarkup(body, opts, viewBox, "", data);
  }
  function accentAttr(role) {
    return role ? ` data-accent-role="${esc(role)}"` : "";
  }
  function box(x, y, w, h, title, sub = "", opts = {}) {
    const cls = `box${opts.tint ? " tint" : ""}${opts.accentRole ? " accent" : ""}${opts.open ? " open" : ""}`;
    const cy = y + h / 2;
    return detailWrap(
      `<rect class="${cls}"${accentAttr(opts.accentRole)} x="${x}" y="${y}" width="${w}" height="${h}" rx="7"/>${txt(title, x + w / 2, cy - (sub ? 8 : 0), w - 18, opts.titleClass || "", 2)}${sub ? txt(sub, x + w / 2, cy + 19, w - 18, "sub", 2) : ""}`,
      opts.detail,
    );
  }

  function flowCard(x, y, w, h, stage) {
    return box(x, y, w, h, stage.title, stage.sub, stage);
  }
  function hierarchyLeaf(x, y, w, h, leaf) {
    return detailWrap(
      `<rect class="leaf" x="${x}" y="${y}" width="${w}" height="${h}" rx="5"/>${txt(leaf.title, x + w / 2, y + h / 2 + 4, w - 12, "sub", 2)}`,
      leaf.detail,
    );
  }
  function arrowH(x1, x2, y, edge, reverse = false) {
    const from = reverse ? x2 : x1,
      to = reverse ? x1 : x2,
      sign = to > from ? 1 : -1,
      end = to - sign * 7;
    return `<path class="line" d="M${from} ${y}H${end}"/><path class="line" d="M${to - sign * 7} ${y - 4}L${to} ${y}L${to - sign * 7} ${y + 4}"/>${txt(edge, (x1 + x2) / 2, y - 25, Math.abs(x2 - x1) - 32, "edge", 3)}`;
  }
  function arrowV(y1, y2, x, edge) {
    const sign = y2 > y1 ? 1 : -1,
      end = y2 - sign * 7;
    return `<path class="line" d="M${x} ${y1}V${end}"/><path class="line" d="M${x - 4} ${y2 - sign * 7}L${x} ${y2}L${x + 4} ${y2 - sign * 7}"/>${txt(edge, x + 18, (y1 + y2) / 2 + 3, 120, "edge", 2, "start")}`;
  }

  // A thumbnail may scale a diagram, but must not remove facts or change its
  // relationships. Keep compact as a compatibility option for saved files.
  function compactDiagram(id, data, opts) {
    return C[id](data, { ...opts, compact: false });
  }

  function narrowSvg(body, opts, height = 400, data = {}) {
    return svgMarkup(body, opts, `0 0 400 ${height}`, "narrow-viz", data);
  }

  function narrowDiagram(id, data, opts) {
    let body = "";
    let height = 400;

    if (id === "flow") {
      const boxH = 68,
        gap = opts.density === "compact" ? 48 : 62;
      height =
        28 + data.stages.length * boxH + (data.stages.length - 1) * gap + 28;
      data.stages.forEach((stage, i) => {
        const y = 28 + i * (boxH + gap);
        body += box(40, y, 320, boxH, stage.title, stage.sub, stage);
        if (i < data.stages.length - 1)
          body += arrowV(y + boxH + 6, y + boxH + gap - 6, 200, data.edges[i]);
      });
    } else if (id === "timeline") {
      const boxH = 66,
        step = 94;
      height = 24 + (data.marks.length - 1) * step + boxH + 24;
      body = `<path class="line" d="M104 38V${height - 38}M100 ${height - 45}L104 ${height - 38}L108 ${height - 45}"/>`;
      data.marks.forEach((mark, i) => {
        const y = 24 + i * step,
          cy = y + boxH / 2,
          active = i === data.nowAt;
        body += `${txt(mark.when, 84, cy + 4, 70, active ? "zone" : "edge", 1, "end")}<circle class="${active ? "box accent" : "box"}"${accentAttr(active ? "active" : undefined)} cx="104" cy="${cy}" r="4.5"/>${box(122, y, 256, boxH, mark.title, mark.sub, { accentRole: active ? "active" : undefined, detail: mark.detail })}`;
      });
    } else if (id === "hierarchy") {
      const gap = opts.density === "compact" ? 28 : 62,
        branchHeights = data.branches.map((branch) => {
          const leaves = branch.leaves || [];
          if (!leaves.length) return branch.sub ? 82 : 70;
          return branch.sub ? 122 : 104;
        });
      height =
        26 +
        68 +
        data.branches.reduce(
          (total, _branch, index) => total + gap + branchHeights[index],
          0,
        ) +
        24;
      body = box(142, 26, 236, 68, data.root, data.rootSub, {
        detail: data.rootDetail,
      });
      const lastBranchY =
        26 +
        68 +
        branchHeights.slice(0, -1).reduce((sum, h) => sum + h + gap, 0) +
        gap;
      // Each sibling is connected to the root's shared bus. A vertical row
      // must never turn a parent/child tree into a chain of dependencies.
      body += `<path class="line hierarchy-root-bus" d="M142 60H24V${lastBranchY + branchHeights.at(-1) / 2}"/>`;
      let branchY = 26 + 68 + gap;
      data.branches.forEach((branch, i) => {
        const branchH = branchHeights[i],
          leaves = branch.leaves || [],
          titleY =
            branchY + (branch.sub ? 28 : leaves.length ? 31 : branchH / 2 + 4),
          subY = branchY + 51;
        let branchMarkup = `<rect class="box${branch.accentRole ? " accent" : ""}"${accentAttr(branch.accentRole)} x="142" y="${branchY}" width="236" height="${branchH}" rx="7"/>${txt(branch.title, 260, titleY, 202, "", 2)}${branch.sub ? txt(branch.sub, 260, subY, 202, "sub", 2) : ""}`;
        let leafTargets = "";
        if (leaves.length) {
          const leafW = (216 - 10 * (leaves.length - 1)) / leaves.length,
            leafY = branchY + (branch.sub ? 70 : 52),
            leafH = branchH - (leafY - branchY) - 10;
          leaves.forEach((leaf, j) => {
            const leafMarkup = hierarchyLeaf(
              152 + j * (leafW + 10),
              leafY,
              leafW,
              leafH,
              leaf,
            );
            if (leaf.detail) leafTargets += leafMarkup;
            else branchMarkup += leafMarkup;
          });
        }
        body +=
          `<g class="hierarchy-branch-link" data-parent="root" data-branch="${i}">${arrowH(24, 134, branchY + branchH / 2, branch.edge)}</g>` +
          detailWrap(branchMarkup, branch.detail) +
          leafTargets;
        branchY += branchH + gap;
      });
    } else if (id === "zones") {
      const zones = [
        { title: data.zoneA, items: data.itemsA },
        { title: data.zoneB, items: data.itemsB },
      ];
      const itemHeight = (item) => {
        const titleRows = lines(item.title, 51, 2).length;
        const subRows = item.sub ? lines(item.sub, 51, 3).length : 0;
        return Math.max(
          54,
          26 + titleRows * 14 + (subRows ? 7 + subRows * 14 : 0),
        );
      };
      const itemGap = 12;
      const zoneHeights = zones.map(
        (zone) =>
          42 +
          zone.items.reduce((sum, item) => sum + itemHeight(item), 0) +
          Math.max(0, zone.items.length - 1) * itemGap +
          14,
      );
      const gap = 70;
      height = 18 + zoneHeights[0] + gap + zoneHeights[1] + 18;
      let y = 18;
      zones.forEach((zone, zi) => {
        const zh = zoneHeights[zi];
        body += `<rect class="band" x="16" y="${y}" width="368" height="${zh}" rx="4"/>${txt(zone.title, 30, y + 25, 330, "zone surface-text", 1, "start")}`;
        let itemY = y + 42;
        zone.items.forEach((item) => {
          const h = itemHeight(item);
          body += box(30, itemY, 340, h, item.title, item.sub, item);
          itemY += h + itemGap;
        });
        if (!zi)
          body += arrowV(y + zh + 8, y + zh + gap - 8, 200, data.crossing);
        y += zh + gap;
      });
    } else if (id === "beforeAfter") {
      height = 350;
      body =
        box(40, 16, 320, 110, data.beforeLabel, data.beforeSub, {}) +
        arrowV(136, 214, 200, data.edge) +
        box(40, 224, 320, 110, data.afterLabel, data.afterSub);
    } else if (id === "hub") {
      const rowH = 58,
        gap = 12;
      height =
        24 +
        70 +
        30 +
        data.spokes.length * rowH +
        (data.spokes.length - 1) * gap +
        24;
      body = box(100, 24, 200, 70, data.centre);
      // All satellites join the same continuous bus. Splitting the bus at
      // each row leaves later spokes disconnected from the central node.
      body += `<path class="hair hub-bus" d="M200 94V${124 + (data.spokes.length - 1) * (rowH + gap) + rowH / 2}"/>`;
      data.spokes.forEach((spoke, i) => {
        const y = 124 + i * (rowH + gap),
          cy = y + rowH / 2,
          towardCentre = data.toward === "centre",
          arrowTip = towardCentre ? 200 : 218,
          arrowBase = towardCentre ? 207 : 211;
        body += `<path class="line" d="M200 ${cy}H218"/><path class="line hub-arrowhead" data-direction="${towardCentre ? "centre" : "satellites"}" d="M${arrowBase} ${cy - 4}L${arrowTip} ${cy}L${arrowBase} ${cy + 4}"/>${txt(spoke.edge, 190, cy + 4, 154, "edge", 1, "end")}${box(224, y, 154, rowH, spoke.title, "", { detail: spoke.detail })}`;
      });
    } else if (id === "compareTwo") {
      const rowH = 102;
      height = 70 + data.rows.length * rowH + 20;
      body = `${detailWrap(txt(data.a, 112, 36, 168, "zone", 1), data.aDetail, [20, 8, 168, 44])}${detailWrap(txt(data.b, 288, 36, 168, "zone", 1), data.bDetail, [212, 8, 168, 44])}`;
      data.rows.forEach((row, i) => {
        const y = 62 + i * rowH;
        body += `${txt(row.label, 20, y + 14, 360, "zone", 1, "start")}${box(20, y + 26, 168, 62, row.a, "", { tint: data.highlight === "a", titleClass: row.winner === "a" ? "zone" : "" })}${box(212, y + 26, 168, 62, row.b, "", { tint: data.highlight === "b", titleClass: row.winner === "b" ? "zone" : "" })}`;
      });
    } else if (id === "funnel") {
      const widths = data.tiers.length === 3 ? [352, 260, 168] : [332, 206],
        boxH = 70,
        gap = 58;
      height =
        24 + data.tiers.length * boxH + (data.tiers.length - 1) * gap + 82;
      data.tiers.forEach((tier, i) => {
        const w = widths[i],
          x = 200 - w / 2,
          y = 24 + i * (boxH + gap);
        if (i) body += arrowV(y - gap + 6, y - 6, 200, data.edges[i - 1]);
        body += box(x, y, w, boxH, tier.title, tier.sub, {
          detail: tier.detail,
        });
      });
      body +=
        arrowV(height - 72, height - 44, 200, "") +
        txt(data.outcome, 200, height - 18, 340, "zone", 2);
    } else if (id === "loop") {
      const boxH = 58,
        gap = 50;
      height =
        70 + data.steps.length * boxH + (data.steps.length - 1) * gap + 54;
      body = txt(data.centre, 200, 30, 340, "zone", 2);
      data.steps.forEach((step, i) => {
        const y = 58 + i * (boxH + gap);
        body += box(78, y, 284, boxH, step.title, "", {
          detail: step.detail,
        });
        if (i < data.steps.length - 1)
          body += arrowV(y + boxH + 6, y + boxH + gap - 6, 220, step.edge);
      });
      const lastY = 58 + (data.steps.length - 1) * (boxH + gap);
      body += `<path class="line loop-return" d="M78 ${lastY + boxH / 2}H38V${58 + boxH / 2}H71M64 ${58 + boxH / 2 - 4}L71 ${58 + boxH / 2}L64 ${58 + boxH / 2 + 4}"/><g transform="translate(18 ${(lastY + 58 + boxH) / 2}) rotate(-90)">${txt(data.steps.at(-1).edge, 0, 0, 220, "edge", 1)}</g>`;
    } else if (id === "matrix") {
      height = 430;
      const x = 20,
        y = 36,
        size = 360,
        half = 180;
      data.quads.forEach((quad, i) => {
        const qx = x + (i % 2) * half,
          qy = y + Math.floor(i / 2) * half;
        body += detailWrap(
          `<rect class="${i === data.markAt ? "band" : "box"}" x="${qx}" y="${qy}" width="${half}" height="${half}"/>${txt(quad.title, qx + half / 2, qy + half / 2 - (quad.sub ? 12 : 0), half - 26, "zone", 2)}${quad.sub ? txt(quad.sub, qx + half / 2, qy + half / 2 + 22, half - 28, "sub", 3) : ""}`,
          quad.detail,
        );
      });
      body += `${txt(data.yAxis, 20, 22, 170, "zone", 1, "start")}${txt(data.xAxis, 380, 418, 170, "zone", 1, "end")}`;
    } else if (id === "figurePath") {
      height = 430;
      const spots = [
        [82, 330],
        [174, 236],
        [266, 142],
      ];
      body = `<path class="line" d="M46 374C88 356 105 304 150 268S220 204 250 164S300 104 350 64M343 60L350 64L346 72"/>${txt(data.goal, 346, 42, 112, "zone", 1, "end")}`;
      data.marks.forEach((mark, i) => {
        body += `<circle class="inkfill" cx="${spots[i][0]}" cy="${spots[i][1]}" r="5"/>${txt(mark, spots[i][0] + 13, spots[i][1] + 4, 108, "", 1, "start")}`;
      });
      body += txt(data.caption, 200, 408, 350, "sub", 2);
    } else if (id === "stairSteps") {
      height = 430;
      const tread = 344 / data.steps.length,
        rise = 48,
        x0 = 28,
        y0 = 354;
      data.steps.forEach((step, i) => {
        const x = x0 + i * tread,
          y = y0 - i * rise;
        body += `<path class="line" d="M${x} ${i ? y + rise : y}V${y}H${x + tread}"/>${txt(step.title, x + tread / 2, y - (step.sub ? 30 : 11), tread - 10, "", 2)}${step.sub ? txt(step.sub, x + tread / 2, y - 9, tread - 10, "sub", 1) : ""}`;
      });
      if (data.caption) body += txt(data.caption, 200, 410, 350, "sub", 2);
    } else if (id === "pyramid") {
      height = 430;
      const baseY = 342,
        blockH = 48;
      data.blocks.forEach((block, i) => {
        const w = 352 - i * 48,
          x = 200 - w / 2,
          y = baseY - i * (blockH + 4);
        body += detailWrap(
          `<rect class="band" x="${x}" y="${y}" width="${w}" height="${blockH}" rx="3"/>${txt(block.title, 200, y + (block.sub ? 19 : 29), w - 12, "", 1)}${block.sub ? txt(block.sub, 200, y + 36, w - 12, "sub", 1) : ""}`,
        );
      });
      if (data.caption) body += txt(data.caption, 200, 408, 350, "sub", 2);
    } else if (id === "gears") {
      height = 430;
      const gs = [
        { cx: 106, cy: 210, r: 42, t: 10 },
        { cx: 200, cy: 158, r: 61, t: 14 },
        { cx: 302, cy: 222, r: 46, t: 11 },
      ];
      [1, 0, 2].forEach((i) => {
        const gear = gs[i];
        body += `<path class="${i === 2 ? "inkfill" : "box"}" d="${gearPath(gear.cx, gear.cy, gear.r, gear.t, i * 0.22)}"/><circle class="line" cx="${gear.cx}" cy="${gear.cy}" r="${(gear.r * 0.22).toFixed(2)}"/>`;
      });
      const labels = [
        [106, 292],
        [200, 70],
        [302, 310],
      ];
      data.labels.forEach((value, i) => {
        body += txt(value, labels[i][0], labels[i][1], 116, "", 2);
      });
      if (data.caption) body += txt(data.caption, 200, 404, 350, "sub", 2);
    }

    return narrowSvg(body, opts, height, data);
  }

  C.flow = (params, opts) => {
    var data = params;
    opts = opts || {};
    known(data, "params", ["stages", "edges"]);
    const stages = records(
      data.stages,
      "stages",
      2,
      6,
      ["title"],
      ["sub", "detail", "accentRole"],
    );
    const edges = strings(data.edges, "edges", stages.length - 1);
    if (opts.compact) return compactDiagram("flow", data, opts);
    if (opts.narrow === true) return narrowDiagram("flow", data, opts);
    let body = "";
    if (stages.length <= 4) {
      const gap = stages.length === 2 ? 160 : stages.length === 3 ? 148 : 116,
        w = (832 - gap * (stages.length - 1)) / stages.length,
        y = 88,
        h = 178;
      stages.forEach((s, i) => {
        const x = 24 + i * (w + gap);
        body += box(x, y, w, h, s.title, s.sub, {
          accentRole: s.accentRole,
          detail: s.detail,
        });
        if (i < stages.length - 1)
          body += arrowH(x + w + 8, x + w + gap - 8, y + h / 2, edges[i]);
      });
    } else {
      const w = 200,
        h = 112,
        xs = [22, 340, 658],
        top = 44,
        bottom = 218,
        positions = [
          [xs[0], top],
          [xs[1], top],
          [xs[2], top],
          [xs[2], bottom],
          [xs[1], bottom],
          [xs[0], bottom],
        ];
      body += arrowH(xs[0] + w + 8, xs[1] - 8, top + h / 2, edges[0]);
      body += arrowH(xs[1] + w + 8, xs[2] - 8, top + h / 2, edges[1]);
      body += arrowV(top + h + 8, bottom - 8, xs[2] + w / 2, edges[2]);
      body += arrowH(xs[1] + w + 8, xs[2] - 8, bottom + h / 2, edges[3], true);
      if (stages.length === 6)
        body += arrowH(
          xs[0] + w + 8,
          xs[1] - 8,
          bottom + h / 2,
          edges[4],
          true,
        );
      stages.forEach((stage, i) => {
        const [x, y] = positions[i];
        body += flowCard(x, y, w, h, stage);
      });
    }
    return svg(body, opts, "wide", data);
  };

  C.timeline = (params, opts) => {
    var data = params;
    opts = opts || {};
    known(data, "params", ["marks", "nowAt", "orientation"]);
    choice(data.orientation, "orientation", ["horizontal", "vertical"], true);
    const marks = records(
      data.marks,
      "marks",
      3,
      5,
      ["when", "title"],
      ["sub", "detail"],
    );
    if (
      data.nowAt != null &&
      (!Number.isInteger(data.nowAt) ||
        data.nowAt < 0 ||
        data.nowAt >= marks.length)
    )
      throw new TypeError("nowAt must index marks");
    if (data.orientation === "vertical") {
      // This rail encodes order, not elapsed duration. Event depth comes from
      // its prose, so no text is dropped or forced into equally sized boxes.
      const wrap = (value, columns) => lines(value || "", columns, Infinity);
      const blocks = marks.map((mark) => ({
        when: wrap(mark.when, 43),
        title: wrap(mark.title, 37),
        sub: mark.sub ? wrap(mark.sub, 43) : [],
      }));
      let cursor = 26;
      const positions = blocks.map((block) => {
        const y = cursor;
        const height =
          block.when.length * 16 +
          8 +
          block.title.length * 18 +
          (block.sub.length ? 8 + block.sub.length * 17 : 0);
        cursor += height + 28;
        return { y, height };
      });
      const writeLines = (rows, y, cls, leading) =>
        `<text class="${cls}" x="58" y="${y}" text-anchor="start">${rows.map((row, index) => `<tspan x="58" dy="${index ? leading : 0}">${esc(row)}</tspan>`).join("")}</text>`;
      let body = `<style>.timeline-vertical .timeline-hit{fill:transparent;stroke:none}.timeline-vertical .timeline-dot{fill:var(--card);stroke:var(--muted);stroke-width:1.5;vector-effect:non-scaling-stroke}.timeline-vertical .timeline-dot[data-accent-role="active"]{fill:var(--red);stroke:var(--red)}</style><path class="line timeline-rail" d="M28 ${positions[0].y + 5}V${positions.at(-1).y + 5}"/>`;
      blocks.forEach((block, index) => {
        const { y, height } = positions[index];
        const titleY = y + block.when.length * 16 + 8;
        const subY = titleY + block.title.length * 18 + 8;
        const content = `<rect class="timeline-hit" x="12" y="${y - 10}" width="376" height="${height + 14}" rx="4"/><circle class="timeline-dot"${accentAttr(index === data.nowAt ? "active" : undefined)} cx="28" cy="${y + 5}" r="4.5"/>${writeLines(block.when, y + 9, "edge", 16)}${writeLines(block.title, titleY + 11, "", 18)}${block.sub.length ? writeLines(block.sub, subY + 9, "sub", 17) : ""}`;
        body += detailWrap(content, marks[index].detail).replace(
          "<g data-diagram-object",
          `<g data-timeline-event="${index}" data-diagram-object`,
        );
      });
      return svgMarkup(
        body,
        opts,
        `0 0 400 ${cursor - 12}`,
        "timeline-vertical",
        data,
      );
    }
    if (opts.compact) return compactDiagram("timeline", data, opts);
    if (opts.narrow === true) return narrowDiagram("timeline", data, opts);
    const n = marks.length,
      gap = 24,
      w = (828 - gap * (n - 1)) / n,
      axisY = 262;
    let body = `<path class="line" d="M22 ${axisY}H858"/><path class="line" d="M851 ${axisY - 4}L858 ${axisY}L851 ${axisY + 4}"/>`;
    marks.forEach((m, i) => {
      const x = 26 + i * (w + gap),
        cx = x + w / 2,
        now = i === data.nowAt;
      body +=
        box(x, 54, w, 140, m.title, m.sub, {
          accentRole: now ? "active" : undefined,
          detail: m.detail,
        }) +
        `<path class="hair" d="M${cx} 194V257"/><circle class="${now ? "box accent" : "box"}"${accentAttr(now ? "active" : undefined)} cx="${cx}" cy="${axisY}" r="4.5"/>${txt(m.when, cx, 292, w, now ? "zone" : "edge", 1)}`;
    });
    return svg(body, opts, "wide", data);
  };

  C.hierarchy = (params, opts) => {
    var data = params;
    opts = opts || {};
    known(data, "params", ["root", "rootSub", "rootDetail", "branches"]);
    text(data.root, "root");
    text(data.rootSub, "rootSub", true);
    text(data.rootDetail, "rootDetail", true);
    const branches = records(
      data.branches,
      "branches",
      2,
      4,
      ["title", "edge"],
      ["sub", "detail", "leaves", "accentRole"],
    );
    branches.forEach((b, i) => {
      if (b.leaves != null) {
        list(b.leaves, `branches[${i}].leaves`, 1, 2);
        b.leaves.forEach((leaf, j) => {
          known(leaf, `branches[${i}].leaves[${j}]`, ["title", "detail"]);
          text(leaf.title, `branches[${i}].leaves[${j}].title`);
          text(leaf.detail, `branches[${i}].leaves[${j}].detail`, true);
        });
      }
    });
    if (opts.compact) return compactDiagram("hierarchy", data, opts);
    if (opts.narrow === true) return narrowDiagram("hierarchy", data, opts);
    const rootW = 300,
      rootX = 290,
      rootY = 18,
      rootH = 82,
      gap = 24,
      branchY = 204,
      branchH = 156,
      bw = (820 - gap * (branches.length - 1)) / branches.length;
    let body = "";
    branches.forEach((b, i) => {
      const x = 30 + i * (bw + gap),
        cx = x + bw / 2;
      // Break only around the label's ink: a few points of clear space on
      // either side keeps the relationship continuous without drawing through it.
      body += `<path class="line hierarchy-connector" data-label-y="160" d="M440 ${rootY + rootH}V124H${cx}V147M${cx} 166V${branchY - 8}"/><path class="line" d="M${cx - 4} ${branchY - 15}L${cx} ${branchY - 8}L${cx + 4} ${branchY - 15}"/>${txt(b.edge, cx, 160, bw - 8, "edge", 1)}`;
    });
    body += box(rootX, rootY, rootW, rootH, data.root, data.rootSub, {
      detail: data.rootDetail,
    });
    branches.forEach((branch, i) => {
      const x = 30 + i * (bw + gap),
        leaves = branch.leaves || [],
        rowY = branchY + 84,
        rowGap = 8,
        rowW =
          (bw - 32 - rowGap * Math.max(0, leaves.length - 1)) /
          Math.max(1, leaves.length),
        titleY = leaves.length
          ? branchY + (branch.sub ? 35 : 50)
          : branchY + branchH / 2 - (branch.sub ? 8 : 0),
        subY = leaves.length ? branchY + 61 : branchY + branchH / 2 + 19;
      let markup = `<rect class="box${branch.accentRole ? " accent" : ""}"${accentAttr(branch.accentRole)} x="${x}" y="${branchY}" width="${bw}" height="${branchH}" rx="7"/>${txt(branch.title, x + bw / 2, titleY, bw - 34, "", 2)}${branch.sub ? txt(branch.sub, x + bw / 2, subY, bw - 34, "sub", 2) : ""}`;
      let leafTargets = "";
      leaves.forEach((leaf, j) => {
        const lx = x + 16 + j * (rowW + rowGap),
          leafMarkup = hierarchyLeaf(lx, rowY, rowW, 45, leaf);
        // A leaf with its own popup is a sibling target. A leaf without one
        // belongs to the branch's single clickable surface and never gets a
        // separate hover/focus outline.
        if (leaf.detail) leafTargets += leafMarkup;
        else markup += leafMarkup;
      });
      body += detailWrap(markup, branch.detail) + leafTargets;
    });
    return svg(body, opts, "wideTall", data);
  };

  C.zones = (params, opts) => {
    var data = params;
    opts = opts || {};
    known(data, "params", ["zoneA", "itemsA", "zoneB", "itemsB", "crossing"]);
    text(data.zoneA, "zoneA");
    text(data.zoneB, "zoneB");
    text(data.crossing, "crossing");
    const a = records(
        data.itemsA,
        "itemsA",
        1,
        3,
        ["title"],
        ["sub", "detail"],
      ),
      b = records(data.itemsB, "itemsB", 1, 3, ["title"], ["sub", "detail"]);
    if (opts.compact) return compactDiagram("zones", data, opts);
    if (opts.narrow === true) return narrowDiagram("zones", data, opts);
    let body = `<rect class="band" x="14" y="10" width="852" height="150" rx="4"/>${txt(data.zoneA, 34, 32, 300, "zone surface-text", 1, "start")}<rect class="band" x="14" y="242" width="852" height="168" rx="4"/>${txt(data.zoneB, 34, 266, 300, "zone surface-text", 1, "start")}`;
    const row = (items, y) => {
      const gap = 34,
        w = (804 - gap * (items.length - 1)) / items.length;
      return items
        .map((v, i) =>
          box(38 + i * (w + gap), y, w, 90, v.title, v.sub, {
            detail: v.detail,
          }),
        )
        .join("");
    };
    body += row(a, 52) + row(b, 286) + arrowV(160, 242, 440, data.crossing);
    return svg(body, opts, "wideTall", data);
  };

  C.beforeAfter = (params, opts) => {
    var data = params;
    opts = opts || {};
    known(data, "params", [
      "orientation",
      "beforeLabel",
      "beforeSub",
      "edge",
      "afterLabel",
      "afterSub",
    ]);
    ["beforeLabel", "beforeSub", "edge", "afterLabel", "afterSub"].forEach(
      (k) => {
        text(data[k], k);
      },
    );
    choice(data.orientation, "orientation", ["vertical", "horizontal"], true);
    if (opts.compact) return compactDiagram("beforeAfter", data, opts);
    if (opts.narrow === true) return narrowDiagram("beforeAfter", data, opts);
    const horizontal = data.orientation === "horizontal";
    let body = "";
    if (horizontal) {
      body +=
        box(12, 70, 126, 220, data.beforeLabel, data.beforeSub, {}) +
        arrowH(150, 250, 180, data.edge) +
        box(262, 70, 126, 220, data.afterLabel, data.afterSub);
    } else {
      body +=
        box(100, 18, 200, 104, data.beforeLabel, data.beforeSub, {}) +
        arrowV(150, 220, 200, data.edge) +
        box(100, 248, 200, 104, data.afterLabel, data.afterSub);
    }
    return svg(body, opts, "pane", data);
  };

  C.hub = (params, opts) => {
    var data = params;
    opts = opts || {};
    known(data, "params", ["centre", "spokes", "toward"]);
    text(data.centre, "centre");
    choice(data.toward, "toward", ["centre", "satellites"]);
    const spokes = records(
      data.spokes,
      "spokes",
      3,
      6,
      ["title", "edge"],
      ["detail"],
    );
    if (opts.compact) return compactDiagram("hub", data, opts);
    if (opts.narrow === true) return narrowDiagram("hub", data, opts);
    // Aligned spokes preserve direction and leave every relationship label
    // beside its own connector; radial labels otherwise drift onto neighbours.
    const rowH = 64,
      gap = 24,
      height = 48 + spokes.length * rowH + (spokes.length - 1) * gap;
    const centerY = height / 2;
    let body = box(24, centerY - 48, 180, 96, data.centre);
    body += `<path class="line" d="M204 ${centerY}H236M236 56V${24 + (spokes.length - 1) * (rowH + gap) + rowH / 2}"/>`;
    spokes.forEach((spoke, i) => {
      const y = 24 + i * (rowH + gap),
        cy = y + rowH / 2;
      body += `<g data-relationship="spoke" data-direction="${data.toward}">${arrowH(236, 622, cy, spoke.edge, data.toward === "centre")}</g>`;
      body += box(630, y, 226, rowH, spoke.title, "", { detail: spoke.detail });
    });
    return svgMarkup(body, opts, `0 0 880 ${height}`, "", data);
  };

  C.compareTwo = (params, opts) => {
    var data = params;
    opts = opts || {};
    known(data, "params", [
      "a",
      "b",
      "aDetail",
      "bDetail",
      "highlight",
      "rows",
    ]);
    text(data.a, "a");
    text(data.b, "b");
    text(data.aDetail, "aDetail", true);
    text(data.bDetail, "bDetail", true);
    choice(data.highlight, "highlight", ["a", "b", "none"]);
    const rows = records(data.rows, "rows", 2, 4, [
      "label",
      "a",
      "b",
      "winner",
    ]);
    rows.forEach((r, i) => {
      choice(r.winner, `rows[${i}].winner`, ["a", "b", "none"]);
    });
    if (opts.compact) return compactDiagram("compareTwo", data, opts);
    if (opts.narrow === true) return narrowDiagram("compareTwo", data, opts);
    const x = [24, 216, 536, 856],
      head = 58,
      rowH = 68,
      tableH = head + rowH * rows.length,
      top = (360 - tableH) / 2,
      bottom = top + tableH;
    let body = "";
    if (data.highlight === "a")
      body += `<rect class="band" x="${x[1]}" y="${top}" width="${x[2] - x[1]}" height="${tableH}"/>`;
    if (data.highlight === "b")
      body += `<rect class="band" x="${x[2]}" y="${top}" width="${x[3] - x[2]}" height="${tableH}"/>`;
    body += `<path class="hair" d="M${x[0]} ${top}H${x[3]}M${x[0]} ${top + head}H${x[3]}M${x[0]} ${top}V${bottom}M${x[1]} ${top}V${bottom}M${x[2]} ${top}V${bottom}M${x[3]} ${top}V${bottom}"/>${detailWrap(txt(data.a, (x[1] + x[2]) / 2, top + 34, x[2] - x[1] - 24, data.highlight === "a" ? "zone surface-text" : "zone", 2), data.aDetail, [x[1], top, x[2] - x[1], head])}${detailWrap(txt(data.b, (x[2] + x[3]) / 2, top + 34, x[3] - x[2] - 24, data.highlight === "b" ? "zone surface-text" : "zone", 2), data.bDetail, [x[2], top, x[3] - x[2], head])}`;
    rows.forEach((r, i) => {
      const y = top + head + i * rowH,
        cy = y + rowH / 2;
      body += `<path class="hair" d="M${x[0]} ${y + rowH}H${x[3]}"/>${txt(r.label, (x[0] + x[1]) / 2, cy, x[1] - x[0] - 24, "sub", 2)}${txt(r.a, (x[1] + x[2]) / 2, cy, x[2] - x[1] - 24, `${r.winner === "a" ? "zone" : "sub"}${data.highlight === "a" ? " surface-text" : ""}`, 2)}${txt(r.b, (x[2] + x[3]) / 2, cy, x[3] - x[2] - 24, `${r.winner === "b" ? "zone" : "sub"}${data.highlight === "b" ? " surface-text" : ""}`, 2)}`;
    });
    return svg(body, opts, "wide", data);
  };

  C.funnel = (params, opts) => {
    var data = params;
    opts = opts || {};
    known(data, "params", ["tiers", "edges", "outcome"]);
    const tiers = records(
        data.tiers,
        "tiers",
        2,
        3,
        ["title"],
        ["sub", "detail"],
      ),
      edges = strings(data.edges, "edges", tiers.length - 1);
    text(data.outcome, "outcome");
    if (opts.compact) return compactDiagram("funnel", data, opts);
    if (opts.narrow === true) return narrowDiagram("funnel", data, opts);
    const widths = tiers.length === 3 ? [320, 224, 132] : [300, 168],
      ys = tiers.length === 3 ? [26, 138, 250] : [50, 190],
      h = 76;
    let body = "";
    tiers.forEach((t, i) => {
      const w = widths[i],
        x = 200 - w / 2,
        y = ys[i];
      if (i) {
        const pw = widths[i - 1],
          px = 200 - pw / 2,
          py = ys[i - 1];
        body += `<path class="hair" d="M${px + 6} ${py + h}L${x + 6} ${y}M${px + pw - 6} ${py + h}L${x + w - 6} ${y}"/>${txt(edges[i - 1], 200, (py + h + y) / 2 + 3, w + 60, "edge", 1)}`;
      }
      body += box(x, y, w, h, t.title, t.sub, { detail: t.detail });
    });
    const lastY = ys.at(-1) + h;
    body +=
      arrowV(lastY, lastY + 34, 200, "") +
      txt(data.outcome, 200, lastY + 57, 300, "zone", 2);
    return svg(body, opts, "pane", data);
  };

  C.loop = (params, opts) => {
    var data = params;
    opts = opts || {};
    known(data, "params", ["centre", "steps"]);
    text(data.centre, "centre");
    const steps = records(
      data.steps,
      "steps",
      3,
      4,
      ["title", "edge"],
      ["detail"],
    );
    if (opts.compact) return compactDiagram("loop", data, opts);
    if (opts.narrow === true) return narrowDiagram("loop", data, opts);
    const cx = 200,
      cy = 200,
      r = 130,
      ar = 84,
      bw = 120,
      bh = 50;
    let body = txt(data.centre, cx, cy + 4, 108, "zone", 3);
    steps.forEach((s, i) => {
      const a = ((-90 + (i * 360) / steps.length) * Math.PI) / 180,
        next = ((-90 + ((i + 1) * 360) / steps.length) * Math.PI) / 180,
        a0 = a + 0.34,
        a1 = next - 0.34,
        x0 = cx + ar * Math.cos(a0),
        y0 = cy + ar * Math.sin(a0),
        x1 = cx + ar * Math.cos(a1),
        y1 = cy + ar * Math.sin(a1),
        endA = a1 - 0.09,
        xe = cx + ar * Math.cos(endA),
        ye = cy + ar * Math.sin(endA);
      body += `<path class="line" d="M${x0} ${y0}A${ar} ${ar} 0 0 1 ${xe} ${ye}"/><path class="line" d="M${x1} ${y1}l${-8 * Math.cos(a1 - 0.55)} ${-8 * Math.sin(a1 - 0.55)}M${x1} ${y1}l${-8 * Math.cos(a1 + 0.55)} ${-8 * Math.sin(a1 + 0.55)}"/>${txt(s.edge, cx + (ar + 24) * Math.cos((a0 + a1) / 2), cy + (ar + 24) * Math.sin((a0 + a1) / 2), 80, "edge", 1)}${box(cx + r * Math.cos(a) - bw / 2, cy + r * Math.sin(a) - bh / 2, bw, bh, s.title, "", { detail: s.detail })}`;
    });
    return svg(body, opts, "pane", data);
  };

  C.matrix = (params, opts) => {
    var data = params;
    opts = opts || {};
    known(data, "params", ["xAxis", "yAxis", "quads", "markAt"]);
    text(data.xAxis, "xAxis");
    text(data.yAxis, "yAxis");
    const quads = records(
      data.quads,
      "quads",
      4,
      4,
      ["title"],
      ["sub", "detail"],
    );
    if (!Number.isInteger(data.markAt) || data.markAt < 0 || data.markAt > 3)
      throw new TypeError("markAt must be an index from 0 to 3");
    if (opts.compact) return compactDiagram("matrix", data, opts);
    if (opts.narrow === true) return narrowDiagram("matrix", data, opts);
    const x = 66,
      y = 24,
      size = 304,
      half = 152;
    let body = "";
    quads.forEach((q, i) => {
      const qx = x + (i % 2) * half,
        qy = y + Math.floor(i / 2) * half;
      body += detailWrap(
        `<rect class="${i === data.markAt ? "band" : "box"}" x="${qx}" y="${qy}" width="${half}" height="${half}"/>${txt(q.title, qx + half / 2, qy + half / 2 - (q.sub ? 12 : 0), half - 24, "zone", 2)}${q.sub ? txt(q.sub, qx + half / 2, qy + half / 2 + 24, half - 32, "sub", 3) : ""}`,
        q.detail,
      );
    });
    body += `<path class="line" d="M${x} ${y}V${y + size}H${x + size}"/><path class="hair" d="M${x + half} ${y}V${y + size}M${x} ${y + half}H${x + size}"/>${txt(data.xAxis, x + half, y + size + 48, 260, "zone", 1)}<g transform="translate(12 ${y + half}) rotate(-90)">${txt(data.yAxis, 0, 0, 260, "zone", 1)}</g>`;
    return svg(body, opts, "pane", data);
  };

  C.figurePath = (params, opts) => {
    var data = params;
    opts = opts || {};
    known(data, "params", ["marks", "goal", "caption"]);
    strings(data.marks, "marks", 3);
    text(data.goal, "goal");
    text(data.caption, "caption");
    if (opts.compact) return compactDiagram("figurePath", data, opts);
    if (opts.narrow === true) return narrowDiagram("figurePath", data, opts);
    const spots = [
      [118, 309],
      [202, 241],
      [277, 135],
    ];
    let body = `<path class="line" d="M46 336C118 336 96 268 168 252S246 224 258 168S292 92 352 74"/><path class="line" d="M352 74V34"/><path class="inkfill" d="M352 34V56L386 45Z"/>${txt(data.goal, 348, 18, 96, "zone", 1)}`;
    data.marks.forEach((m, i) => {
      body += `<circle class="inkfill" cx="${spots[i][0]}" cy="${spots[i][1]}" r="5"/>${txt(m, spots[i][0] + 14, spots[i][1] + 18, 100, "edge", 1, "start")}`;
    });
    body += `<g transform="translate(326 82)"><circle class="line" cx="0" cy="-30" r="7.5"/><path class="line" d="M0 -22V-6M0 -16L-10 -9M0 -16L10 -9M0 -6L-8 6M0 -6L8 6"/></g>${txt(data.caption, 200, 374, 350, "sub", 2)}`;
    return svg(body, opts, "pane", data);
  };

  C.stairSteps = (params, opts) => {
    var data = params;
    opts = opts || {};
    known(data, "params", ["steps", "caption"]);
    const steps = records(data.steps, "steps", 3, 6, ["title"], ["sub"]);
    text(data.caption, "caption", true);
    const x0 = 34,
      y0 = 326,
      tread = 332 / steps.length,
      rise = 46;
    let body = "";
    if (opts.compact) return compactDiagram("stairSteps", data, opts);
    if (opts.narrow === true) return narrowDiagram("stairSteps", data, opts);
    steps.forEach((s, i) => {
      const x = x0 + i * tread,
        y = y0 - i * rise;
      body += `<path class="line" d="M${x} ${i ? y + rise : y}V${y}H${x + tread}"/>${txt(s.title, x + tread / 2, y - (s.sub ? 30 : 12), tread - 8, "edge", 2)}${s.sub ? txt(s.sub, x + tread / 2, y - 9, tread - 8, "sub", 1) : ""}`;
    });
    if (data.caption) body += txt(data.caption, 200, 374, 352, "sub", 2);
    return svg(body, opts, "pane", data);
  };

  C.pyramid = (params, opts) => {
    var data = params;
    opts = opts || {};
    known(data, "params", ["blocks", "caption"]);
    const blocks = records(data.blocks, "blocks", 6, 6, ["title"], ["sub"]);
    text(data.caption, "caption", true);
    const cells = [
      { x: 54, y: 286, w: 292, h: 56 },
      { x: 82, y: 226, w: 116, h: 56 },
      { x: 202, y: 226, w: 116, h: 56 },
      { x: 112, y: 166, w: 86, h: 56 },
      { x: 202, y: 166, w: 86, h: 56 },
      { x: 150, y: 58, w: 100, h: 104, tri: true },
    ];
    let body = "";
    if (opts.compact) return compactDiagram("pyramid", data, opts);
    // Preserve the saved level structure at every width. The former narrow
    // variant turned four levels into six, which changed the represented claim.
    cells.forEach((c, i) => {
      const cx = c.x + c.w / 2;
      body += c.tri
        ? `<path class="band" d="M${cx} ${c.y}L${c.x + c.w} ${c.y + c.h}H${c.x}Z"/>`
        : `<rect class="band" x="${c.x}" y="${c.y}" width="${c.w}" height="${c.h}" rx="3"/>`;
      body += txt(
        blocks[i].title,
        cx,
        c.tri ? c.y + 55 : c.y + (blocks[i].sub ? 22 : 32),
        c.w - 10,
        "edge surface-text",
        c.tri ? 2 : 1,
      );
      if (blocks[i].sub)
        body += txt(
          blocks[i].sub,
          cx,
          c.tri ? c.y + 78 : c.y + 42,
          c.w - 10,
          "sub surface-text",
          1,
        );
    });
    if (data.caption) body += txt(data.caption, 200, 374, 352, "sub", 2);
    return svg(body, opts, "pane", data);
  };

  function gearPath(cx, cy, r, teeth, phase = 0) {
    const inner = r - 3.4,
      outer = r + 3.4,
      per = (Math.PI * 2) / teeth,
      points = [];
    for (let i = 0; i < teeth; i++) {
      const a = phase + i * per;
      [
        [inner, -0.5],
        [inner, -0.28],
        [outer, -0.18],
        [outer, 0.18],
        [inner, 0.28],
        [inner, 0.5],
      ].forEach(([rad, f]) => {
        points.push([
          cx + rad * Math.cos(a + per * f),
          cy + rad * Math.sin(a + per * f),
        ]);
      });
    }
    return (
      points
        .map((p, i) => `${i ? "L" : "M"}${p[0].toFixed(1)} ${p[1].toFixed(1)}`)
        .join("") + "Z"
    );
  }
  C.gears = (params, opts) => {
    var data = params;
    opts = opts || {};
    known(data, "params", ["labels", "caption"]);
    strings(data.labels, "labels", 3);
    text(data.caption, "caption", true);
    const gs = [
      { cx: 150, cy: 186, r: 33, t: 8 },
      { cx: 236, cy: 150, r: 58, t: 14 },
      { cx: 321, cy: 203, r: 41, t: 10 },
    ];
    let body = "";
    if (opts.compact) return compactDiagram("gears", data, opts);
    if (opts.narrow === true) return narrowDiagram("gears", data, opts);
    [1, 0, 2].forEach((i) => {
      const g = gs[i],
        d = gearPath(g.cx, g.cy, g.r, g.t, i * 0.22);
      body += `<path class="${i === 2 ? "inkfill" : "box"}" d="${d}"/><circle class="line" cx="${g.cx}" cy="${g.cy}" r="${g.r * 0.22}"/>`;
    });
    const lp = [
      [150, 268],
      [236, 54],
      [321, 280],
    ];
    data.labels.forEach((v, i) => {
      body += txt(v, lp[i][0], lp[i][1], 112, "edge", 1);
    });
    body += `<circle class="line" cx="92" cy="212" r="11"/><path class="line" d="M93 223Q98 252 101 280M101 280L91 316M101 280L124 316M80 319H103M118 319H136M94 238L138 192"/><circle class="inkfill" cx="138" cy="192" r="4"/>`;
    if (data.caption) body += txt(data.caption, 200, 366, 352, "sub", 2);
    return svg(body, opts, "pane", data);
  };

  /* Charts share a 360 × 200 viewBox and 12px outer padding. Quantitative
     plots use x=54…346 and y=24…144; labels, axis titles, and the required
     source-note line occupy the reserved margins rather than the plot. */
  const CHART = Object.freeze({ left: 54, right: 346, top: 24, base: 144 });

  function number(value, name, min = -Infinity, integer = false) {
    if (
      !Number.isFinite(value) ||
      value < min ||
      (integer && !Number.isInteger(value))
    )
      throw new TypeError(
        `${name} must be ${integer ? "an integer" : "a finite number"}${min !== -Infinity ? ` at least ${min}` : ""}`,
      );
    return value;
  }
  function boolean(value, name) {
    if (value != null && typeof value !== "boolean")
      throw new TypeError(`${name} must be boolean when supplied`);
  }
  function detail(value, name) {
    text(value, name, true);
  }
  function chartParams(data, allowed) {
    known(data, "params", ["title", ...allowed]);
    text(data.title, "title", true);
    text(data.sourceNote, "sourceNote");
    return data;
  }
  function chartItem(value, name, fields, required) {
    known(value, name, fields);
    required.forEach((key) => {
      if (key === "label") text(value[key], `${name}.${key}`);
      else number(value[key], `${name}.${key}`);
    });
    if (fields.includes("open")) boolean(value.open, `${name}.open`);
    if (fields.includes("detail")) detail(value.detail, `${name}.detail`);
    return value;
  }
  function fmt(value) {
    if (Number.isInteger(value)) return String(value);
    return String(Math.round(value * 100) / 100);
  }
  function scale(value, fromMin, fromMax, toMin, toMax) {
    return (
      toMin + ((value - fromMin) / (fromMax - fromMin || 1)) * (toMax - toMin)
    );
  }
  function path(points) {
    return points
      .map(
        (point, i) =>
          `${i ? "L" : "M"}${point[0].toFixed(2)} ${point[1].toFixed(2)}`,
      )
      .join("");
  }
  function openClass(value, line = false) {
    return value && value.open === true ? (line ? " open-line" : " open") : "";
  }
  function chartLabel(
    value,
    x,
    y,
    cls = "",
    anchor = "middle",
    baseline = "auto",
  ) {
    const classes = /(^|\s)value(\s|$)/.test(cls) ? `${cls} data-label` : cls;
    return `<text class="${classes}" x="${Number(x).toFixed(2)}" y="${Number(y).toFixed(2)}" text-anchor="${anchor}" dominant-baseline="${baseline}">${esc(value)}</text>`;
  }
  function categoryLabel(value, x, y, width, anchor = "middle") {
    return txt(value, x, y, width, "muted", 2, anchor);
  }
  function unitTitle(title, unit) {
    return unit == null ? title : `${title} (${unit})`;
  }
  function chartFrame(body, data, opts, baseline = "zero") {
    const heading = data.title
      ? chartLabel(data.title, 12, 13, "chart-title", "start")
      : "";
    const source = chartLabel(data.sourceNote, 12, 195, "source", "start");
    return svgMarkup(
      `${heading}${body}${source}`,
      opts,
      "0 0 360 200",
      "chart-viz",
      data,
    ).replace(
      " class=",
      ` data-kind="chart" data-baseline="${esc(baseline)}" class=`,
    );
  }
  function axisX(title, unit, y = 177) {
    return chartLabel(unitTitle(title, unit), 200, y, "axis-title");
  }
  function axisY(title, unit, x = 11, cy = 84) {
    return `<g transform="translate(${x} ${cy}) rotate(-90)">${chartLabel(unitTitle(title, unit), 0, 0, "axis-title")}</g>`;
  }
  function niceTicks(min, max, count = 5) {
    if (min === max) max = min + 1;
    const rough = Math.abs(max - min) / Math.max(1, count - 1);
    const magnitude = 10 ** Math.floor(Math.log10(rough || 1));
    const normalized = rough / magnitude;
    const step =
      (normalized <= 1 ? 1 : normalized <= 2 ? 2 : normalized <= 5 ? 5 : 10) *
      magnitude;
    const first = Math.ceil(min / step) * step;
    const values = [];
    for (let value = first; value <= max + step * 1e-8; value += step) {
      values.push(Math.round(value * 1e10) / 1e10);
      if (values.length === 7) break;
    }
    if (!values.length || Math.abs(values[0] - min) > step * 0.1)
      values.unshift(min);
    if (values.length < 7 && Math.abs(values.at(-1) - max) > step * 0.1)
      values.push(max);
    return values.slice(0, 7);
  }
  function numericAxes(min, max, yTitle, unit, options = {}) {
    const left = options.left == null ? CHART.left : options.left,
      right = options.right == null ? CHART.right : options.right,
      top = options.top == null ? CHART.top : options.top,
      base = options.base == null ? CHART.base : options.base,
      horizontal = options.horizontal === true;
    let body = "";
    niceTicks(min, max, options.tickCount || 5).forEach((tick) => {
      if (horizontal) {
        const x = scale(tick, min, max, left, right);
        body += `<path class="faint" d="M${x.toFixed(2)} ${top}V${base}"/>${chartLabel(fmt(tick), x, base + 13, "muted")}`;
      } else {
        const y = scale(tick, min, max, base, top);
        body += `<path class="faint" d="M${left} ${y.toFixed(2)}H${right}"/>${chartLabel(fmt(tick), left - 6, y + 3.5, "muted", "end")}`;
      }
    });
    body += horizontal
      ? `<path class="hair" d="M${left} ${base}H${right}"/>`
      : `<path class="hair" d="M${left} ${top}V${base}H${right}"/>`;
    if (yTitle) body += axisY(yTitle, unit);
    return body;
  }
  function unitRungs(x, y, width, height, value, horizontal = false) {
    if (!Number.isInteger(value) || value <= 1) return "";
    let body = "";
    for (let i = 1; i < value; i++) {
      if (horizontal) {
        const xx = x + (width * i) / value;
        body += `<path class="hair" d="M${xx.toFixed(2)} ${y}V${(y + height).toFixed(2)}"/>`;
      } else {
        const yy = y + height - (height * i) / value;
        body += `<path class="hair" d="M${x} ${yy.toFixed(2)}H${(x + width).toFixed(2)}"/>`;
      }
    }
    return body;
  }
  function pointData(
    data,
    min,
    max,
    fields = ["x", "y", "label", "open", "detail"],
  ) {
    return list(data.points, "points", min, max).map((value, i) => {
      chartItem(value, `points[${i}]`, fields, ["x", "y"]);
      if (value.label != null) text(value.label, `points[${i}].label`, true);
      return value;
    });
  }
  function labeledData(data, key, min, max, numberKeys, fields) {
    return list(data[key], key, min, max).map((value, i) => {
      chartItem(value, `${key}[${i}]`, fields, ["label", ...numberKeys]);
      numberKeys.forEach((name) => {
        number(value[name], `${key}[${i}].${name}`, 0);
      });
      return value;
    });
  }
  function wrapDetail(markup, item) {
    return detailWrap(markup, item && item.detail);
  }
  function labelBoxes(points) {
    const occupied = [];
    return points.map((point, index) => {
      const value = point.label || fmt(point.y),
        width = Math.max(24, value.length * 6.2),
        height = 13,
        candidates = [
          [8, -8, "start"],
          [-8, -8, "end"],
          [8, 14, "start"],
          [-8, 14, "end"],
          [0, -13, "middle"],
          [0, 18, "middle"],
        ];
      let chosen = candidates[index % candidates.length];
      for (const candidate of candidates) {
        const left =
            point.px +
            candidate[0] -
            (candidate[2] === "end"
              ? width
              : candidate[2] === "middle"
                ? width / 2
                : 0),
          top = point.py + candidate[1] - height;
        const box = { left, top, right: left + width, bottom: top + height };
        if (
          left >= CHART.left &&
          box.right <= CHART.right &&
          top >= CHART.top - 10 &&
          box.bottom <= CHART.base &&
          !occupied.some(
            (other) =>
              box.left < other.right + 2 &&
              box.right + 2 > other.left &&
              box.top < other.bottom + 2 &&
              box.bottom + 2 > other.top,
          )
        ) {
          occupied.push(box);
          chosen = candidate;
          break;
        }
      }
      return chartLabel(
        value,
        point.px + chosen[0],
        point.py + chosen[1],
        "value",
        chosen[2],
      );
    });
  }

  C.rungBars = (params, opts) => {
    const data = chartParams(params, [
        "items",
        "unit",
        "yAxisTitle",
        "sourceNote",
      ]),
      items = labeledData(
        data,
        "items",
        2,
        8,
        ["value"],
        ["label", "value", "open", "detail"],
      );
    opts = opts || {};
    text(data.unit, "unit");
    text(data.yAxisTitle, "yAxisTitle");
    const max = Math.max(1, ...items.map((item) => item.value)),
      slot = (CHART.right - CHART.left) / items.length,
      width = Math.min(34, slot - 6);
    let body = numericAxes(0, max, data.yAxisTitle, data.unit);
    items.forEach((item, i) => {
      const height = scale(item.value, 0, max, 0, CHART.base - CHART.top),
        x = CHART.left + i * slot + (slot - width) / 2,
        y = CHART.base - height;
      body += wrapDetail(
        `<rect class="fill${openClass(item)}" x="${x.toFixed(2)}" y="${y.toFixed(2)}" width="${width.toFixed(2)}" height="${height.toFixed(2)}"/>${unitRungs(x, y, width, height, item.value)}${chartLabel(fmt(item.value), x + width / 2, Math.max(20, y - 5), "value")}${categoryLabel(item.label, x + width / 2, 158, slot - 2)}`,
        item,
      );
    });
    return chartFrame(body, data, opts, "zero");
  };

  C.hairlineLine = (params, opts) => {
    const data = chartParams(params, [
        "points",
        "xLabels",
        "xAxisTitle",
        "yAxisTitle",
        "unit",
        "baselineZero",
        "sourceNote",
      ]),
      points = pointData(data, 3, 30);
    opts = opts || {};
    strings(data.xLabels, "xLabels", points.length);
    ["xAxisTitle", "yAxisTitle", "unit"].forEach((key) => {
      text(data[key], key);
    });
    choice(data.baselineZero, "baselineZero", ["yes", "no"], true);
    const xmin = Math.min(...points.map((point) => point.x)),
      xmax = Math.max(...points.map((point) => point.x)),
      observedMin = Math.min(...points.map((point) => point.y)),
      ymin =
        data.baselineZero === "no" ? observedMin : Math.min(0, observedMin),
      ymax = Math.max(...points.map((point) => point.y), ymin + 1),
      xy = points.map((point) => ({
        ...point,
        px: scale(point.x, xmin, xmax, CHART.left, CHART.right),
        py: scale(point.y, ymin, ymax, CHART.base, CHART.top),
      }));
    let body = numericAxes(ymin, ymax, data.yAxisTitle, data.unit);
    body += `<path class="line" d="${path(xy.map((point) => [point.px, point.py]))}"/>`;
    xy.forEach((point) => {
      body += wrapDetail(
        `<circle class="tone-2${openClass(point)}" cx="${point.px.toFixed(2)}" cy="${point.py.toFixed(2)}" r="3"/>`,
        point,
      );
    });
    if (points.length <= 12) body += labelBoxes(xy).join("");
    const stride = Math.max(1, Math.ceil(points.length / 7));
    data.xLabels.forEach((value, i) => {
      if (i % stride === 0 || i === points.length - 1)
        body += chartLabel(value, xy[i].px, 157, "muted");
    });
    body += axisX(data.xAxisTitle);
    return chartFrame(
      body,
      data,
      opts,
      data.baselineZero === "no" ? "observed-min" : "zero",
    );
  };

  C.hairlineArea = (params, opts) => {
    const data = chartParams(params, [
        "points",
        "xLabels",
        "xAxisTitle",
        "yAxisTitle",
        "unit",
        "sourceNote",
      ]),
      points = pointData(data, 5, 60, ["x", "y", "label", "open"]);
    opts = opts || {};
    strings(data.xLabels, "xLabels", 2, 12);
    ["xAxisTitle", "yAxisTitle", "unit"].forEach((key) => {
      text(data[key], key);
    });
    const xmin = Math.min(...points.map((point) => point.x)),
      xmax = Math.max(...points.map((point) => point.x)),
      ymax = Math.max(1, ...points.map((point) => point.y)),
      xy = points.map((point) => ({
        ...point,
        px: scale(point.x, xmin, xmax, CHART.left, CHART.right),
        py: scale(point.y, 0, ymax, CHART.base, CHART.top),
      }));
    let body = numericAxes(0, ymax, data.yAxisTitle, data.unit),
      area = `${path(xy.map((point) => [point.px, point.py]))}L${CHART.right} ${CHART.base}L${CHART.left} ${CHART.base}Z`;
    body += `<path class="tone-1" d="${area}"/><path class="line" d="${path(xy.map((point) => [point.px, point.py]))}"/>`;
    xy.forEach((point) => {
      body += `<circle class="${point.open ? "open" : "tone-2"}" cx="${point.px.toFixed(2)}" cy="${point.py.toFixed(2)}" r="2.5"/>`;
    });
    body += labelBoxes(xy).join("");
    data.xLabels.forEach((value, i) => {
      body += chartLabel(
        value,
        scale(i, 0, data.xLabels.length - 1, CHART.left, CHART.right),
        157,
        "muted",
      );
    });
    body += axisX(data.xAxisTitle);
    return chartFrame(body, data, opts, "zero");
  };

  C.tickDonut = (params, opts) => {
    const data = chartParams(params, ["parts", "unit", "total", "sourceNote"]);
    opts = opts || {};
    text(data.unit, "unit");
    text(data.total, "total", true);
    const parts = list(data.parts, "parts", 2, 6).map((value, i) => {
        chartItem(
          value,
          `parts[${i}]`,
          ["label", "value", "open", "detail"],
          ["label", "value"],
        );
        number(value.value, `parts[${i}].value`, 0, true);
        return value;
      }),
      total = parts.reduce((sum, part) => sum + part.value, 0);
    if (!total || total > 120)
      throw new TypeError("parts must total from 1 to 120 ticks");
    const cx = 180,
      cy = 83,
      inner = 42,
      outer = 53;
    let body = "",
      cursor = 0;
    parts.forEach((part) => {
      let marks = "";
      const start = cursor;
      for (let i = 0; i < part.value; i++, cursor++) {
        const angle = ((cursor + 0.5) / total) * Math.PI * 2 - Math.PI / 2,
          x1 = cx + Math.cos(angle) * inner,
          y1 = cy + Math.sin(angle) * inner,
          x2 = cx + Math.cos(angle) * outer,
          y2 = cy + Math.sin(angle) * outer;
        marks += `<path class="line${openClass(part, true)}" d="M${x1.toFixed(2)} ${y1.toFixed(2)}L${x2.toFixed(2)} ${y2.toFixed(2)}"/>`;
      }
      const mid =
          ((start + part.value / 2) / total) * Math.PI * 2 - Math.PI / 2,
        lx = cx + Math.cos(mid) * 72,
        ly = cy + Math.sin(mid) * 62,
        anchor =
          Math.cos(mid) > 0.2
            ? "start"
            : Math.cos(mid) < -0.2
              ? "end"
              : "middle";
      marks += `${chartLabel(part.label, lx, ly, "muted", anchor)}${chartLabel(part.value, lx, ly + 12, "value", anchor)}`;
      body += wrapDetail(marks, part);
    });
    body += `${chartLabel(data.total || total, cx, cy - 1, "value")}${chartLabel(data.unit, cx, cy + 14, "muted")}`;
    return chartFrame(body, data, opts, "not-applicable");
  };

  C.tickRows = (params, opts) => {
    const data = chartParams(params, [
        "items",
        "unit",
        "xAxisTitle",
        "sourceNote",
      ]),
      items = labeledData(
        data,
        "items",
        2,
        8,
        ["value"],
        ["label", "value", "open", "detail"],
      );
    opts = opts || {};
    text(data.unit, "unit");
    text(data.xAxisTitle, "xAxisTitle");
    items.forEach((item, i) => {
      number(item.value, `items[${i}].value`, 0, true);
    });
    if (items.some((item) => item.value > 40))
      throw new TypeError("items[].value must be at most 40 ticks");
    const max = Math.max(1, ...items.map((item) => item.value)),
      row = 116 / items.length,
      tickGap = Math.min(7, 210 / max);
    let body = `<path class="hair" d="M112 ${CHART.top}V${CHART.base}"/>`;
    items.forEach((item, ri) => {
      const y = CHART.top + row * (ri + 0.5);
      let marks = categoryLabel(item.label, 104, y + 4, 88, "end");
      for (let i = 0; i < item.value; i++) {
        const x = 118 + i * tickGap;
        marks += `<path class="line${openClass(item, true)}" d="M${x.toFixed(2)} ${(y - 5).toFixed(2)}V${(y + 6).toFixed(2)}"/>`;
      }
      marks += chartLabel(fmt(item.value), 346, y + 4, "value", "end");
      body += wrapDetail(marks, item);
    });
    body += axisX(data.xAxisTitle, data.unit, 174);
    return chartFrame(body, data, opts, "zero");
  };

  C.pairedRungs = (params, opts) => {
    const data = chartParams(params, [
      "aLabel",
      "bLabel",
      "items",
      "unit",
      "xAxisTitle",
      "sourceNote",
    ]);
    opts = opts || {};
    ["aLabel", "bLabel", "unit", "xAxisTitle"].forEach((key) => {
      text(data[key], key);
    });
    const items = labeledData(
        data,
        "items",
        2,
        6,
        ["a", "b"],
        ["label", "a", "b", "open", "detail"],
      ),
      max = Math.max(1, ...items.flatMap((item) => [item.a, item.b])),
      row = 106 / items.length,
      x0 = 96,
      width = 238;
    let body = `${chartLabel(data.aLabel, 220, 14, "axis-title", "end")}${chartLabel(data.bLabel, 334, 14, "axis-title", "end")}`;
    body += numericAxes(0, max, "", data.unit, {
      left: x0,
      right: x0 + width,
      top: CHART.top,
      base: CHART.base,
      horizontal: true,
    });
    items.forEach((item, i) => {
      const y = CHART.top + 3 + i * row,
        aw = scale(item.a, 0, max, 0, width),
        bw = scale(item.b, 0, max, 0, width),
        h = Math.max(5, Math.min(8, row / 2 - 2));
      body += wrapDetail(
        `${categoryLabel(item.label, x0 - 7, y + h + 2, 82, "end")}<rect class="tone-3${openClass(item)}" x="${x0}" y="${y.toFixed(2)}" width="${aw.toFixed(2)}" height="${h.toFixed(2)}"/>${unitRungs(x0, y, aw, h, item.a, true)}${chartLabel(fmt(item.a), x0 + aw + 4, y + h, "value", "start")}<rect class="tone-1${openClass(item)}" x="${x0}" y="${(y + h + 3).toFixed(2)}" width="${bw.toFixed(2)}" height="${h.toFixed(2)}"/>${unitRungs(x0, y + h + 3, bw, h, item.b, true)}${chartLabel(fmt(item.b), x0 + bw + 4, y + h * 2 + 3, "value", "start")}`,
        item,
      );
    });
    body += axisX(data.xAxisTitle, data.unit);
    return chartFrame(body, data, opts, "zero");
  };

  C.stackedRungs = (params, opts) => {
    const data = chartParams(params, [
      "partLabels",
      "items",
      "unit",
      "xAxisTitle",
      "sourceNote",
    ]);
    opts = opts || {};
    strings(data.partLabels, "partLabels", 2, 4);
    text(data.unit, "unit");
    text(data.xAxisTitle, "xAxisTitle");
    const items = list(data.items, "items", 2, 6).map((value, i) => {
        chartItem(
          value,
          `items[${i}]`,
          ["label", "parts", "open", "detail"],
          ["label"],
        );
        list(
          value.parts,
          `items[${i}].parts`,
          data.partLabels.length,
          data.partLabels.length,
        );
        value.parts.forEach((part, j) => {
          number(part, `items[${i}].parts[${j}]`, 0);
        });
        return value;
      }),
      totals = items.map((item) =>
        item.parts.reduce((sum, value) => sum + value, 0),
      ),
      max = Math.max(1, ...totals),
      row = 108 / items.length,
      x0 = 91,
      plotW = 239,
      showSegments = items.length * data.partLabels.length <= 12;
    let body = data.partLabels
      .map((label, i) =>
        chartLabel(
          label,
          x0 + (i + 0.5) * (plotW / data.partLabels.length),
          14,
          `axis-title tone-name-${i + 1}`,
        ),
      )
      .join("");
    body += numericAxes(0, max, "", data.unit, {
      left: x0,
      right: x0 + plotW,
      top: CHART.top,
      base: CHART.base,
      horizontal: true,
    });
    items.forEach((item, ri) => {
      const y = CHART.top + ri * row + 4,
        height = Math.min(16, row - 5);
      let x = x0,
        marks = categoryLabel(
          item.label,
          x0 - 7,
          y + height / 2 + 4,
          77,
          "end",
        );
      item.parts.forEach((value, pi) => {
        const width = scale(value, 0, max, 0, plotW);
        marks += `<rect class="tone-${pi + 1}${openClass(item)}" x="${x.toFixed(2)}" y="${y.toFixed(2)}" width="${width.toFixed(2)}" height="${height.toFixed(2)}"/>${unitRungs(x, y, width, height, value, true)}`;
        if (showSegments && width >= 18) {
          const label = fmt(value),
            padW = label.length * 7 + 4;
          marks += `<rect class="tone-${pi + 1} label-pad${openClass(item)}" x="${(x + width / 2 - padW / 2).toFixed(2)}" y="${(y + height / 2 - 6).toFixed(2)}" width="${padW}" height="12"/>`;
          marks += chartLabel(
            label,
            x + width / 2,
            y + height / 2 + 4,
            "value",
          );
        }
        x += width;
      });
      marks += chartLabel(
        fmt(totals[ri]),
        x + 4,
        y + height / 2 + 4,
        "value",
        "start",
      );
      body += wrapDetail(marks, item);
    });
    body += axisX(data.xAxisTitle, data.unit);
    return chartFrame(body, data, opts, "zero");
  };

  C.plumbScatter = (params, opts) => {
    const data = chartParams(params, [
        "points",
        "xAxisTitle",
        "yAxisTitle",
        "xUnit",
        "yUnit",
        "sourceNote",
      ]),
      points = pointData(data, 3, 20);
    opts = opts || {};
    ["xAxisTitle", "yAxisTitle", "xUnit", "yUnit"].forEach((key) => {
      text(data[key], key);
    });
    points.forEach((point, i) => {
      text(point.label, `points[${i}].label`);
    });
    const xmin = Math.min(0, ...points.map((point) => point.x)),
      xmax = Math.max(1, ...points.map((point) => point.x)),
      ymin = Math.min(0, ...points.map((point) => point.y)),
      ymax = Math.max(1, ...points.map((point) => point.y)),
      xy = points.map((point) => ({
        ...point,
        px: scale(point.x, xmin, xmax, CHART.left, CHART.right),
        py: scale(point.y, ymin, ymax, CHART.base, CHART.top),
      }));
    let body = numericAxes(ymin, ymax, data.yAxisTitle, data.yUnit);
    niceTicks(xmin, xmax, 5).forEach((tick) => {
      const x = scale(tick, xmin, xmax, CHART.left, CHART.right);
      body += `${chartLabel(fmt(tick), x, 157, "muted")}<path class="faint" d="M${x.toFixed(2)} ${CHART.top}V${CHART.base}"/>`;
    });
    xy.forEach((point) => {
      body += wrapDetail(
        `<path class="quiet${openClass(point, true)}" d="M${point.px.toFixed(2)} ${CHART.base}V${point.py.toFixed(2)}"/><circle class="tone-3${openClass(point)}" cx="${point.px.toFixed(2)}" cy="${point.py.toFixed(2)}" r="3.5"/>`,
        point,
      );
    });
    body += labelBoxes(xy).join("") + axisX(data.xAxisTitle, data.xUnit);
    return chartFrame(body, data, opts, "zero");
  };

  C.rungWaterfall = (params, opts) => {
    const data = chartParams(params, [
      "start",
      "startLabel",
      "steps",
      "endLabel",
      "unit",
      "yAxisTitle",
      "sourceNote",
    ]);
    opts = opts || {};
    number(data.start, "start");
    ["startLabel", "endLabel", "unit", "yAxisTitle"].forEach((key) => {
      text(data[key], key);
    });
    const steps = list(data.steps, "steps", 2, 6).map((value, i) =>
        chartItem(
          value,
          `steps[${i}]`,
          ["label", "delta", "open", "detail"],
          ["label", "delta"],
        ),
      ),
      cumulative = [data.start];
    steps.forEach((step) => {
      cumulative.push(cumulative.at(-1) + step.delta);
    });
    const ymin = Math.min(0, ...cumulative),
      ymax = Math.max(1, ...cumulative),
      count = steps.length + 2,
      slot = (CHART.right - CHART.left) / count,
      width = Math.min(28, slot - 5),
      ypos = (value) => scale(value, ymin, ymax, CHART.base, CHART.top);
    let body = numericAxes(ymin, ymax, data.yAxisTitle, data.unit);
    const fullBar = (value, label, index, tone) => {
      const x = CHART.left + index * slot + (slot - width) / 2,
        y0 = ypos(0),
        y1 = ypos(value),
        y = Math.min(y0, y1),
        height = Math.max(1, Math.abs(y1 - y0));
      return `<rect class="${tone}" x="${x.toFixed(2)}" y="${y.toFixed(2)}" width="${width.toFixed(2)}" height="${height.toFixed(2)}"/>${chartLabel(fmt(value), x + width / 2, Math.max(20, y - 5), "value")}${categoryLabel(label, x + width / 2, 158, slot - 2)}`;
    };
    body += fullBar(data.start, data.startLabel, 0, "tone-3");
    steps.forEach((step, i) => {
      const before = cumulative[i],
        after = cumulative[i + 1],
        x = CHART.left + (i + 1) * slot + (slot - width) / 2,
        yBefore = ypos(before),
        yAfter = ypos(after),
        y = Math.min(yBefore, yAfter),
        height = Math.max(1, Math.abs(yAfter - yBefore));
      body += wrapDetail(
        `<rect class="tone-1${openClass(step)}" x="${x.toFixed(2)}" y="${y.toFixed(2)}" width="${width.toFixed(2)}" height="${height.toFixed(2)}"/>${unitRungs(x, y, width, height, Math.abs(step.delta))}${chartLabel(`${step.delta >= 0 ? "+" : ""}${fmt(step.delta)}`, x + width / 2, step.delta >= 0 ? Math.max(20, y - 5) : Math.min(141, y + height + 12), "value")}${categoryLabel(step.label, x + width / 2, 158, slot - 2)}`,
        step,
      );
      if (i < steps.length - 1)
        body += `<path class="faint" d="M${(x + width).toFixed(2)} ${yAfter.toFixed(2)}H${(x + slot).toFixed(2)}"/>`;
    });
    body += fullBar(cumulative.at(-1), data.endLabel, count - 1, "tone-3");
    return chartFrame(body, data, opts, "zero");
  };

  C.dotHeat = (params, opts) => {
    const data = chartParams(params, [
      "rows",
      "cols",
      "values",
      "rowAxisTitle",
      "colAxisTitle",
      "unit",
      "sourceNote",
    ]);
    opts = opts || {};
    strings(data.rows, "rows", 2, 8);
    strings(data.cols, "cols", 2, 12);
    ["rowAxisTitle", "colAxisTitle", "unit"].forEach((key) => {
      text(data[key], key);
    });
    list(data.values, "values", data.rows.length, data.rows.length);
    data.values.forEach((row, ri) => {
      list(row, `values[${ri}]`, data.cols.length, data.cols.length);
      row.forEach((value, ci) => {
        number(value, `values[${ri}][${ci}]`, 0);
      });
    });
    const max = Math.max(1, ...data.values.flat()),
      left = 92,
      top = 29,
      width = 252,
      height = 111,
      cellW = width / data.cols.length,
      cellH = height / data.rows.length;
    let body = chartLabel(
      `Dot area · ${data.unit}`,
      344,
      14,
      "axis-title",
      "end",
    );
    data.cols.forEach((value, i) => {
      body += categoryLabel(value, left + (i + 0.5) * cellW, 24, cellW - 2);
    });
    data.rows.forEach((rowLabel, ri) => {
      body += categoryLabel(
        rowLabel,
        left - 7,
        top + (ri + 0.5) * cellH + 4,
        76,
        "end",
      );
      data.values[ri].forEach((value, ci) => {
        const radius =
            Math.sqrt(value / max) * Math.max(1, Math.min(8, cellH / 2 - 2)),
          x = left + (ci + 0.5) * cellW,
          y = top + (ri + 0.5) * cellH;
        body += `<circle class="tone-2" cx="${x.toFixed(2)}" cy="${y.toFixed(2)}" r="${radius.toFixed(2)}"/>${chartLabel(value, x, y + 3.5, "value")}`;
      });
    });
    body += `<g transform="translate(11 84) rotate(-90)">${chartLabel(data.rowAxisTitle, 0, 0, "axis-title")}</g>${chartLabel(data.colAxisTitle, 218, 158, "axis-title")}`;
    return chartFrame(body, data, opts, "not-applicable");
  };

  C.tickGauge = (params, opts) => {
    const data = chartParams(params, [
      "value",
      "max",
      "label",
      "unit",
      "open",
      "sourceNote",
    ]);
    opts = opts || {};
    number(data.value, "value", 0, true);
    number(data.max, "max", 1, true);
    if (data.max > 100) throw new TypeError("max must be at most 100 ticks");
    if (data.value > data.max) throw new TypeError("value must not exceed max");
    ["label", "unit"].forEach((key) => {
      text(data[key], key);
    });
    choice(data.open, "open", ["yes", "no"], true);
    const cx = 180,
      cy = 116,
      inner = 56,
      outer = 72;
    let body = "";
    for (let i = 0; i < data.max; i++) {
      const angle = Math.PI + ((i + 0.5) / data.max) * Math.PI,
        x1 = cx + Math.cos(angle) * inner,
        y1 = cy + Math.sin(angle) * inner,
        x2 = cx + Math.cos(angle) * outer,
        y2 = cy + Math.sin(angle) * outer,
        active = i < data.value,
        open = active && data.open === "yes";
      body += `<path class="${active ? "line" : "faint"}${open ? " open-line" : ""}" d="M${x1.toFixed(2)} ${y1.toFixed(2)}L${x2.toFixed(2)} ${y2.toFixed(2)}"/>`;
    }
    body += `${chartLabel(fmt(data.value), cx, 103, "value")}${chartLabel(`0`, cx - outer, 136, "muted")}${chartLabel(fmt(data.max), cx + outer, 136, "value")}${chartLabel(unitTitle(data.label, data.unit), cx, 160, "axis-title")}`;
    return chartFrame(body, data, opts, "zero");
  };

  C.dumbbell = (params, opts) => {
    const data = chartParams(params, [
      "beforeLabel",
      "afterLabel",
      "items",
      "unit",
      "xAxisTitle",
      "sourceNote",
    ]);
    opts = opts || {};
    ["beforeLabel", "afterLabel", "unit", "xAxisTitle"].forEach((key) => {
      text(data[key], key);
    });
    const items = labeledData(
        data,
        "items",
        2,
        6,
        ["before", "after"],
        ["label", "before", "after", "open", "detail"],
      ),
      max = Math.max(1, ...items.flatMap((item) => [item.before, item.after])),
      left = 103,
      right = 336,
      row = 105 / items.length;
    let body = `${chartLabel(data.beforeLabel, 220, 14, "axis-title", "end")}${chartLabel(data.afterLabel, 336, 14, "axis-title", "end")}`;
    body += numericAxes(0, max, "", data.unit, {
      left,
      right,
      top: CHART.top,
      base: CHART.base,
      horizontal: true,
    });
    items.forEach((item, i) => {
      const y = CHART.top + row * (i + 0.5),
        before = scale(item.before, 0, max, left, right),
        after = scale(item.after, 0, max, left, right);
      body += wrapDetail(
        `${categoryLabel(item.label, left - 8, y + 4, 88, "end")}<path class="quiet${openClass(item, true)}" d="M${before.toFixed(2)} ${y.toFixed(2)}H${after.toFixed(2)}"/><circle class="fill${openClass(item)}" cx="${before.toFixed(2)}" cy="${y.toFixed(2)}" r="4"/><circle class="tone-4${openClass(item)}" cx="${after.toFixed(2)}" cy="${y.toFixed(2)}" r="4"/>${chartLabel(fmt(item.before), before, y - 7, "value")}${chartLabel(fmt(item.after), after, y + 14, "value")}`,
        item,
      );
    });
    body += axisX(data.xAxisTitle, data.unit);
    return chartFrame(body, data, opts, "zero");
  };

  function treemapNodes(values, name, depth = 0) {
    return list(values, name, 2, depth ? 6 : 8).map((value, i) => {
      chartItem(
        value,
        `${name}[${i}]`,
        ["label", "value", "children", "open", "detail"],
        ["label", "value"],
      );
      number(value.value, `${name}[${i}].value`, 0);
      if (value.children != null) {
        if (depth)
          throw new TypeError(
            `${name}[${i}].children may only be one level deep`,
          );
        treemapNodes(value.children, `${name}[${i}].children`, depth + 1);
      }
      return value;
    });
  }
  function treemapRects(items, x, y, width, height, depth = 0) {
    const total = items.reduce((sum, item) => sum + item.value, 0);
    if (!total) throw new TypeError("treemap levels must total more than zero");
    let cursor = depth % 2 ? y : x,
      body = "";
    items.forEach((item, i) => {
      const ratio = item.value / total,
        w = depth % 2 ? width : width * ratio,
        h = depth % 2 ? height * ratio : height,
        rx = depth % 2 ? x : cursor,
        ry = depth % 2 ? cursor : y,
        header = item.children ? Math.min(20, h * 0.3) : 0;
      cursor += depth % 2 ? h : w;
      body += wrapDetail(
        `<rect class="tone-${(i % 4) + 1}${openClass(item)}" x="${rx.toFixed(2)}" y="${ry.toFixed(2)}" width="${w.toFixed(2)}" height="${h.toFixed(2)}"/>${categoryLabel(`${item.label} ${fmt(item.value)}`, rx + 5, ry + 14, Math.max(18, w - 10), "start")}`,
        item,
      );
      if (item.children)
        body += treemapRects(
          item.children,
          rx + 3,
          ry + header,
          Math.max(1, w - 6),
          Math.max(1, h - header - 3),
          depth + 1,
        );
    });
    return body;
  }
  C.treemap = (params, opts) => {
    const data = chartParams(params, ["items", "unit", "total", "sourceNote"]);
    opts = opts || {};
    text(data.unit, "unit");
    text(data.total, "total", true);
    const items = treemapNodes(data.items, "items"),
      sum = items.reduce((total, item) => total + item.value, 0);
    const body = `${chartLabel(data.total || `${fmt(sum)} ${data.unit}`, 348, 14, "value", "end")}${treemapRects(items, 14, 24, 332, 145)}`;
    return chartFrame(body, data, opts, "not-applicable");
  };

  C.rungHistogram = (params, opts) => {
    const data = chartParams(params, [
        "bins",
        "xAxisTitle",
        "unit",
        "sourceNote",
      ]),
      bins = labeledData(
        data,
        "bins",
        3,
        12,
        ["count"],
        ["label", "count", "open", "detail"],
      );
    opts = opts || {};
    text(data.xAxisTitle, "xAxisTitle");
    text(data.unit, "unit");
    bins.forEach((bin, i) => {
      number(bin.count, `bins[${i}].count`, 0, true);
    });
    const max = Math.max(1, ...bins.map((bin) => bin.count)),
      slot = (CHART.right - CHART.left) / bins.length,
      width = slot - 1;
    let body = numericAxes(0, max, "Count", data.unit);
    bins.forEach((bin, i) => {
      const height = scale(bin.count, 0, max, 0, CHART.base - CHART.top),
        x = CHART.left + i * slot,
        y = CHART.base - height;
      body += wrapDetail(
        `<rect class="tone-2${openClass(bin)}" x="${x.toFixed(2)}" y="${y.toFixed(2)}" width="${width.toFixed(2)}" height="${height.toFixed(2)}"/>${unitRungs(x, y, width, height, bin.count)}${chartLabel(bin.count, x + width / 2, Math.max(20, y - 5), "value")}${categoryLabel(bin.label, x + width / 2, 158, slot - 1)}`,
        bin,
      );
    });
    body += axisX(data.xAxisTitle, data.unit);
    return chartFrame(body, data, opts, "zero");
  };

  C.tickBox = (params, opts) => {
    const data = chartParams(params, [
      "groups",
      "xAxisTitle",
      "unit",
      "sourceNote",
    ]);
    opts = opts || {};
    text(data.xAxisTitle, "xAxisTitle");
    text(data.unit, "unit");
    const groups = list(data.groups, "groups", 1, 6).map((value, i) => {
        chartItem(
          value,
          `groups[${i}]`,
          [
            "label",
            "min",
            "q1",
            "median",
            "q3",
            "max",
            "outliers",
            "open",
            "detail",
          ],
          ["label", "min", "q1", "median", "q3", "max"],
        );
        if (
          !(
            value.min <= value.q1 &&
            value.q1 <= value.median &&
            value.median <= value.q3 &&
            value.q3 <= value.max
          )
        )
          throw new TypeError(
            `groups[${i}] five-number values must be ordered`,
          );
        if (value.outliers != null) {
          list(value.outliers, `groups[${i}].outliers`, 0, 20);
          value.outliers.forEach((outlier, j) => {
            number(outlier, `groups[${i}].outliers[${j}]`);
          });
        }
        return value;
      }),
      observed = groups.flatMap((group) => [
        group.min,
        group.max,
        ...(group.outliers || []),
      ]),
      min = Math.min(0, ...observed),
      max = Math.max(1, ...observed),
      left = 91,
      right = 340,
      row = 108 / groups.length;
    let body = numericAxes(min, max, "", data.unit, {
      left,
      right,
      top: CHART.top,
      base: CHART.base,
      horizontal: true,
    });
    groups.forEach((group, i) => {
      const y = CHART.top + row * (i + 0.5),
        pos = (value) => scale(value, min, max, left, right),
        boxH = Math.min(16, row - 5),
        q1 = pos(group.q1),
        q3 = pos(group.q3),
        lineCls = `line${openClass(group, true)}`;
      let marks = `${categoryLabel(group.label, left - 7, y + 4, 76, "end")}<path class="${lineCls}" d="M${pos(group.min).toFixed(2)} ${y.toFixed(2)}H${pos(group.max).toFixed(2)}M${pos(group.min).toFixed(2)} ${(y - 5).toFixed(2)}V${(y + 5).toFixed(2)}M${pos(group.max).toFixed(2)} ${(y - 5).toFixed(2)}V${(y + 5).toFixed(2)}"/><rect class="fill${openClass(group)}" x="${q1.toFixed(2)}" y="${(y - boxH / 2).toFixed(2)}" width="${Math.max(1, q3 - q1).toFixed(2)}" height="${boxH.toFixed(2)}"/><path class="${lineCls}" d="M${pos(group.median).toFixed(2)} ${(y - boxH / 2).toFixed(2)}V${(y + boxH / 2).toFixed(2)}"/>${chartLabel(fmt(group.min), pos(group.min), y - 10, "value")}${chartLabel(fmt(group.q1), pos(group.q1), y + 17, "value")}${chartLabel(fmt(group.median), pos(group.median), y - 10, "value")}${chartLabel(fmt(group.q3), pos(group.q3), y + 17, "value")}${chartLabel(fmt(group.max), pos(group.max), y - 10, "value")}`;
      (group.outliers || []).forEach((outlier) => {
        marks += `<circle class="tone-4${openClass(group)}" cx="${pos(outlier).toFixed(2)}" cy="${y.toFixed(2)}" r="2.5"/>${chartLabel(fmt(outlier), pos(outlier), y + 17, "value")}`;
      });
      body += wrapDetail(marks, group);
    });
    body += axisX(data.xAxisTitle, data.unit);
    return chartFrame(body, data, opts, "zero");
  };

  C.streamRibbon = (params, opts) => {
    const data = chartParams(params, [
      "seriesLabels",
      "xLabels",
      "values",
      "xAxisTitle",
      "yAxisTitle",
      "unit",
      "sourceNote",
    ]);
    opts = opts || {};
    strings(data.seriesLabels, "seriesLabels", 2, 5);
    strings(data.xLabels, "xLabels", 3, 12);
    ["xAxisTitle", "yAxisTitle", "unit"].forEach((key) => {
      text(data[key], key);
    });
    list(
      data.values,
      "values",
      data.seriesLabels.length,
      data.seriesLabels.length,
    );
    data.values.forEach((series, si) => {
      list(series, `values[${si}]`, data.xLabels.length, data.xLabels.length);
      series.forEach((value, xi) => {
        number(value, `values[${si}][${xi}]`, 0);
      });
    });
    const totals = data.xLabels.map((_, xi) =>
        data.values.reduce((sum, series) => sum + series[xi], 0),
      ),
      max = Math.max(1, ...totals),
      left = CHART.left,
      right = 292,
      x = (index) => scale(index, 0, data.xLabels.length - 1, left, right),
      y = (value) => scale(value, 0, max, CHART.base, CHART.top),
      cumulative = new Array(data.xLabels.length).fill(0);
    let body = numericAxes(0, max, data.yAxisTitle, data.unit, { right });
    data.values.forEach((series, si) => {
      const bottom = cumulative.slice(),
        top = series.map((value, xi) => bottom[xi] + value),
        upper = top.map((value, xi) => [x(xi), y(value)]),
        lower = bottom.map((value, xi) => [x(xi), y(value)]).reverse();
      body += `<path class="tone-${si + 1}" d="${path([...upper, ...lower])}Z"/>`;
      top.forEach((value, xi) => {
        cumulative[xi] = value;
      });
    });
    const railYs = data.seriesLabels.map(
      (_, i) =>
        CHART.top +
        ((i + 0.5) * (CHART.base - CHART.top)) / data.seriesLabels.length,
    );
    let atLast = 0;
    data.seriesLabels.forEach((label, si) => {
      const mid = atLast + data.values[si].at(-1) / 2,
        fromY = y(mid);
      atLast += data.values[si].at(-1);
      body += `<path class="quiet" d="M${right} ${fromY.toFixed(2)}L302 ${railYs[si].toFixed(2)}"/>${chartLabel(label, 306, railYs[si] + 3.5, "axis-title", "start")}`;
    });
    const stride = Math.max(1, Math.ceil(data.xLabels.length / 7));
    data.xLabels.forEach((label, i) => {
      if (i % stride === 0 || i === data.xLabels.length - 1)
        body += chartLabel(label, x(i), 157, "muted");
    });
    body += `${chartLabel(fmt(totals[0]), left + 4, Math.max(20, y(totals[0]) - 5), "value", "start")}${chartLabel(fmt(totals.at(-1)), right - 4, Math.max(20, y(totals.at(-1)) - 5), "value", "end")}${axisX(data.xAxisTitle)}`;
    return chartFrame(body, data, opts, "zero");
  };

  C.rangeBars = (params, opts) => {
    const data = chartParams(params, [
        "items",
        "unit",
        "xAxisTitle",
        "scaleMin",
        "scaleMax",
        "sourceNote",
      ]),
      items = list(data.items, "items", 1, 8).map((value, i) => {
        chartItem(
          value,
          `items[${i}]`,
          ["label", "low", "high", "mark", "markLabel", "open", "detail"],
          ["label", "low", "high"],
        );
        if (value.low > value.high)
          throw new TypeError(`items[${i}].low must not exceed high`);
        if (value.mark != null) number(value.mark, `items[${i}].mark`);
        if (value.markLabel != null)
          text(value.markLabel, `items[${i}].markLabel`, true);
        return value;
      });
    opts = opts || {};
    text(data.unit, "unit");
    text(data.xAxisTitle, "xAxisTitle");
    if (data.scaleMin != null) number(data.scaleMin, "scaleMin");
    if (data.scaleMax != null) number(data.scaleMax, "scaleMax");
    const observed = items.flatMap((item) => [
        item.low,
        item.high,
        ...(item.mark == null ? [] : [item.mark]),
      ]),
      min = data.scaleMin == null ? Math.min(0, ...observed) : data.scaleMin,
      max = data.scaleMax == null ? Math.max(1, ...observed) : data.scaleMax;
    if (min >= max) throw new TypeError("scaleMin must be less than scaleMax");
    if (observed.some((value) => value < min || value > max))
      throw new TypeError(
        "scaleMin and scaleMax must contain every item value",
      );
    const left = 94,
      right = 338,
      row = 107 / items.length,
      pos = (value) => scale(value, min, max, left, right);
    let body = numericAxes(min, max, "", data.unit, {
      left,
      right,
      top: CHART.top,
      base: CHART.base,
      horizontal: true,
    });
    items.forEach((item, i) => {
      const y = CHART.top + row * (i + 0.5),
        low = pos(item.low),
        high = pos(item.high);
      let marks = `${categoryLabel(item.label, left - 7, y + 4, 80, "end")}<path class="quiet${openClass(item, true)}" d="M${low.toFixed(2)} ${y.toFixed(2)}H${high.toFixed(2)}"/><path class="line${openClass(item, true)}" d="M${low.toFixed(2)} ${(y - 5).toFixed(2)}V${(y + 5).toFixed(2)}M${high.toFixed(2)} ${(y - 5).toFixed(2)}V${(y + 5).toFixed(2)}"/>${chartLabel(fmt(item.low), low, y - 7, "value")}${chartLabel(fmt(item.high), high, y - 7, "value")}`;
      if (item.mark != null) {
        const mark = pos(item.mark);
        marks += `<circle class="tone-4${openClass(item)}" cx="${mark.toFixed(2)}" cy="${y.toFixed(2)}" r="3.5"/>${chartLabel(item.markLabel || fmt(item.mark), mark, y + 13, "value")}`;
      }
      body += wrapDetail(marks, item);
    });
    body += axisX(data.xAxisTitle, data.unit);
    return chartFrame(body, data, opts, `scale-min:${fmt(min)}`);
  };

  LD.registry = REGISTRY.slice();
  // New work uses the reviewed core; saved files may still render all IDs.
  LD.recommendedRegistry = [
    "flow",
    "timeline",
    "hierarchy",
    "zones",
    "beforeAfter",
    "hub",
    "compareTwo",
    "rungBars",
    "hairlineLine",
    "stackedRungs",
    "rangeBars",
  ];
  LD.components = C;
  LD.render = (id, params, opts) => {
    var fn = C[id];
    if (!fn) throw new Error("unknown component: " + id);
    return fn(params, opts || {});
  };
})();
</script>
<script id="ld-runtime">
(() => {
  function boot() {
    window.LegalDesign = window.LegalDesign || {};
    const LD = window.LegalDesign;
    const root = document.documentElement;
    const stateNode = document.getElementById("legaldesign-state");
    if (!stateNode) return;

    const TEXT_TOKENS = ["ink", "muted", "faint", "red", "red-strong"];
    const FILL_TOKENS = ["card", "card-strong", "tint", "bg", "none"];
    // Template export replaces matter-specific copy, but these values describe
    // how a component is encoded rather than what the matter says. Preserve
    // them so the sanitized template remains both intentional and renderable.
    const TEMPLATE_STRUCTURAL_PARAM_KEYS = new Set([
      "accentRole",
      "baselineZero",
      "highlight",
      "markAt",
      "nowAt",
      "open",
      "orientation",
      "toward",
      "winner",
    ]);
    const TEMPLATE_HEAD_STYLE_BASELINES = Array.from(
      document.head ? document.head.querySelectorAll("style") : [],
    ).map((style, index) => ({
      id: style.id || null,
      index: index,
      attributes: Array.from(style.attributes).map((attribute) => [
        attribute.name,
        attribute.value,
      ]),
      text: style.textContent,
    }));
    const TEMPLATE_CSS_TOKENS = new Set([
      "bg",
      "card",
      "card-strong",
      "faint",
      "ink",
      "insert",
      "label",
      "line",
      "line-strong",
      "muted",
      "red",
      "red-strong",
      "red-wash",
      "scrim",
      "shadow",
      "shadow-soft",
      "tint",
    ]);
    const PLACEHOLDERS = {
      title: "[The claim in one sentence, ending with a period.]",
      cardTitle: "[The open item in five words.]",
      cardLine: "[One fact about it, under twenty words.]",
      figureLabel: "[Stage name.]",
    };
    const RELATIONSHIPS = [
      "sequence",
      "time",
      "hierarchy",
      "containment",
      "comparison",
      "convergence",
      "feedback",
      "priority",
      "progression",
      "interaction",
      "part-of-whole",
      "distribution",
      "correlation",
      "change",
      "quantity",
    ];
    const VARIANT_AXES = [
      "form",
      "framing",
      "granularity",
      "emphasis",
      "layout",
      "register",
      "single",
    ];
    const LEGACY_LAYOUTS = ["cards", "zones", "agenda", "track"];
    const VARIANT_FIELDS = new Set([
      "agreed_line",
      "axis",
      "cards",
      "component",
      "encoding",
      "html",
      "items",
      "layout",
      "params",
      "rows",
      "segments",
      "sub",
      "text",
      "title",
      "why",
    ]);
    const LEGACY_COLLECTION_FIELDS = {
      cards: new Set(["action", "detail", "line", "tag", "title"]),
      items: new Set(["line", "n", "title"]),
      segments: new Set(["detail", "label", "line", "name", "state"]),
    };
    const UNIT_KINDS = [
      "text",
      "card",
      "figure",
      "table",
      "evidence",
      "decision",
    ];
    const UNIT_ROLES = [
      "title",
      "answer",
      "summary",
      "action",
      "scope",
      "finding",
      "expected",
      "matter",
    ];
    let selection = [];
    const undo = [];
    let redo = [];
    let editing = false;
    let gesture = null;
    let textUndoArmed = false;
    let returnFocus = null;
    let returnFocusKey = null;
    const popupTrail = [];
    let fileHandle = null;
    let suppressEditorClick = false;
    const EDITOR_OBJECTS =
      '[data-editable],[data-diagram-object],[data-editor-object],.ld-card,.sb-card,section.ld-unit[data-kind="card"],.ld-diagram svg g[role="button"]';
    const popupFallbackNodes = new WeakSet();
    const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)");
    const MIN_FIGURE_TEXT_PX = 11;
    const FIGURE_TEXT_ROUNDING_PX = 0.01;
    const WIDE_FRAME_WIDTH = 880;
    const WIDE_MIN_TEXT_PX = 12.25;

    function announceReady() {
      root.setAttribute("data-legaldesign-ready", "true");
      window.dispatchEvent(new CustomEvent("legaldesign:ready"));
    }

    function scheduleReady(afterPageListeners) {
      root.removeAttribute("data-legaldesign-ready");
      if (!afterPageListeners) {
        queueMicrotask(announceReady);
        return;
      }
      requestAnimationFrame(() => {
        requestAnimationFrame(announceReady);
      });
    }

    function safeJSON(value) {
      return JSON.stringify(value).replace(/</g, "\\u003c");
    }

    function clone(value) {
      return JSON.parse(JSON.stringify(value));
    }
    function same(a, b) {
      return safeJSON(a) === safeJSON(b);
    }
    function all(selector, scope) {
      return Array.prototype.slice.call(
        (scope || document).querySelectorAll(selector),
      );
    }
    function removeLegacyReviewChrome(scope) {
      all(
        ".ld-review,.ld-sheet-open,.ld-bottom-save,#ld-phone-sheet,#ld-selection-popover",
        scope || document,
      ).forEach((node) => {
        node.remove();
      });
    }
    function visible(node) {
      const box = node.getBoundingClientRect();
      return (
        !node.hidden &&
        box.width > 0 &&
        box.height > 0 &&
        getComputedStyle(node).visibility !== "hidden"
      );
    }
    function sanitizeEditorTokens(scope) {
      all("[data-fill-token]", scope || document).forEach((node) => {
        if (!FILL_TOKENS.includes(node.getAttribute("data-fill-token")))
          node.removeAttribute("data-fill-token");
      });
      all("[data-text-token]", scope || document).forEach((node) => {
        if (!TEXT_TOKENS.includes(node.getAttribute("data-text-token")))
          node.removeAttribute("data-text-token");
      });
    }
    function restoreTrustedTemplateStyles(cloneRoot) {
      all("style", cloneRoot).forEach((style) => {
        if (!style.closest("head")) style.remove();
      });
      const head = cloneRoot.querySelector("head");
      if (!head) return;
      const candidates = all("style", head);
      const claimed = new Set();
      TEMPLATE_HEAD_STYLE_BASELINES.forEach((baseline) => {
        let style = baseline.id
          ? candidates.find(
              (candidate) =>
                !claimed.has(candidate) && candidate.id === baseline.id,
            )
          : candidates[baseline.index];
        if (style && claimed.has(style)) style = null;
        if (!style)
          style = candidates.find((candidate) => !claimed.has(candidate));
        if (!style) {
          style = document.createElement("style");
          head.appendChild(style);
        }
        Array.from(style.attributes).forEach((attribute) => {
          style.removeAttribute(attribute.name);
        });
        baseline.attributes.forEach(([name, value]) => {
          style.setAttribute(name, value);
        });
        style.textContent = baseline.text;
        claimed.add(style);
      });
      candidates.forEach((style) => {
        if (!claimed.has(style)) style.remove();
      });
    }
    function safeTemplateMeasurement(value, allowKeywords) {
      const keywords = allowKeywords || [];
      return String(value)
        .trim()
        .split(/\s+/)
        .every(
          (part) =>
            keywords.includes(part) ||
            /^-?(?:\d+(?:\.\d+)?|\.\d+)(?:px|%|em|rem|vh|vw)?$/.test(part),
        );
    }
    function safeTemplateTransform(value) {
      value = String(value).trim();
      if (value === "none") return true;
      const number = "-?(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|%|deg)?";
      const args = `${number}(?:\\s*(?:,|\\s)\\s*${number}){0,15}`;
      const transform = new RegExp(
        `(?:matrix3d|matrix|translate3d|translate|translateX|translateY|scale3d|scale|scaleX|scaleY|rotate)\\(\\s*${args}\\s*\\)`,
        "gi",
      );
      return value.replace(transform, "").trim() === "";
    }
    function safeTemplateColor(value) {
      value = String(value).trim();
      if (["currentcolor", "inherit", "none", "transparent"].includes(value))
        return true;
      const match = /^var\(--([a-z0-9-]+)\)$/.exec(value);
      return Boolean(match && TEMPLATE_CSS_TOKENS.has(match[1]));
    }
    function safeTemplateStyleValue(property, value) {
      // Composed pages encode placement with numeric custom properties. These
      // are structural coordinates, not matter content; stripping them collapses
      // every unit into a single grid column in an exported template.
      if (
        ["--ld-row", "--ld-column", "--ld-span", "--ld-row-span"].includes(
          property,
        )
      )
        return /^[1-9]\d{0,3}$/.test(String(value).trim());
      const measurements = new Set([
        "bottom",
        "border-radius",
        "column-gap",
        "font-size",
        "gap",
        "height",
        "left",
        "max-height",
        "max-width",
        "min-height",
        "min-width",
        "right",
        "row-gap",
        "top",
        "width",
      ]);
      const boxMeasurements =
        /^(?:margin|padding)(?:-(?:top|right|bottom|left))?$/;
      if (measurements.has(property))
        return safeTemplateMeasurement(value, ["auto", "none"]);
      if (boxMeasurements.test(property))
        return safeTemplateMeasurement(value, ["auto"]);
      if (
        ["background", "background-color", "border-color", "color"].includes(
          property,
        )
      )
        return safeTemplateColor(value);
      if (property === "transform") return safeTemplateTransform(value);
      if (property === "transform-origin")
        return safeTemplateMeasurement(value, [
          "bottom",
          "center",
          "left",
          "right",
          "top",
        ]);
      if (property === "position")
        return ["absolute", "fixed", "relative", "static", "sticky"].includes(
          value,
        );
      if (property === "display")
        return [
          "block",
          "flex",
          "grid",
          "inline",
          "inline-block",
          "inline-flex",
          "none",
        ].includes(value);
      if (property === "justify-content")
        return [
          "center",
          "end",
          "flex-end",
          "flex-start",
          "space-around",
          "space-between",
          "space-evenly",
          "start",
        ].includes(value);
      if (property === "text-align")
        return ["center", "end", "left", "right", "start"].includes(value);
      if (property === "box-sizing")
        return ["border-box", "content-box"].includes(value);
      if (property === "transform-box")
        return ["border-box", "content-box", "fill-box", "view-box"].includes(
          value,
        );
      if (property === "font-style")
        return ["italic", "normal", "oblique"].includes(value);
      if (property === "font-weight")
        return /^(?:normal|bold|[1-9]00)$/.test(value);
      if (property === "text-decoration-line")
        return /^(?:none|underline|overline|line-through)(?:\s+(?:underline|overline|line-through))*$/.test(
          value,
        );
      if (property === "line-height")
        return value === "normal" || safeTemplateMeasurement(value);
      if (property === "z-index") return /^-?\d+$/.test(value);
      return false;
    }
    function scrubTemplateMatterAttributes(cloneRoot) {
      const idReferences = new Set([
        "aria-controls",
        "aria-describedby",
        "aria-labelledby",
      ]);
      const stateValues = {
        "aria-current": new Set([
          "date",
          "false",
          "location",
          "page",
          "step",
          "time",
          "true",
        ]),
        "aria-disabled": new Set(["false", "true"]),
        "aria-expanded": new Set(["false", "true"]),
        "aria-haspopup": new Set([
          "dialog",
          "false",
          "grid",
          "listbox",
          "menu",
          "tree",
          "true",
        ]),
        "aria-hidden": new Set(["false", "true"]),
        "aria-live": new Set(["assertive", "off", "polite"]),
        "aria-modal": new Set(["false", "true"]),
        "aria-pressed": new Set(["false", "mixed", "true"]),
      };
      const ids = new Set(
        all("[id]", cloneRoot)
          .map((node) => node.id)
          .filter(Boolean),
      );
      all("*", cloneRoot).forEach((node) => {
        if (node.localName === "style" || node.localName === "script") return;
        if (node.hasAttribute("style")) {
          const kept = Array.from(node.style)
            .map((property) => ({
              property: property,
              value: node.style.getPropertyValue(property).trim(),
              priority: node.style.getPropertyPriority(property),
            }))
            .filter((item) =>
              safeTemplateStyleValue(item.property, item.value),
            );
          node.removeAttribute("style");
          kept.forEach((item) => {
            node.style.setProperty(item.property, item.value, item.priority);
          });
        }
        if (node.hasAttribute("value")) {
          const value = node.getAttribute("value");
          const safeDecision =
            node.hasAttribute("data-decision-option") &&
            /^option-\d+$/.test(value);
          const safeFill =
            node.matches("#editor-fill-token option") &&
            [""].concat(FILL_TOKENS).includes(value);
          const safeText =
            node.matches("#editor-text-token option") &&
            [""].concat(TEXT_TOKENS).includes(value);
          if (!safeDecision && !safeFill && !safeText)
            node.removeAttribute("value");
        }
        if (node.hasAttribute("placeholder")) {
          if (node.hasAttribute("data-decision-custom"))
            node.setAttribute("placeholder", "Custom position");
          else if (node.hasAttribute("data-decision-note"))
            node.setAttribute("placeholder", "Note");
          else if (node.matches("input,textarea"))
            node.setAttribute("placeholder", "[Editable value.]");
          else node.removeAttribute("placeholder");
        }
        Array.from(node.attributes).forEach((attribute) => {
          const name = attribute.name.toLowerCase();
          if (!name.startsWith("aria-")) return;
          if (name === "aria-label" || name === "aria-description") {
            if (name === "aria-description")
              node.setAttribute(name, "[Accessible description.]");
            return;
          }
          if (idReferences.has(name)) {
            const references = attribute.value
              .split(/\s+/)
              .filter((id) => id && ids.has(id));
            if (references.length)
              node.setAttribute(name, references.join(" "));
            else node.removeAttribute(name);
            return;
          }
          if (stateValues[name] && stateValues[name].has(attribute.value))
            return;
          node.removeAttribute(name);
        });
      });
    }
    function normalizeChrome() {
      ["mode-toggle", "export-html", "export-template"].forEach((id) => {
        const control = document.getElementById(id);
        if (control) control.setAttribute("data-editor-only", "");
      });
      const controls = document.querySelector(".ld-controls");
      const editorTools = document.querySelector(".ld-editor-tools");
      if (controls) controls.classList.add("ld-pills");
      if (controls && editorTools && editorTools.parentElement !== controls) {
        const theme = document.getElementById("theme-toggle");
        controls.insertBefore(editorTools, theme || controls.firstChild);
      }
      [
        ["editor-font-down", "A−", "Decrease type"],
        ["editor-font-up", "A+", "Increase type"],
        ["editor-bold", "B", "Bold"],
        ["editor-italic", "I", "Italic"],
        ["editor-underline", "U", "Underline"],
      ].forEach((entry) => {
        const button = document.getElementById(entry[0]);
        if (!button) return;
        button.textContent = entry[1];
        button.setAttribute("aria-label", entry[2]);
        button.title = entry[2];
      });
      const theme = document.getElementById("theme-toggle");
      if (theme) {
        theme.setAttribute("aria-label", "Theme");
        theme.title = "Theme";
        theme.innerHTML =
          '<svg class="ld-theme-icon ld-theme-moon" aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.8A9 9 0 1 1 11.2 3 7 7 0 0 0 21 12.8z"></path></svg><svg class="ld-theme-icon ld-theme-sun" aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"></circle><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"></path></svg>';
      }
      const overflow = document.getElementById("editor-overflow-toggle");
      if (overflow) {
        overflow.textContent = "More";
        overflow.setAttribute("aria-label", "More");
      }
      [
        ["editor-fill-token", "Fill", FILL_TOKENS],
        ["editor-text-token", "Text", TEXT_TOKENS],
      ].forEach((entry) => {
        const select = document.getElementById(entry[0]);
        if (!select) return;
        const value = select.value;
        select.replaceChildren();
        [""].concat(entry[2]).forEach((token) => {
          const option = document.createElement("option");
          option.value = token;
          option.textContent = token || entry[1];
          select.appendChild(option);
        });
        if ([""].concat(entry[2]).includes(value)) select.value = value;
      });
      sanitizeEditorTokens(document);
      installAuthoringControls();
    }
    function rememberReturnFocus(source) {
      const node = source || document.activeElement;
      const unit = node && node.closest("[data-unit]");
      returnFocus = node;
      returnFocusKey = node
        ? {
            id: node.id || null,
            detail: node.getAttribute("data-detail"),
            evidence: node.getAttribute("data-evidence"),
            card: node.matches(".ld-card[data-evidence]"),
            unit: unit && unit.getAttribute("data-unit"),
          }
        : null;
    }
    function restoreReturnFocus() {
      let target = returnFocus;
      const key = returnFocusKey;
      if (target && !target.isConnected && key) {
        if (key.id) target = document.getElementById(key.id);
        if ((!target || !target.isConnected) && key.detail)
          target = all("[data-detail]").find(
            (node) =>
              node.getAttribute("data-detail") === key.detail &&
              !node.closest("[hidden]"),
          );
        if ((!target || !target.isConnected) && key.evidence)
          target = all(
            key.card ? ".ld-card[data-evidence]" : "[data-evidence]",
          ).find(
            (node) =>
              node.getAttribute("data-evidence") === key.evidence &&
              !node.closest("[hidden]"),
          );
      }
      if (target && target.isConnected) target.focus();
      returnFocus = null;
      returnFocusKey = null;
    }
    function parseDate(value) {
      const time = Date.parse(value || "");
      return Number.isFinite(time) ? time : 0;
    }

    let blockState;
    try {
      blockState = JSON.parse(stateNode.textContent);
      if (blockState.schema !== "legaldesign.state.v1")
        throw new TypeError("unsupported legaldesign state schema");
      const roundTrip = JSON.parse(safeJSON(blockState));
      if (!same(blockState, roundTrip))
        throw new Error("legaldesign state round-trip mismatch");
    } catch (error) {
      console.error(
        "LegalDesign refused to render stale or invalid state.",
        error,
      );
      root.setAttribute("data-legaldesign-error", "state");
      return;
    }

    function stableLocationHash(value) {
      let hash = 2166136261;
      value = String(value || "");
      for (let index = 0; index < value.length; index += 1) {
        hash ^= value.charCodeAt(index);
        hash = Math.imul(hash, 16777619);
      }
      return (hash >>> 0).toString(36);
    }
    function stableTemplateLocation() {
      try {
        const url = new URL(window.location.href);
        url.search = "";
        url.hash = "";
        return url.href;
      } catch (_) {
        return String(window.location.href || "").split(/[?#]/)[0];
      }
    }

    let state = blockState;
    const exportMode = root.getAttribute("data-exported");
    if (exportMode === "client") {
      const blueprintId =
        root.getAttribute("data-client-blueprint-id") || state.artifactId;
      const instanceId =
        blueprintId +
        "-client-copy-" +
        stableLocationHash(stableTemplateLocation());
      state.artifactId = instanceId;
      root.setAttribute("data-client-blueprint-id", blueprintId);
      root.setAttribute("data-client-id", instanceId);
      stateNode.textContent = safeJSON(state);
    }
    if (
      exportMode === "template" &&
      ["exported", "packaged"].includes(root.getAttribute("data-template-kind"))
    ) {
      const blueprintId =
        root.getAttribute("data-template-blueprint-id") || state.artifactId;
      const instanceId =
        blueprintId + "-copy-" + stableLocationHash(stableTemplateLocation());
      state.artifactId = instanceId;
      root.setAttribute("data-template-blueprint-id", blueprintId);
      root.setAttribute("data-template-id", instanceId);
      stateNode.textContent = safeJSON(state);
    }
    let storageKey = "legaldesign:" + state.artifactId + ":v1";
    if (exportMode)
      storageKey = "legaldesign:" + state.artifactId + ":" + exportMode + ":v1";
    const domStorageKey = storageKey + ":dom";

    function writeBlock() {
      stateNode.textContent = safeJSON(state);
    }
    function mirror() {
      writeBlock();
      try {
        localStorage.setItem(storageKey, safeJSON(state));
      } catch (_) {
        /* local files may deny storage */
      }
    }
    function editorRoots() {
      return [
        document.querySelector("main"),
        document.getElementById("popup-scrim"),
      ].filter(Boolean);
    }
    function cleanSnapshotHTML(node) {
      const copy = node.cloneNode(true);
      all("[contenteditable]", copy).forEach((editable) => {
        editable.removeAttribute("contenteditable");
        editable.removeAttribute("spellcheck");
      });
      all(".ld-selected,.ld-selection-mark", copy).forEach((selected) => {
        selected.classList.remove("ld-selected", "ld-selection-mark");
      });
      return copy.innerHTML;
    }
    function editorDOMSnapshot() {
      return {
        schema: "legaldesign.editor-dom.v1",
        artifactId: state.artifactId,
        savedAt: state.savedAt,
        roots: editorRoots().map((node, index) => ({
          id: node.id || null,
          index: index,
          html: cleanSnapshotHTML(node),
        })),
      };
    }
    function storeEditorDOM() {
      if (root.getAttribute("data-exported") === "client") return;
      try {
        localStorage.setItem(domStorageKey, safeJSON(editorDOMSnapshot()));
      } catch (_) {
        /* local files may deny storage */
      }
    }
    function readEditorDOM() {
      try {
        const saved = JSON.parse(localStorage.getItem(domStorageKey) || "null");
        if (
          saved &&
          saved.schema === "legaldesign.editor-dom.v1" &&
          saved.artifactId === state.artifactId &&
          Array.isArray(saved.roots)
        )
          return saved;
      } catch (_) {
        /* malformed recovery data is ignored */
      }
      return null;
    }
    function applyEditorDOM(saved) {
      if (!saved || parseDate(saved.savedAt) > parseDate(state.savedAt)) return;
      (saved.roots || []).forEach((item) => {
        const current = item.id
          ? document.getElementById(item.id)
          : editorRoots()[item.index];
        if (current && typeof item.html === "string")
          current.innerHTML = item.html;
      });
      sanitizeEditorTokens(document);
    }
    function captureSnapshot() {
      syncEditorDOMToState();
      return {
        state: clone(state),
        roots: editorRoots().map((node) => ({
          id: node.id || null,
          tag: node.tagName,
          html: cleanSnapshotHTML(node),
        })),
      };
    }
    function checkpoint() {
      undo.push(captureSnapshot());
      if (undo.length > 100) undo.shift();
      redo = [];
      updateEditorButtons();
    }
    function change(fn, options) {
      if (!options || options.history !== false) checkpoint();
      fn(state);
      state.savedAt = new Date().toISOString();
      mirror();
      renderAll();
      storeEditorDOM();
    }
    function restoreSnapshot(next) {
      state = clone(next.state);
      (next.roots || []).forEach((saved, index) => {
        const current = saved.id
          ? document.getElementById(saved.id)
          : editorRoots()[index];
        if (current) current.innerHTML = saved.html;
      });
      sanitizeEditorTokens(document);
      selection = [];
      gesture = null;
      textUndoArmed = false;
      mirror();
      applyTheme((state.review && state.review.theme) || initialTheme(), false);
      bindApproaches();
      bindUnits();
      bindDecisions();
      bindPopupDOM();
      installComposedForm();
      stampEditorOrigins();
      renderSelection();
      storeEditorDOM();
    }
    function doUndo() {
      if (!undo.length) return;
      redo.push(captureSnapshot());
      restoreSnapshot(undo.pop());
      updateEditorButtons();
    }
    function doRedo() {
      if (!redo.length) return;
      undo.push(captureSnapshot());
      restoreSnapshot(redo.pop());
      updateEditorButtons();
    }

    function initialTheme() {
      if (
        state.review &&
        (state.review.theme === "light" || state.review.theme === "dark")
      )
        return state.review.theme;
      return window.matchMedia &&
        window.matchMedia("(prefers-color-scheme: dark)").matches
        ? "dark"
        : "light";
    }
    function applyTheme(theme, persist) {
      const next = theme === "dark" ? "dark" : "light";
      root.setAttribute("data-theme", next);
      if (!state.review) state.review = {};
      state.review.theme = next;
      if (persist) {
        state.savedAt = new Date().toISOString();
        mirror();
      }
    }

    function installRestoreBanner() {
      let stored = null;
      try {
        stored = JSON.parse(localStorage.getItem(storageKey) || "null");
      } catch (_) {
        stored = null;
      }
      if (root.getAttribute("data-exported") === "client") {
        if (
          stored &&
          stored.schema === state.schema &&
          parseDate(stored.savedAt) > parseDate(state.savedAt) &&
          !same(stored, state)
        ) {
          state = stored;
          mirror();
          renderAll({ sync: false });
        }
        return;
      }
      if (
        !stored ||
        stored.schema !== state.schema ||
        parseDate(stored.savedAt) <= parseDate(state.savedAt) ||
        same(stored, state)
      )
        return;
      const banner = document.createElement("div");
      banner.className = "ld-restore";
      banner.id = "ld-restore-banner";
      banner.innerHTML =
        '<span>Restore unsaved changes?</span><button type="button" data-restore>Restore</button><button type="button" data-discard>Discard</button>';
      const bar = document.querySelector(".ld-bar");
      (bar ? bar.parentNode : document.body).insertBefore(
        banner,
        bar ? bar.nextSibling : document.body.firstChild,
      );
      banner.querySelector("[data-restore]").addEventListener("click", () => {
        state = stored;
        applyEditorDOM(readEditorDOM());
        mirror();
        banner.remove();
        renderAll({ sync: false });
        bindApproaches();
        bindUnits();
        bindDecisions();
        bindPopupDOM();
        installComposedForm();
        stampEditorOrigins();
        storeEditorDOM();
      });
      banner.querySelector("[data-discard]").addEventListener("click", () => {
        try {
          localStorage.removeItem(storageKey);
          localStorage.removeItem(domStorageKey);
        } catch (_) {}
        banner.remove();
        mirror();
      });
    }

    function unitState(id) {
      return state.units && state.units[id];
    }
    function selectedVariant(unit) {
      return unit && unit.variants ? unit.variants[unit.selected] : null;
    }
    function setVariant(id, variant) {
      const unit = unitState(id);
      if (!unit || !unit.variants || !unit.variants[variant]) return;
      change(() => {
        unit.selected = variant;
      });
    }
    function hasPageApproaches() {
      return Boolean(
        state.approaches &&
          state.approaches.a &&
          state.approaches.b &&
          document.querySelector("[data-approach='a']") &&
          document.querySelector("[data-approach='b']"),
      );
    }
    function currentApproach() {
      return state.review && state.review.approach === "b" ? "b" : "a";
    }
    function renderApproach() {
      if (!hasPageApproaches()) return;
      const selected = currentApproach();
      root.setAttribute("data-page-approach", selected);
      all("[data-approach]").forEach((page) => {
        page.hidden = page.getAttribute("data-approach") !== selected;
      });
      all("[data-select-approach]").forEach((button) => {
        button.setAttribute(
          "aria-pressed",
          button.getAttribute("data-select-approach") === selected
            ? "true"
            : "false",
        );
      });
    }
    function setApproach(approach) {
      if (!hasPageApproaches() || !["a", "b"].includes(approach)) return;
      if (!state.review) state.review = {};
      if (state.review.approach === approach) return;
      syncEditorDOMToState();
      state.review.approach = approach;
      state.review.location = 0;
      state.savedAt = new Date().toISOString();
      const nav = document.getElementById("ld-form-navigation");
      if (nav) nav.remove();
      renderAll({ sync: false });
      installComposedForm();
      mirror();
      storeEditorDOM();
    }
    function bindApproaches() {
      const control = document.getElementById("ld-page-approach");
      if (!control || control.__legalDesignApproachBound) return;
      control.__legalDesignApproachBound = true;
      control.addEventListener("click", (event) => {
        const button = event.target.closest("[data-select-approach]");
        if (!button) return;
        setApproach(button.getAttribute("data-select-approach"));
      });
      control.addEventListener("keydown", (event) => {
        if (!["ArrowLeft", "ArrowRight"].includes(event.key)) return;
        event.preventDefault();
        setApproach(event.key === "ArrowLeft" ? "a" : "b");
        const selected = control.querySelector(
          '[data-select-approach="' + currentApproach() + '"]',
        );
        if (selected) selected.focus();
      });
    }
    function pointerPart(value) {
      return String(value).replace(/~/g, "~0").replace(/\//g, "~1");
    }
    function pointerValue(rootValue, pointer, nextValue) {
      if (!rootValue || !pointer || pointer[0] !== "/") return false;
      const parts = pointer
        .slice(1)
        .split("/")
        .map((part) => part.replace(/~1/g, "/").replace(/~0/g, "~"));
      let current = rootValue;
      for (let index = 0; index < parts.length - 1; index += 1) {
        if (current == null || typeof current !== "object") return false;
        current = current[parts[index]];
      }
      if (current == null || typeof current !== "object") return false;
      current[parts[parts.length - 1]] = nextValue;
      return true;
    }
    function diagramLeaves(value, path, output) {
      const leaves = output || [];
      const pointer = path || "";
      if (typeof value === "string" || typeof value === "number") {
        leaves.push({
          path: pointer,
          value: String(value).replace(/\s+/g, " ").trim(),
          used: false,
        });
        return leaves;
      }
      if (Array.isArray(value)) {
        value.forEach((item, index) => {
          diagramLeaves(item, pointer + "/" + index, leaves);
        });
        return leaves;
      }
      if (value && typeof value === "object") {
        Object.keys(value).forEach((key) => {
          diagramLeaves(value[key], pointer + "/" + pointerPart(key), leaves);
        });
      }
      return leaves;
    }
    function diagramLabelValue(label) {
      const lines = all("tspan", label);
      return (
        lines.length
          ? lines.map((line) => line.textContent.trim()).join(" ")
          : label.textContent
      )
        .replace(/\s+/g, " ")
        .trim();
    }
    function annotateDiagramLabels(scope, params) {
      const leaves = diagramLeaves(params || {});
      all(".ld-diagram svg text", scope || document).forEach((label) => {
        label.setAttribute("data-diagram-label", "");
        label.setAttribute("data-editable", "");
        if (editing) {
          label.setAttribute("tabindex", "0");
          label.setAttribute("role", "textbox");
          label.setAttribute("aria-label", "Editable diagram label");
        } else {
          label.removeAttribute("tabindex");
          label.removeAttribute("role");
          label.removeAttribute("aria-label");
        }
        if (!label.hasAttribute("data-placeholder")) {
          label.setAttribute(
            "data-placeholder",
            diagramLabelValue(label) || PLACEHOLDERS.figureLabel,
          );
        }
        if (!label.hasAttribute("data-param-path")) {
          const value = diagramLabelValue(label);
          const match = leaves.find(
            (leaf) => !leaf.used && leaf.value === value,
          );
          if (match) {
            match.used = true;
            label.setAttribute("data-param-path", match.path);
          }
        }
      });
    }

    function renderFigure(section, unit) {
      all(".ld-variant", section).forEach((variant) => {
        const id = variant.getAttribute("data-variant");
        const spec = unit.variants && unit.variants[id];
        const host = variant.querySelector(".ld-diagram");
        if (!host || !spec || !spec.component) return;
        if (
          (host.hasAttribute("data-editor-preserve-dom") ||
            root.getAttribute("data-exported") === "client") &&
          host.querySelector("svg")
        ) {
          annotateDiagramLabels(host, spec.params);
          return;
        }
        // Page scaling is presentation only. Keep the authored diagram in its
        // desktop geometry when the complete 4:3 page is fitted on a phone.
        const hostWidth = host.closest(".ld-fixed-page")
          ? host.clientWidth
          : host.getBoundingClientRect().width;
        const narrow =
          hostWidth <
          (WIDE_FRAME_WIDTH * MIN_FIGURE_TEXT_PX) / WIDE_MIN_TEXT_PX;
        host.innerHTML = LD.render(spec.component, spec.params, {
          narrow: narrow,
          density: singleComposition() ? "compact" : "standard",
          ariaLabel: unit.claim,
        });
        const figure = host.querySelector("svg");
        if (!figure) return;
        annotateDiagramLabels(host, spec.params);
        figure.setAttribute("data-frame", narrow ? "narrow" : "wide");
        if (narrow && hostWidth > 480 && !host.closest(".ld-fixed-page")) {
          const sectionBox = section.getBoundingClientRect();
          const hostBox = host.getBoundingClientRect();
          const source = section.querySelector(".ld-source-note");
          let trailing = 0;
          if (source) {
            const style = getComputedStyle(source);
            trailing =
              source.getBoundingClientRect().height +
              (parseFloat(style.marginTop) || 0) +
              (parseFloat(style.marginBottom) || 0);
          }
          const sectionStyle = getComputedStyle(section);
          const available =
            sectionBox.bottom -
            hostBox.top -
            trailing -
            (parseFloat(sectionStyle.paddingBottom) || 0) -
            (parseFloat(sectionStyle.borderBottomWidth) || 0);
          const viewHeight = figure.viewBox.baseVal.height;
          const minimum =
            (viewHeight * (MIN_FIGURE_TEXT_PX + FIGURE_TEXT_ROUNDING_PX)) /
            WIDE_MIN_TEXT_PX;
          figure.style.height = Math.max(available, minimum) + "px";
          figure.style.maxHeight = "none";
        }
      });
    }

    function installInlineDetail(section, unit) {
      // A prose block is not one giant hyperlink. Move its authored target to
      // a short phrase, or a named detail link for older specifications. Added
      // editor text keeps its existing whole-object popup behavior.
      if (
        unit.kind !== "text" ||
        !section.matches(".ld-composed-unit[data-detail]")
      )
        return;
      const detail = section.getAttribute("data-detail");
      all(".ld-variant > [data-variant-body]", section).forEach((body) => {
        const button = document.createElement("button");
        button.type = "button";
        button.className = "ld-inline-detail";
        button.setAttribute("data-detail", detail);
        button.setAttribute("aria-haspopup", "dialog");
        button.setAttribute("aria-controls", detail);
        const anchor = unit.detailAnchor;
        const walker = document.createTreeWalker(body, NodeFilter.SHOW_TEXT);
        for (
          let node = walker.nextNode();
          anchor && node;
          node = walker.nextNode()
        ) {
          const at = node.textContent.indexOf(anchor);
          if (at < 0 || node.parentElement.closest("a,button")) continue;
          const match = node.splitText(at);
          match.splitText(anchor.length);
          button.textContent = anchor;
          match.replaceWith(button);
          break;
        }
        if (!button.parentNode) {
          const title = state.evidence?.[detail]?.popup?.title;
          button.textContent =
            title && title.length <= 72 ? title : "Read supporting detail";
          const line = document.createElement("p");
          line.className = "ld-detail-linkline";
          line.appendChild(button);
          body.appendChild(line);
        }
      });
      [
        "data-detail",
        "role",
        "tabindex",
        "aria-haspopup",
        "aria-controls",
      ].forEach((name) => {
        section.removeAttribute(name);
      });
      section.classList.remove("ld-popup-trigger");
    }

    function renderUnit(section) {
      const id = section.getAttribute("data-unit");
      const unit = unitState(id);
      if (!unit) return;
      all(".ld-variant", section).forEach((variant) => {
        const key = variant.getAttribute("data-variant");
        variant.hidden = key !== unit.selected;
        if (
          unit.edits &&
          unit.edits[key] != null &&
          unit.kind !== "figure" &&
          root.getAttribute("data-exported") !== "client"
        ) {
          const body = variant.querySelector("[data-variant-body]");
          if (body && body.innerHTML !== unit.edits[key])
            body.innerHTML = unit.edits[key];
        }
      });
      all(".ld-ab button", section).forEach((button) => {
        button.setAttribute(
          "aria-pressed",
          button.getAttribute("data-select-variant") === unit.selected
            ? "true"
            : "false",
        );
      });
      const why = section.querySelector(".ld-why");
      if (why) {
        const current = selectedVariant(unit);
        why.textContent = current && current.why ? current.why : "";
      }
      if (unit.kind === "figure") renderFigure(section, unit);
      installInlineDetail(section, unit);
    }

    function renderAll(options) {
      if (editing && (!options || options.sync !== false))
        syncEditorDOMToState();
      renderApproach();
      all("section.ld-unit[data-unit]").forEach(renderUnit);
      renderDecisions();
      applyTheme((state.review && state.review.theme) || initialTheme(), false);
      updateComposedForm();
    }

    function composedSections() {
      if (!root.hasAttribute("data-composed-shell")) return [];
      const selected = document.querySelector(
        '#legaldesign-composed-root > [data-approach="' +
          currentApproach() +
          '"]',
      );
      if (selected)
        return all(
          ":scope > .ld-composed-section[data-composition-section]",
          selected,
        );
      return all(
        "#legaldesign-composed-root > .ld-composed-section[data-composition-section]",
      );
    }
    function composedForm() {
      return state.brief && state.brief.form;
    }
    function singleComposition() {
      return state.sourceSchemaVersion === "legaldesign.build.v4";
    }
    function pageScaleFor(node) {
      return node && node.closest && node.closest(".ld-fixed-page")
        ? Number(root.style.getPropertyValue("--ld-page-scale")) || 1
        : 1;
    }
    function layoutViewport() {
      // WebKit may report the pinch-zoomed visual viewport through innerWidth
      // and innerHeight. Refitting to those values counteracts native zoom.
      // Root client dimensions describe the layout viewport, matching CSS.
      return {
        width: root.clientWidth || window.innerWidth,
        height: root.clientHeight || window.innerHeight,
      };
    }
    function onLayoutResize(callback) {
      let previous = layoutViewport();
      window.addEventListener("resize", () => {
        const next = layoutViewport();
        if (next.width === previous.width && next.height === previous.height)
          return;
        previous = next;
        callback();
      });
    }
    function fixedPageTargets() {
      if (root.hasAttribute("data-composed-shell")) {
        const approaches = all(
          "#legaldesign-composed-root > .ld-page-approach",
        );
        if (composedForm() === "one-page") return approaches;
        return all(".ld-page-approach > .ld-composed-section");
      }
      const slides = all("main > .sb-slide[data-frame]");
      if (slides.length) return slides;
      // Reference templates retain their own composition. The library is an
      // authoring catalogue, not a deliverable, and is intentionally excluded.
      return all("main.ld-page");
    }
    function paginateReferenceOverview() {
      if (root.hasAttribute("data-composed-shell")) return;
      const main = document.querySelector("main.ld-page");
      const core = main && main.querySelector(":scope > .ld-core");
      const figure = core && core.querySelector(":scope > .ld-figure");
      if (!main || !core || !figure || main.querySelector(":scope > .sb-slide"))
        return;
      // Old single-sheet references held overview, diagram and four detailed
      // cards in one tall area. Preserve every node, splitting at that existing
      // semantic boundary rather than squeezing the diagram or dropping copy.
      const overview = document.createElement("section");
      overview.className = "sb-slide is-current ld-reference-page";
      overview.setAttribute("data-frame", "1");
      const details = document.createElement("section");
      details.className = "sb-slide ld-reference-page";
      details.setAttribute("data-frame", "2");
      details.hidden = true;
      let afterCore = false;
      Array.from(main.children).forEach((node) => {
        if (node === core) {
          afterCore = true;
          overview.appendChild(figure);
          core.classList.add("ld-reference-cards");
          details.appendChild(core);
        } else (afterCore ? details : overview).appendChild(node);
      });
      main.append(overview, details);
      if (!main.id) main.id = "slide-stage";
    }
    function referenceEvidenceDetails() {
      const scrim = document.getElementById("popup-scrim");
      if (!scrim || root.hasAttribute("data-composed-shell")) return;
      all("main .finding-grid .evidence-panel").forEach((panel, index) => {
        const page = panel.closest(".sb-slide[data-frame]");
        const id =
          "reference-evidence-" +
          (page?.getAttribute("data-frame") || index + 1);
        const title =
          panel.querySelector("h2")?.textContent || "Evidence reviewed";
        if (document.getElementById(id)) return;
        state.evidence[id] = {
          cite: "",
          locator: "",
          excerpt: "",
          link: null,
          status: "authored",
          popup: { type: "detail", title, lede: "", sections: [] },
        };
        const pop = document.createElement("article");
        pop.id = id;
        pop.className = "pop";
        pop.hidden = true;
        pop.setAttribute("data-evidence-id", id);
        pop.setAttribute("role", "dialog");
        pop.setAttribute("aria-modal", "true");
        pop.setAttribute("aria-label", title);
        const close = document.createElement("button");
        close.className = "ld-popup-close";
        close.type = "button";
        close.textContent = "×";
        close.setAttribute("aria-label", "Close evidence review");
        pop.appendChild(close);
        const button = document.createElement("button");
        button.type = "button";
        button.className = "ld-control ld-reference-evidence";
        button.textContent = title;
        button.setAttribute("data-detail", id);
        button.setAttribute("aria-haspopup", "dialog");
        button.setAttribute("aria-controls", id);
        panel.replaceWith(button);
        pop.appendChild(panel);
        scrim.appendChild(pop);
      });
    }
    let pageFitFrame = 0;
    let pageFitInstalled = false;
    function openPageReader(trigger) {
      const page = visibleFixedPages()[0];
      const scrim = document.getElementById("popup-scrim");
      if (!page || !scrim) return;
      document.getElementById("ld-page-reader")?.remove();
      const reader = document.createElement("div");
      reader.id = "ld-page-reader";
      reader.className = "pop ld-page-reader";
      reader.setAttribute("role", "dialog");
      reader.setAttribute("aria-modal", "true");
      reader.setAttribute("aria-label", "Read current page");
      reader.innerHTML =
        '<button type="button" class="ld-popup-close" aria-label="Close page reader">×</button>';
      const nodes = Array.from(page.children);
      let readerId = 0;
      nodes.forEach((source) => {
        const copy = source.cloneNode(true);
        all("[hidden],.ld-unit-tools,.ld-why,[data-editor-only]", copy).forEach(
          (node) => {
            node.remove();
          },
        );
        [copy, ...all(".ld-unit[data-unit]", copy)].forEach((figure) => {
          const unit = unitState(figure.getAttribute("data-unit"));
          const variant = unit && selectedVariant(unit);
          if (
            variant &&
            variant.component &&
            !figure.querySelector("[data-editor-preserve-dom]")
          ) {
            const host = figure.querySelector(".ld-diagram");
            if (host)
              host.innerHTML = LD.render(variant.component, variant.params, {
                narrow: true,
                density: singleComposition() ? "compact" : "standard",
                ariaLabel: unit.claim,
              });
          }
        });
        const svgIds = new Map();
        all("[id]", copy)
          .filter((node) => node instanceof SVGElement)
          .forEach((node) => {
            const next = "ld-reader-svg-" + ++readerId;
            svgIds.set(node.id, next);
            node.id = next;
          });
        const rewriteSVGReference = (value) =>
          value.replace(
            /url\(\s*(['"]?)#([^)'"\s]+)\1\s*\)/g,
            (whole, quote, id) =>
              svgIds.has(id) ? "url(#" + svgIds.get(id) + ")" : whole,
          );
        [copy, ...all("*", copy)].forEach((node) => {
          if (
            node.matches(
              '.ld-composed-unit[data-kind="text"]:is([data-detail],[data-evidence])',
            )
          )
            all(":scope > .ld-variant > [data-variant-body]", node).forEach(
              (body) => {
                body.classList.add("ld-reader-popup-text");
              },
            );
          if (!(node instanceof SVGElement)) node.removeAttribute("id");
          if (node instanceof SVGElement) {
            Array.from(node.attributes).forEach((attribute) => {
              let value = rewriteSVGReference(attribute.value);
              if (
                ["href", "xlink:href"].includes(attribute.name) &&
                value.startsWith("#") &&
                svgIds.has(value.slice(1))
              )
                value = "#" + svgIds.get(value.slice(1));
              if (value !== attribute.value)
                node.setAttribute(attribute.name, value);
            });
            if (node.tagName.toLowerCase() === "style")
              node.textContent = rewriteSVGReference(node.textContent);
          }
          node.removeAttribute("contenteditable");
          node.removeAttribute("spellcheck");
          node.removeAttribute("tabindex");
          [
            "aria-controls",
            "aria-labelledby",
            "aria-describedby",
            "for",
          ].forEach((name) => {
            node.removeAttribute(name);
          });
          Array.from(node.attributes).forEach((attribute) => {
            if (
              attribute.name.startsWith("data-") &&
              !["data-detail", "data-evidence", "data-section-target"].includes(
                attribute.name,
              )
            )
              node.removeAttribute(attribute.name);
          });
          if (node.classList.contains("ld-unit"))
            node.classList.add("ld-reader-unit");
          node.classList.remove(
            "ld-unit",
            "ld-composed-unit",
            "ld-fixed-page",
            "ld-popup-trigger",
            "ld-selected",
            "ld-selection-mark",
          );
          if (!(node instanceof SVGElement)) node.removeAttribute("style");
        });
        all(
          "input,textarea,select,button:not([data-detail]):not([data-evidence]):not([data-section-target])",
          copy,
        ).forEach((control) => {
          const text = document.createElement("p");
          text.textContent = control.matches("input,textarea,select")
            ? (control.getAttribute("aria-label") ||
                control.getAttribute("placeholder") ||
                "Response") +
              ": " +
              (control.value || "—")
            : control.textContent +
              (control.getAttribute("aria-pressed") === "true"
                ? " — selected"
                : "");
          control.replaceWith(text);
        });
        copy.className = "ld-reader-section";
        reader.appendChild(copy);
      });
      scrim.appendChild(reader);
      installSectionLinks();
      bindPopupDOM();
      openPopup(reader.id, trigger);
      all(".ld-diagram svg", reader).forEach((svg) => {
        const frameWidth = svg.viewBox.baseVal.width || 400;
        const fontSizes = all("text", svg)
          .map((text) => parseFloat(getComputedStyle(text).fontSize))
          .filter((size) => size > 0);
        const smallest = fontSizes.length ? Math.min(...fontSizes) : 13;
        const available = svg.parentElement.clientWidth;
        // Fit ordinary narrow diagrams completely. Pan only when the actual
        // component would otherwise put a label below 11 screen pixels.
        svg.style.width =
          Math.max(available, (frameWidth * 11) / smallest) + "px";
        svg.style.height = "auto";
        svg.style.maxHeight = "none";
      });
      updatePopupScrollHint(reader);
    }
    function installPageReader() {
      let button = document.getElementById("ld-read-page");
      if (!button) {
        button = document.createElement("button");
        button.id = "ld-read-page";
        button.className = "ld-control ld-read-page";
        button.type = "button";
        button.innerHTML =
          '<svg viewBox="0 0 24 24" width="20" height="20" aria-hidden="true" focusable="false"><path d="M12 5.5C9 3.5 5.5 3.5 2 4.5v14c3.5-1 7-1 10 1 3-2 6.5-2 10-1v-14c-3.5-1-7-1-10 1Zm0 0v14" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>';
        button.setAttribute("aria-label", "Read page");
        button.title = "Read page — larger text and diagram details";
        button.setAttribute("aria-haspopup", "dialog");
        button.hidden = true;
        document.body.appendChild(button);
      }
      if (!button.__legalDesignReaderBound) {
        button.__legalDesignReaderBound = true;
        button.addEventListener("click", () => openPageReader(button));
      }
    }
    function visibleFixedPages() {
      return all(".ld-fixed-page").filter(
        (page) =>
          !page.closest("[hidden]") &&
          getComputedStyle(page).display !== "none",
      );
    }
    function pageOverflow(page, diagnostics) {
      const bounds = page.getBoundingClientRect();
      const tolerance = Math.max(1, pageScaleFor(page) * 2);
      const outside = (box) =>
        box.width > 0 &&
        box.height > 0 &&
        (box.left < bounds.left - tolerance ||
          box.right > bounds.right + tolerance ||
          box.top < bounds.top - tolerance ||
          box.bottom > bounds.bottom + tolerance);
      const concealedByDisclosure = (node) => {
        let closed = node.parentElement?.closest("details:not([open])");
        while (closed && page.contains(closed)) {
          if (!closed.querySelector(":scope > summary")?.contains(node))
            return true;
          closed = closed.parentElement?.closest("details:not([open])");
        }
        return false;
      };
      const candidates = all("*", page).filter(
        (node) =>
          !node.closest("[hidden],[data-editor-only],.ld-unit-tools") &&
          !concealedByDisclosure(node) &&
          !node.matches("script,style") &&
          node.getClientRects().length,
      );
      if (
        candidates.some((node) => {
          const box = node.getBoundingClientRect();
          const style = getComputedStyle(node);
          const clipsContent =
            !(node instanceof SVGElement) &&
            ((/auto|scroll|hidden|clip/.test(style.overflowY) &&
              node.scrollHeight > node.clientHeight + 2) ||
              (/auto|scroll|hidden|clip/.test(style.overflowX) &&
                node.scrollWidth > node.clientWidth + 2));
          const invalid = outside(box) || clipsContent;
          if (invalid && diagnostics)
            diagnostics.push({
              page: page.id || page.getAttribute("data-composition-section"),
              tag: node.tagName,
              className: node.getAttribute("class"),
              kind: clipsContent ? "internal clipping" : "box outside frame",
              height: node.clientHeight,
              scrollHeight: node.scrollHeight,
              width: node.clientWidth,
              scrollWidth: node.scrollWidth,
              top: box.top - bounds.top,
              bottom: box.bottom - bounds.top,
            });
          return invalid;
        })
      )
        return true;
      // A fixed-height text box can have in-bounds edges while its ink spills
      // beyond the page. Check actual text geometry as well as container boxes.
      return candidates.some((node) =>
        Array.from(node.childNodes).some((child) => {
          if (child.nodeType !== Node.TEXT_NODE || !child.textContent.trim())
            return false;
          const range = document.createRange();
          range.selectNodeContents(child);
          const box = range.getBoundingClientRect();
          const invalid = outside(box);
          if (invalid && diagnostics)
            diagnostics.push({
              page: page.id || page.dataset.compositionSection,
              tag: node.tagName,
              className: node.getAttribute("class"),
              kind: "text ink outside frame",
              top: box.top - bounds.top,
              bottom: box.bottom - bounds.top,
            });
          return invalid;
        }),
      );
    }
    function validatePageFit(showNotice = true) {
      const invalid = visibleFixedPages().some((page) => pageOverflow(page));
      root.toggleAttribute("data-page-overflow", invalid);
      let notice = document.getElementById("ld-page-fit-error");
      if (invalid && showNotice && !notice) {
        notice = document.createElement("div");
        notice.id = "ld-page-fit-error";
        notice.className = "ld-page-fit-error";
        notice.setAttribute("data-editor-chrome", "");
        notice.setAttribute("role", "alert");
        notice.textContent =
          "This page has more content than the available window can hold. Move detail into a popup or split it into another page before exporting. Your content has not been removed.";
        document.body.appendChild(notice);
      }
      if (notice && !invalid && !notice.hasAttribute("data-export-fit-error"))
        notice.remove();
      return !invalid;
    }
    function validateExportPageFit(includeOtherApproaches) {
      const issues = [];
      const pages = fixedPageTargets().filter((page) => {
        const approach = page.closest("[data-approach]");
        return (
          includeOtherApproaches ||
          !approach ||
          approach.dataset.approach === currentApproach()
        );
      });
      const changed = new Map();
      const reveal = (node) => {
        if (!node || changed.has(node)) return;
        changed.set(node, { hidden: node.hidden, display: node.style.display });
        node.hidden = false;
        if (getComputedStyle(node).display === "none")
          node.style.display = "block";
      };
      let valid = true;
      try {
        for (const page of pages) {
          reveal(page.closest("[data-approach]"));
          reveal(page);
          // Hidden approach figures have no measurable width until revealed.
          // Keep authored SVG edits; only state-driven figures are rendered.
          all('.ld-unit[data-kind="figure"]', page).forEach((section) => {
            const unit = unitState(section.getAttribute("data-unit"));
            if (unit) renderFigure(section, unit);
          });
          if (pageOverflow(page, issues)) valid = false;
          // A collapsed dashboard must not conceal a group that cannot fit.
          const groups = all("details.ld-overview-group", page);
          const openStates = groups.map((group) => group.open);
          try {
            for (const active of groups) {
              groups.forEach((group) => {
                group.open = group === active;
              });
              if (pageOverflow(page, issues)) valid = false;
            }
          } finally {
            groups.forEach((group, index) => {
              group.open = openStates[index];
            });
          }
        }
      } finally {
        changed.forEach((prior, node) => {
          node.hidden = prior.hidden;
          node.style.display = prior.display;
          if (!node.getAttribute("style")) node.removeAttribute("style");
        });
      }
      LD.lastPageFitIssues = issues;
      if (!valid) {
        let notice = document.getElementById("ld-page-fit-error");
        if (!notice) {
          notice = document.createElement("div");
          notice.id = "ld-page-fit-error";
          notice.className = "ld-page-fit-error";
          notice.setAttribute("data-editor-chrome", "");
          notice.setAttribute("role", "alert");
          document.body.appendChild(notice);
        }
        notice.textContent =
          "An output page exceeds its available window. Review every page and move detail into a popup or another page before exporting. Nothing has been deleted.";
        notice.setAttribute("data-export-fit-error", "");
      } else {
        const notice = document.getElementById("ld-page-fit-error");
        if (notice) notice.remove();
      }
      return valid;
    }
    function fitFixedPages() {
      pageFitFrame = 0;
      if (!root.hasAttribute("data-fixed-pages")) return;
      const viewport = layoutViewport();
      const bar = document.querySelector(".ld-bar");
      const approach = document.getElementById("ld-page-approach");
      const toolbarBottom = bar
        ? Math.max(0, bar.getBoundingClientRect().bottom)
        : 0;
      const approachHeight =
        approach && !approach.hidden ? approach.offsetHeight + 10 : 0;
      const rail = document.querySelector("nav.rail");
      const railRight =
        rail && rail.getClientRects().length
          ? rail.getBoundingClientRect().right + 12
          : 0;
      const leftGutter = root.hasAttribute("data-has-slide-index")
        ? viewport.width <= 760
          ? 68
          : 214
        : 12;
      const stable =
        root.hasAttribute("data-composed-shell") && singleComposition();
      root.toggleAttribute("data-stable-type", stable);
      const availableWidth = Math.max(
        1,
        viewport.width - Math.max(leftGutter, railRight) - 12,
      );
      // Desktop type is a reading size, not a percentage of the window. Limit
      // the combined rail/page measure; extra width becomes outer margin.
      const width =
        stable && viewport.width > 760
          ? Math.min(availableWidth, 1440)
          : availableWidth;
      const shellOffset = (availableWidth - width) / 2;
      const pageLeftGutter = Math.max(leftGutter, railRight) + shellOffset;
      root.style.setProperty("--ld-index-left", shellOffset + "px");
      const resultTop = toolbarBottom + 10 + approachHeight;
      root.style.setProperty("--ld-result-top", resultTop + "px");
      const result = document.querySelector(".ld-save-banner");
      const resultHeight =
        result && result.getClientRects().length
          ? result.getBoundingClientRect().height + 10
          : 0;
      const top = resultTop + resultHeight;
      const reader = document.getElementById("ld-read-page");
      // The book reader is always available, not a substitute for missing
      // content. Its reserved corner belongs to chrome, not the page itself.
      if (reader) reader.hidden = false;
      const readerReserve = stable && viewport.width > 760 ? 0 : 52;
      const height = Math.max(1, viewport.height - top - 12 - readerReserve);
      let scale = Math.min(width / 1200, height / 900, 1);
      if (stable && viewport.width > 760) scale = 1;
      const baselineScale = scale;
      // On short desktop windows, a sparse page need not become a thumbnail
      // simply because the planning reference is 900px tall. Try a readable
      // coordinate scale against actual content, leaving the approved roomier
      // layouts and phone overview unchanged. Never crop to claim a fit.
      // Authored reference pages have pinned footers and their own spacing;
      // preserve that approved coordinate scale. This content-fit pass is for
      // the flow-layout composer, whose children participate in natural flow.
      if (
        root.hasAttribute("data-composed-shell") &&
        !stable &&
        viewport.width > 760 &&
        scale < 0.8
      ) {
        const candidate = Math.min(width / 1200, 1);
        for (let trial = candidate; trial > baselineScale; trial -= 0.02) {
          root.style.setProperty("--ld-page-scale", String(trial));
          root.style.setProperty("--ld-page-width", width / trial + "px");
          root.style.setProperty("--ld-page-height", height / trial + "px");
          const changed = new Map();
          const reveal = (node) => {
            if (!node || changed.has(node)) return;
            changed.set(node, {
              hidden: node.hidden,
              display: node.style.display,
            });
            node.hidden = false;
            if (getComputedStyle(node).display === "none")
              node.style.display = "block";
          };
          let fits = true;
          try {
            for (const page of fixedPageTargets()) {
              reveal(page.closest("[data-approach]"));
              reveal(page);
              if (pageOverflow(page)) {
                fits = false;
                break;
              }
            }
          } finally {
            changed.forEach((prior, node) => {
              node.hidden = prior.hidden;
              node.style.display = prior.display;
            });
          }
          if (fits) {
            scale = trial;
            break;
          }
        }
      }
      root.style.setProperty("--ld-page-scale", String(scale));
      // Keep one uniform coordinate scale for persisted edits, but let each
      // dimension fill the window independently. 1200×900 remains a design
      // reference, never a letterboxed aspect-ratio constraint.
      root.style.setProperty("--ld-page-width", width / scale + "px");
      root.style.setProperty("--ld-page-height", height / scale + "px");
      root.style.setProperty("--ld-page-left", pageLeftGutter + "px");
      root.style.setProperty("--ld-page-top", top + "px");
      root.setAttribute("data-page-layout", "adaptive");
      root.style.setProperty("--ld-approach-top", toolbarBottom + 6 + "px");
      validatePageFit();
      alignSlideIndex();
      // Saved/client SVG is kept verbatim rather than rerendered. In Chromium,
      // a changed ancestor scale can leave its glyph paint transform stale even
      // when getBBox is correct. Invalidate that paint after layout settles,
      // using an identity transform and restoring every authored attribute.
      all(".ld-fixed-page .ld-diagram svg text").forEach((label) => {
        const original = label.getAttribute("transform");
        label.setAttribute("transform", (original || "") + " translate(0 0)");
        label.getBoundingClientRect();
        if (original === null) label.removeAttribute("transform");
        else label.setAttribute("transform", original);
      });
      if (editing) renderSelection();
    }
    function schedulePageFit() {
      if (!pageFitFrame) pageFitFrame = requestAnimationFrame(fitFixedPages);
    }
    function alignSlideIndex() {
      const nav = document.getElementById("ld-form-navigation");
      if (!nav || !root.hasAttribute("data-composed-shell")) return;
      const viewport = layoutViewport();
      if (viewport.width <= 760) {
        nav.style.removeProperty("height");
        nav.style.removeProperty("--ld-index-top");
        return;
      }
      const pages = visibleFixedPages();
      const bodies = pages.flatMap((page) => all(".ld-composed-grid", page));
      if (!bodies.length) return;
      const top = Math.min(
        ...bodies.map((node) => node.getBoundingClientRect().top),
      );
      const bottom = Math.max(
        ...bodies.map((node) => node.getBoundingClientRect().bottom),
      );
      // Align the rail to the actual explanation, including its scope footer,
      // not to an empty viewport-sized page box. Long indices scroll inside it.
      nav.style.setProperty("--ld-index-top", top + "px");
      nav.style.height =
        Math.max(44, Math.min(bottom, viewport.height - 12) - top) + "px";
    }
    function installFixedPages() {
      paginateReferenceOverview();
      referenceEvidenceDetails();
      const targets = fixedPageTargets();
      if (!targets.length) return;
      root.setAttribute("data-fixed-pages", "4:3");
      installPageReader();
      all(".ld-fixed-page").forEach((page) => {
        if (!targets.includes(page)) page.classList.remove("ld-fixed-page");
      });
      targets.forEach((page) => {
        page.classList.add("ld-fixed-page");
        if (!page.querySelector("h1,h2")) {
          const heading = document.createElement("h1");
          heading.className = "ld-page-heading";
          heading.setAttribute("data-editable", "");
          heading.setAttribute(
            "data-placeholder",
            "[Title for the subjects covered on this page.]",
          );
          heading.textContent =
            page.getAttribute("aria-label") ||
            state.brief?.title ||
            "Supporting details";
          page.prepend(heading);
        }
      });
      if (!pageFitInstalled) {
        pageFitInstalled = true;
        onLayoutResize(schedulePageFit);
        const bar = document.querySelector(".ld-bar");
        if (bar && typeof ResizeObserver !== "undefined")
          new ResizeObserver(schedulePageFit).observe(bar);
        new MutationObserver((records) => {
          if (
            records.some(
              (record) =>
                !record.target.closest ||
                !record.target.closest(
                  "#ld-selection-box,#ld-editor-layer,#ld-page-fit-error,#ld-page-reader",
                ),
            )
          )
            schedulePageFit();
        }).observe(document.body, {
          subtree: true,
          childList: true,
          characterData: true,
        });
        document.addEventListener("toggle", schedulePageFit, true);
        document.fonts && document.fonts.ready.then(schedulePageFit);
      }
      // Resolve the viewport transform before renderAll creates SVG text.
      // Painting text first at the reference scale then changing its ancestor
      // scale can leave Chromium's glyph paint cache at the old coordinates.
      // Geometry APIs still report correct boxes, masking the error until hover.
      if (pageFitFrame) cancelAnimationFrame(pageFitFrame);
      fitFixedPages();
    }
    LD.checkPageFit = (options = {}) => ({
      valid: options.allPages ? validateExportPageFit(true) : validatePageFit(),
      ratio: "adaptive",
      authoringReference: "4:3",
      width: parseFloat(root.style.getPropertyValue("--ld-page-width")) || 1200,
      height:
        parseFloat(root.style.getPropertyValue("--ld-page-height")) || 900,
      visiblePages: visibleFixedPages().length,
    });
    function composedLocation(sectionCount) {
      const location = state.review && state.review.location;
      return Number.isInteger(location)
        ? Math.max(0, Math.min(sectionCount - 1, location))
        : 0;
    }
    function composedIndexItems(sections) {
      return sections.map((section, index) => {
        const heading = section.querySelector("h1,h2,h3");
        return {
          id: section.getAttribute("data-composition-section"),
          label:
            section.getAttribute("data-index-label") ||
            (heading && heading.textContent.trim()) ||
            section.getAttribute("aria-label") ||
            "Section " + (index + 1),
        };
      });
    }
    function composedIndexGroups() {
      const overview = state.overview;
      if (!overview?.groups?.length) return [];
      const targets = new Map(
        (overview.topics || []).map((topic) => [
          topic.unitId,
          topic.targetSectionId,
        ]),
      );
      return overview.groups.map((group) => ({
        label: group.label,
        sectionIds: group.topicUnitIds
          .map((id) => targets.get(id))
          .filter(Boolean),
      }));
    }
    function updateComposedForm() {
      installSectionLinks();
      installFixedPages();
      const sections = composedSections();
      if (sections.length < 2) return;
      const form = composedForm();
      const location = composedLocation(sections.length);
      sections.forEach((section, index) => {
        section.hidden = form !== "one-page" && index !== location;
      });
      // Hidden slide hosts have zero width. Resolve a newly visible figure
      // against its real page width so first navigation and resize agree.
      // renderFigure retains authored/edit-preserved and client SVG verbatim.
      sections
        .filter((section) => !section.hidden)
        .forEach((section) => {
          all("section.ld-unit[data-unit]", section).forEach((node) => {
            const unit = unitState(node.getAttribute("data-unit"));
            if (unit?.kind === "figure") renderFigure(node, unit);
          });
        });
      const nav = document.getElementById("ld-form-navigation");
      if (!nav) return;
      if (
        nav.__legalDesignIndexSignature !==
        JSON.stringify({
          form,
          items: composedIndexItems(sections),
          groups: composedIndexGroups(),
        })
      ) {
        installComposedForm();
        return;
      }
      if (form !== "one-page") {
        const status = nav.querySelector("[data-walkthrough-status]");
        const previous = nav.querySelector("[data-walkthrough-previous]");
        const next = nav.querySelector("[data-walkthrough-next]");
        if (status) status.textContent = location + 1 + " / " + sections.length;
        if (previous) previous.disabled = location === 0;
        if (next) next.disabled = location === sections.length - 1;
      }
      all("[data-report-section]", nav).forEach((button) => {
        button.setAttribute(
          "aria-current",
          Number(button.dataset.reportSection) === location ? "true" : "false",
        );
      });
      // Navigation, not repaint, reveals the current subject. A reader may
      // still close that group or inspect another without it snapping back.
      if (nav.__legalDesignGroupLocation !== location) {
        all(".ld-index-group", nav).forEach((group) => {
          group.open = Boolean(group.querySelector('[aria-current="true"]'));
        });
        nav.__legalDesignGroupLocation = location;
      }
    }
    let sectionLinksBound = false;
    function installSectionLinks() {
      installOverviewGroups();
      all("[data-section-target]").forEach((node) => {
        node.setAttribute("role", "link");
        node.setAttribute("tabindex", "0");
      });
      if (sectionLinksBound) return;
      sectionLinksBound = true;
      const activate = (event) => {
        const target = event.target.closest("[data-section-target]");
        if (!target || editing) return;
        if (event.type === "keydown" && !["Enter", " "].includes(event.key))
          return;
        const sections = composedSections();
        const index = sections.findIndex(
          (section) =>
            section.dataset.compositionSection === target.dataset.sectionTarget,
        );
        if (index < 0) return;
        event.preventDefault();
        const fromReader = Boolean(target.closest("#ld-page-reader"));
        if (activePopup()) closePopup();
        setComposedLocation(index);
        if (fromReader) {
          openPageReader(document.getElementById("ld-read-page"));
          return;
        }
        const heading = sections[index].querySelector("h1,h2,h3");
        if (heading) {
          heading.setAttribute("tabindex", "-1");
          heading.focus({ preventScroll: true });
        }
      };
      document.addEventListener("click", activate);
      document.addEventListener("keydown", activate);
    }
    let overviewGroupsBound = false;
    function installOverviewGroups() {
      if (overviewGroupsBound) return;
      overviewGroupsBound = true;
      // Native keyboard activation also emits click. Apply the complete
      // one-open transition synchronously: a delayed toggle from a freshly
      // cloned reader must not overrule a newer user choice.
      document.addEventListener("click", (event) => {
        const summary = event.target.closest(".ld-overview-group > summary");
        if (!summary) return;
        const group = summary.parentElement;
        const container = group.closest(".ld-overview-groups");
        if (!container) return;
        event.preventDefault();
        const opening = !group.open;
        if (opening)
          all(":scope > details.ld-overview-group", container).forEach(
            (peer) => {
              if (peer !== group) peer.open = false;
            },
          );
        group.open = opening;
      });
      document.addEventListener(
        "toggle",
        (event) => {
          const group = event.target;
          if (!group.matches?.("details.ld-overview-group")) return;
          const container = group.closest(".ld-overview-groups");
          if (!container) return;
          if (!container.closest("#ld-page-reader")) storeEditorDOM();
          schedulePageFit();
        },
        true,
      );
    }
    function setComposedLocation(index, options) {
      const sections = composedSections();
      if (!sections.length) return;
      const next = Math.max(0, Math.min(sections.length - 1, index));
      if (!state.review) state.review = {};
      state.review.location = next;
      state.savedAt = new Date().toISOString();
      mirror();
      updateComposedForm();
      storeEditorDOM();
      schedulePageFit();
    }
    function installComposedForm() {
      installLegacySlideIndex();
      const sections = composedSections();
      const form = composedForm();
      if (
        sections.length < 2 ||
        !["slide-brief", "walkthrough", "report"].includes(form)
      ) {
        if (root.hasAttribute("data-composed-shell")) {
          const stale = document.getElementById("ld-form-navigation");
          if (stale) stale.remove();
          root.removeAttribute("data-has-slide-index");
        }
        return;
      }
      const main = document.getElementById("legaldesign-composed-root");
      let nav = document.getElementById("ld-form-navigation");
      const items = composedIndexItems(sections);
      const groups = composedIndexGroups();
      const signature = JSON.stringify({ form, items, groups });
      // Exported/restored navigation is presentation only. Rebuild it from the
      // active approach so a reset template cannot retain another page's index.
      if (nav && nav.__legalDesignIndexSignature !== signature) {
        nav.remove();
        nav = null;
      }
      if (!nav) {
        nav = document.createElement("nav");
        nav.id = "ld-form-navigation";
        nav.className = "ld-form-nav ld-slide-index";
        nav.setAttribute(
          "aria-label",
          form === "report" ? "Report sections" : "Slide navigation",
        );
        nav.innerHTML =
          '<button class="ld-control ld-index-toggle" type="button" data-index-toggle aria-controls="ld-index-list" aria-expanded="true">Contents</button>' +
          '<div id="ld-index-list" data-index-list></div>';
        const list = nav.querySelector("[data-index-list]");
        const makeEntry = (item, index) => {
          const button = document.createElement("button");
          button.className = "ld-control";
          button.type = "button";
          button.setAttribute("data-report-section", String(index));
          const number = document.createElement("span");
          number.className = "ld-index-number";
          number.setAttribute("aria-hidden", "true");
          number.textContent = String(index + 1);
          const label = document.createElement("span");
          label.className = "ld-index-label";
          label.textContent = item.label;
          button.append(number, label);
          return button;
        };
        const groupedIds = new Set(groups.flatMap((group) => group.sectionIds));
        items.forEach((item, index) => {
          if (!groupedIds.has(item.id))
            list.appendChild(makeEntry(item, index));
        });
        groups.forEach((group) => {
          const details = document.createElement("details");
          details.className = "ld-index-group";
          const summary = document.createElement("summary");
          summary.textContent = group.label;
          details.appendChild(summary);
          const entries = document.createElement("div");
          entries.className = "ld-index-group-items";
          group.sectionIds.forEach((id) => {
            const index = items.findIndex((item) => item.id === id);
            if (index >= 0) entries.appendChild(makeEntry(items[index], index));
          });
          details.appendChild(entries);
          summary.addEventListener("click", (event) => {
            event.preventDefault();
            const opening = !details.open;
            all(".ld-index-group", list).forEach((peer) => {
              peer.open = false;
            });
            details.open = opening;
          });
          list.appendChild(details);
        });
        if (form !== "one-page") {
          const pager = document.createElement("div");
          pager.className = "ld-index-pager";
          pager.innerHTML =
            '<button class="ld-control" type="button" data-walkthrough-previous>Previous</button>' +
            '<span data-walkthrough-status aria-live="polite"></span>' +
            '<button class="ld-control" type="button" data-walkthrough-next>Next</button>';
          (singleComposition() ? nav : list).appendChild(pager);
        }
        main.insertBefore(nav, main.firstChild);
      }
      nav.__legalDesignIndexSignature = signature;
      installSlideIndexToggle(nav);
      if (!nav.__legalDesignFormBound) {
        nav.__legalDesignFormBound = true;
        const previous = nav.querySelector("[data-walkthrough-previous]");
        const next = nav.querySelector("[data-walkthrough-next]");
        if (previous)
          previous.addEventListener("click", () => {
            setComposedLocation(composedLocation(sections.length) - 1);
          });
        if (next)
          next.addEventListener("click", () => {
            setComposedLocation(composedLocation(sections.length) + 1);
          });
        all("[data-report-section]", nav).forEach((button) => {
          button.addEventListener("click", () => {
            setComposedLocation(
              Number(button.getAttribute("data-report-section")),
              {
                scroll: form === "report",
              },
            );
          });
        });
      }
      updateComposedForm();
    }

    function installSlideIndexToggle(nav) {
      root.setAttribute("data-has-slide-index", "true");
      const toggle = nav.querySelector("[data-index-toggle]");
      const list = nav.querySelector("[data-index-list]");
      if (!toggle || !list || nav.__legalDesignIndexBound) return;
      nav.__legalDesignIndexBound = true;
      const phone = window.matchMedia("(max-width: 760px)");
      const setExpanded = (expanded) => {
        nav.setAttribute("data-index-expanded", expanded ? "true" : "false");
        toggle.setAttribute("aria-expanded", expanded ? "true" : "false");
        toggle.textContent = expanded ? "Contents" : "☰";
        toggle.setAttribute(
          "aria-label",
          expanded ? "Collapse contents" : "Open contents",
        );
        list.hidden = !expanded;
      };
      setExpanded(!phone.matches);
      toggle.addEventListener("click", () => {
        setExpanded(toggle.getAttribute("aria-expanded") !== "true");
      });
      nav.addEventListener("click", (event) => {
        if (phone.matches && event.target.closest("[data-report-section]"))
          setExpanded(false);
      });
      phone.addEventListener("change", () => setExpanded(!phone.matches));
      const bar = document.querySelector(".ld-bar");
      if (bar && typeof ResizeObserver !== "undefined") {
        const position = () =>
          nav.style.setProperty(
            "--ld-index-top",
            Math.ceil(bar.getBoundingClientRect().height + 16) + "px",
          );
        position();
        const observer = new ResizeObserver(position);
        observer.observe(bar);
      }
    }

    function installLegacySlideIndex() {
      if (root.hasAttribute("data-composed-shell")) return;
      const slides = all("section.sb-slide[data-frame]");
      const stage = document.getElementById("slide-stage");
      if (slides.length < 2 || !stage) return;
      let nav = document.getElementById("ld-legacy-slide-index");
      if (!nav) {
        nav = document.createElement("nav");
        nav.id = "ld-legacy-slide-index";
        nav.className = "ld-slide-index";
        nav.setAttribute("aria-label", "Slide navigation");
        nav.innerHTML =
          '<button class="ld-control ld-index-toggle" type="button" data-index-toggle aria-controls="ld-legacy-index-list" aria-expanded="true">Contents</button><div id="ld-legacy-index-list" data-index-list></div>';
        const list = nav.querySelector("[data-index-list]");
        slides.forEach((slide, index) => {
          const button = document.createElement("button");
          button.type = "button";
          button.className = "ld-control";
          button.setAttribute("data-report-section", String(index));
          list.appendChild(button);
        });
        const pager = document.querySelector(".sb-navigation");
        if (pager) list.appendChild(pager);
        stage.before(nav);
      }
      installSlideIndexToggle(nav);
      if (nav.__legalDesignLegacyBound) return;
      nav.__legalDesignLegacyBound = true;
      const refresh = () => {
        all("[data-report-section]", nav).forEach((button, index) => {
          const heading = slides[index].querySelector(
            ".ld-variant:not([hidden]) h1,h2,h3",
          );
          button.textContent =
            (heading && heading.textContent.trim()) || "Slide " + (index + 1);
          button.setAttribute(
            "aria-current",
            slides[index].classList.contains("is-current") ? "true" : "false",
          );
        });
      };
      nav.addEventListener("click", (event) => {
        const button = event.target.closest("[data-report-section]");
        if (!button) return;
        const frame = Number(button.getAttribute("data-report-section")) + 1;
        if (window.LegalDesignWalkthrough)
          window.LegalDesignWalkthrough.showFrame(frame, true);
        else {
          const original = document.querySelector(
            '[data-frame-button="' + frame + '"]',
          );
          if (original) original.click();
          else {
            slides.forEach((slide, index) => {
              slide.hidden = index !== frame - 1;
              slide.classList.toggle("is-current", index === frame - 1);
            });
            if (!state.review) state.review = {};
            state.review.location = frame - 1;
            mirror();
            schedulePageFit();
          }
        }
        refresh();
      });
      const observer = new MutationObserver(refresh);
      observer.observe(stage, {
        attributes: true,
        attributeFilter: ["class", "hidden"],
        childList: true,
        characterData: true,
        subtree: true,
      });
      refresh();
    }

    function syncUnitEdit(section) {
      if (!section || !section.isConnected) return;
      const id = section.getAttribute("data-unit");
      const unit = unitState(id);
      if (!unit || unit.kind === "figure") return;
      if (!unit.edits) unit.edits = {};
      all(".ld-variant[data-variant]", section).forEach((variant) => {
        const body = variant.querySelector("[data-variant-body]");
        if (body)
          unit.edits[variant.getAttribute("data-variant")] = body.innerHTML;
      });
    }
    function syncEvidenceEdit(pop) {
      if (!pop || !pop.isConnected) return;
      const id = pop.getAttribute("data-evidence-id") || pop.id;
      const item = state.evidence && state.evidence[id];
      if (!item) return;
      all("[data-evidence-field]", pop).forEach((node) => {
        const field = node.getAttribute("data-evidence-field");
        const value = node.textContent.trim();
        if (!value) return;
        if (["cite", "locator", "detail"].includes(field)) item[field] = value;
        else if (field === "popup.title" && item.popup)
          item.popup.title = value;
        else if (field === "popup.lede" && item.popup) item.popup.lede = value;
        else {
          const match = /^popup\.sections\.(\d+)\.(heading|body)$/.exec(field);
          const section =
            match && item.popup && Array.isArray(item.popup.sections)
              ? item.popup.sections[Number(match[1])]
              : null;
          if (!section) return;
          section[match[2]] = value;
        }
        if (field === "detail") item.excerpt = value;
        if (field === "locator")
          item.provenance = (item.sourceId || "source") + " · " + value;
      });
    }
    function syncEditorDOMToState(nodes) {
      let sections = [];
      let popups = [];
      if (nodes && nodes.length) {
        nodes.forEach((node) => {
          const section =
            node && node.closest && node.closest("section.ld-unit[data-unit]");
          if (section && !sections.includes(section)) sections.push(section);
          const popup =
            node && node.closest && node.closest(".pop[data-evidence-id]");
          if (popup && !popups.includes(popup)) popups.push(popup);
        });
      } else {
        sections = all("section.ld-unit[data-unit]");
        popups = all(".pop[data-evidence-id]");
      }
      sections.forEach(syncUnitEdit);
      popups.forEach(syncEvidenceEdit);
    }
    function commitEditorDOM(nodes) {
      (nodes || []).forEach((node) => {
        if (!node || !node.closest) return;
        const diagram = node.closest(".ld-diagram");
        const section = node.closest("section.ld-unit[data-unit]");
        if (diagram) diagram.setAttribute("data-editor-preserve-dom", "true");
        else if (
          section &&
          unitState(section.getAttribute("data-unit")) &&
          unitState(section.getAttribute("data-unit")).kind === "figure"
        )
          all(".ld-diagram", section).forEach((host) => {
            host.setAttribute("data-editor-preserve-dom", "true");
          });
      });
      syncEditorDOMToState(nodes);
      state.savedAt = new Date().toISOString();
      mirror();
      storeEditorDOM();
      renderSelection();
      updateEditorButtons();
    }

    function bindUnits() {
      all("section.ld-unit[data-unit]").forEach((section) => {
        const id = section.getAttribute("data-unit");
        const ab = section.querySelector(".ld-ab");
        if (ab && !ab.__legalDesignUnitBound) {
          ab.__legalDesignUnitBound = true;
          ab.addEventListener("keydown", (event) => {
            if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") return;
            event.preventDefault();
            event.stopPropagation();
            setVariant(id, event.key === "ArrowLeft" ? "a" : "b");
          });
          all("[data-select-variant]", ab).forEach((button) => {
            if (button.__legalDesignUnitBound) return;
            button.__legalDesignUnitBound = true;
            button.addEventListener("click", () => {
              setVariant(id, button.getAttribute("data-select-variant"));
            });
          });
        }
        const whyButton = section.querySelector(".ld-why-toggle");
        const why = section.querySelector(".ld-why");
        if (whyButton && why && !whyButton.__legalDesignUnitBound) {
          whyButton.__legalDesignUnitBound = true;
          whyButton.addEventListener("click", () => {
            const open = why.hidden;
            why.hidden = !open;
            whyButton.setAttribute("aria-expanded", open ? "true" : "false");
          });
        }
        all("[data-editable]", section).forEach((node) => {
          if (node.__legalDesignUnitBound) return;
          node.__legalDesignUnitBound = true;
          node.addEventListener("blur", () => {
            if (!node.isConnected || !node.hasAttribute("contenteditable"))
              return;
            node.removeAttribute("contenteditable");
            node.removeAttribute("spellcheck");
            textUndoArmed = false;
            commitEditorDOM([node]);
          });
        });
      });
    }

    function renderDecisions() {
      all('[data-kind="decision"][data-unit]').forEach((unit) => {
        const id = unit.getAttribute("data-unit");
        const value =
          state.review && state.review.decisions
            ? state.review.decisions[id]
            : null;
        const choice =
          value && typeof value === "object" && !Array.isArray(value)
            ? value.choice
            : value;
        const choices =
          value && typeof value === "object" && !Array.isArray(value)
            ? value.choices
            : value;
        all("[data-decision-option]", unit).forEach((button) => {
          const selected = unit.hasAttribute("data-multi")
            ? Array.isArray(choices) && choices.includes(button.value)
            : choice === button.value;
          button.setAttribute("aria-pressed", selected ? "true" : "false");
        });
        all("[data-decision-custom],[data-decision-note]", unit).forEach(
          (input) => {
            const key = input.hasAttribute("data-decision-custom")
              ? "custom"
              : "note";
            const next =
              value && typeof value === "object" && !Array.isArray(value)
                ? value[key] || ""
                : "";
            if (document.activeElement !== input) input.value = next;
          },
        );
        unit.toggleAttribute(
          "data-answered",
          Boolean(
            unit.hasAttribute("data-multi")
              ? (Array.isArray(choices) && choices.length) ||
                  (value && (value.custom || value.note))
              : choice || (value && (value.custom || value.note)),
          ),
        );
      });
    }
    function bindDecisions() {
      all('[data-kind="decision"][data-unit]').forEach((unit) => {
        const id = unit.getAttribute("data-unit");
        all("[data-decision-option]", unit).forEach((button) => {
          if (button.__legalDesignDecisionBound) return;
          button.__legalDesignDecisionBound = true;
          button.addEventListener("click", () => {
            change(() => {
              if (!state.review.decisions) state.review.decisions = {};
              if (unit.hasAttribute("data-multi")) {
                const current = state.review.decisions[id];
                const values = Array.isArray(current)
                  ? current
                  : current && Array.isArray(current.choices)
                    ? current.choices
                    : [];
                const at = values.indexOf(button.value);
                if (at >= 0) values.splice(at, 1);
                else values.push(button.value);
                if (
                  current &&
                  typeof current === "object" &&
                  !Array.isArray(current)
                )
                  current.choices = values;
                else state.review.decisions[id] = values;
              } else {
                const current = state.review.decisions[id];
                if (
                  current &&
                  typeof current === "object" &&
                  !Array.isArray(current)
                )
                  current.choice = button.value;
                else state.review.decisions[id] = button.value;
              }
            });
          });
        });
        all("[data-decision-custom],[data-decision-note]", unit).forEach(
          (input) => {
            if (input.__legalDesignDecisionBound) return;
            input.__legalDesignDecisionBound = true;
            input.addEventListener("change", () => {
              change(() => {
                if (!state.review.decisions) state.review.decisions = {};
                let current = state.review.decisions[id];
                if (
                  !current ||
                  typeof current !== "object" ||
                  Array.isArray(current)
                ) {
                  current = unit.hasAttribute("data-multi")
                    ? {
                        choices: Array.isArray(current)
                          ? current.slice()
                          : current
                            ? [current]
                            : [],
                      }
                    : { choice: current || null };
                }
                current[
                  input.hasAttribute("data-decision-custom") ? "custom" : "note"
                ] = input.value;
                state.review.decisions[id] = current;
              });
            });
          },
        );
      });
    }

    function popupFor(id) {
      const pop = document.getElementById(id);
      return pop && pop.matches("#popup-scrim .pop") ? pop : null;
    }
    function activePopup() {
      const scrim = document.getElementById("popup-scrim");
      if (!scrim || scrim.hidden) return null;
      return (
        all(".pop", scrim).find((pop) => !pop.hidden && visible(pop)) || null
      );
    }
    function popupFocusables(pop) {
      return all(
        'a[href],button,input:not([type="hidden"]),select,textarea,summary,[contenteditable="true"],[tabindex]',
        pop,
      ).filter(
        (node) =>
          node.tabIndex >= 0 &&
          !node.matches(':disabled,[aria-disabled="true"]') &&
          !node.closest('[hidden],[inert],[aria-hidden="true"]') &&
          visible(node),
      );
    }
    function focusPopupFallback(pop) {
      if (!pop.hasAttribute("tabindex")) {
        pop.setAttribute("tabindex", "-1");
        popupFallbackNodes.add(pop);
      }
      pop.focus();
    }
    function clearPopupFallback(pop) {
      if (!popupFallbackNodes.has(pop)) return;
      pop.removeAttribute("tabindex");
      popupFallbackNodes.delete(pop);
    }
    function trapPopupTab(event, pop) {
      const focusables = popupFocusables(pop);
      if (!focusables.length) {
        event.preventDefault();
        focusPopupFallback(pop);
        return;
      }
      const active = document.activeElement;
      const first = focusables[0];
      const last = focusables[focusables.length - 1];
      if (
        !focusables.includes(active) ||
        (event.shiftKey && active === first) ||
        (!event.shiftKey && active === last)
      ) {
        event.preventDefault();
        (event.shiftKey ? last : first).focus();
      }
    }
    function openPopup(id, source) {
      const scrim = document.getElementById("popup-scrim");
      const pop = popupFor(id);
      if (!scrim || !pop) return;
      const prior = activePopup();
      if (prior && prior !== pop && source && prior.contains(source)) {
        popupTrail.push({
          pop: prior,
          source,
          scrollTop: prior.scrollTop,
          returnFocus,
          returnFocusKey,
        });
      } else if (!prior) {
        popupTrail.length = 0;
      }
      rememberReturnFocus(source);
      all(".pop", scrim).forEach((item) => {
        clearPopupFallback(item);
        item.hidden = item !== pop;
      });
      scrim.hidden = false;
      pop.hidden = false;
      document.body.style.overflow = "hidden";
      const close = pop.querySelector(".ld-popup-close,.pop-close");
      const focusables = popupFocusables(pop);
      const initial =
        close && focusables.includes(close) ? close : focusables[0];
      if (initial) initial.focus();
      else focusPopupFallback(pop);
      updatePopupScrollHint(pop);
    }
    function closePopup() {
      const scrim = document.getElementById("popup-scrim");
      if (!scrim || scrim.hidden) return;
      const prior = popupTrail.pop();
      if (prior?.pop.isConnected) {
        all(".pop", scrim).forEach((item) => {
          item.hidden = item !== prior.pop;
          clearPopupFallback(item);
        });
        prior.pop.scrollTop = prior.scrollTop;
        returnFocus = prior.returnFocus;
        returnFocusKey = prior.returnFocusKey;
        if (prior.source.isConnected)
          prior.source.focus({ preventScroll: true });
        else focusPopupFallback(prior.pop);
        updatePopupScrollHint(prior.pop);
        return;
      }
      scrim.hidden = true;
      all(".pop", scrim).forEach((item) => {
        item.hidden = true;
        clearPopupFallback(item);
      });
      document.body.style.overflow = "";
      restoreReturnFocus();
    }
    function bindPopupDOM() {
      all("[data-evidence],[data-detail]").forEach((node) => {
        if (!node.matches("a,button,input,select,textarea,[tabindex]")) {
          node.tabIndex = 0;
          node.setAttribute("role", "button");
        }
      });
      all("#popup-scrim .pop").forEach((pop) => {
        if (pop.querySelector(".ld-popup-authoring"))
          popupAuthoringControls(pop);
        if (pop.__legalDesignPopupBound) return;
        pop.__legalDesignPopupBound = true;
        pop.addEventListener("scroll", () => {
          updatePopupScrollHint(pop);
        });
        all("[data-evidence-field][data-editable]", pop).forEach((node) => {
          if (node.__legalDesignEvidenceBound) return;
          node.__legalDesignEvidenceBound = true;
          node.addEventListener("blur", () => {
            if (!node.isConnected || !node.hasAttribute("contenteditable"))
              return;
            node.removeAttribute("contenteditable");
            node.removeAttribute("spellcheck");
            textUndoArmed = false;
            commitEditorDOM([node]);
          });
        });
      });
      all("#popup-scrim a[href]").forEach((link) => {
        try {
          const url = new URL(link.getAttribute("href"));
          if (url.protocol !== "http:" && url.protocol !== "https:")
            throw new Error("scheme");
        } catch (_) {
          link.setAttribute("href", "#");
        }
      });
    }
    function bindPopups() {
      bindPopupDOM();
      document.addEventListener("click", (event) => {
        if (suppressEditorClick) {
          suppressEditorClick = false;
          event.preventDefault();
          return;
        }
        const trigger = event.target.closest("[data-evidence],[data-detail]");
        if (editing && trigger && !trigger.closest(".pop")) return;
        const interactive = event.target.closest(
          "button,input,select,textarea,.ld-unit-tools",
        );
        if (trigger && interactive && interactive !== trigger) return;
        const editableTarget =
          editing && event.target.closest("[data-editable]");
        if (trigger && editableTarget && trigger.contains(editableTarget))
          return;
        if (trigger) {
          event.preventDefault();
          openPopup(
            trigger.getAttribute("data-evidence") ||
              trigger.getAttribute("data-detail"),
            trigger,
          );
          return;
        }
        if (
          event.target.closest(".ld-popup-close,.pop-close") ||
          event.target.id === "popup-scrim"
        ) {
          event.preventDefault();
          closePopup();
        }
      });
      document.addEventListener("keydown", (event) => {
        const pop = activePopup();
        if (pop && event.key === "Tab") {
          trapPopupTab(event, pop);
          return;
        }
        const detail =
          event.target.closest &&
          event.target.closest("[data-evidence],[data-detail]");
        const editableTarget =
          editing &&
          event.target.closest &&
          event.target.closest("[data-editable]");
        if (detail && editableTarget && detail.contains(editableTarget)) return;
        if (detail && (event.key === "Enter" || event.key === " ")) {
          event.preventDefault();
          openPopup(
            detail.getAttribute("data-evidence") ||
              detail.getAttribute("data-detail"),
            detail,
          );
        }
        if (pop && event.key === "Escape") {
          event.preventDefault();
          closePopup();
        }
      });
    }

    function updatePopupScrollHint(pop) {
      if (!pop) return;
      pop.toggleAttribute(
        "data-scroll-more",
        pop.scrollTop + pop.clientHeight < pop.scrollHeight - 1,
      );
    }

    function installAuthoringControls() {
      const tools = document.querySelector(".ld-editor-tools");
      if (!tools) return;
      for (const [id, label] of [
        ["editor-add-text", "Text box"],
        ["editor-popup", "Add popup"],
        ["editor-remove-popup", "Remove popup"],
      ]) {
        if (document.getElementById(id)) continue;
        const button = document.createElement("button");
        button.id = id;
        button.type = "button";
        button.className = "ld-tool";
        button.textContent = label;
        button.title = label;
        tools.appendChild(button);
      }
      for (const [kind, tokens] of [
        ["fill", FILL_TOKENS],
        ["text", TEXT_TOKENS],
      ]) {
        const select = document.getElementById(`editor-${kind}-token`);
        if (!select) continue;
        document.getElementById(`editor-${kind}-palette`)?.remove();
        select.hidden = true;
        const button = document.createElement("button");
        button.id = `editor-${kind}-palette`;
        button.type = "button";
        button.className = "ld-tool";
        button.textContent = kind === "fill" ? "Fill ▾" : "Text ▾";
        button.setAttribute(
          "aria-label",
          kind === "fill" ? "Fill color" : "Text color",
        );
        button.setAttribute("aria-haspopup", "dialog");
        button.setAttribute("aria-expanded", "false");
        select.after(button);
        button.addEventListener("click", () => {
          const existing = document.getElementById("ld-color-palette");
          const same = existing && existing.dataset.kind === kind;
          if (existing) existing.remove();
          all('[id$="-palette"].ld-tool').forEach((node) => {
            node.setAttribute("aria-expanded", "false");
          });
          if (same) return;
          const panel = document.createElement("div");
          panel.id = "ld-color-palette";
          panel.dataset.kind = kind;
          panel.setAttribute("data-editor-only", "");
          panel.setAttribute("role", "dialog");
          panel.setAttribute(
            "aria-label",
            kind === "fill" ? "Choose fill color" : "Choose text color",
          );
          const names = {
            card: "Card",
            "card-strong": "Raised card",
            tint: "Soft tint",
            bg: "Page",
            none: "No fill",
            ink: "Primary text",
            muted: "Secondary text",
            faint: "Quiet text",
            red: "Accent",
            "red-strong": "Strong accent",
          };
          const heading = document.createElement("strong");
          heading.textContent = kind === "fill" ? "Fill color" : "Text color";
          panel.appendChild(heading);
          const grid = document.createElement("div");
          grid.className = "ld-swatch-grid";
          const painted = selection.map((node) =>
            node.getAttribute(`data-${kind}-token`),
          );
          const currentToken =
            painted.length && painted.every((value) => value === painted[0])
              ? painted[0]
              : null;
          tokens.forEach((token) => {
            const swatch = document.createElement("button");
            swatch.type = "button";
            swatch.className = "ld-color-swatch";
            swatch.dataset.colorToken = token;
            swatch.setAttribute("aria-label", names[token]);
            swatch.setAttribute("aria-pressed", String(currentToken === token));
            swatch.title = names[token];
            swatch.style.background =
              token === "none" ? "transparent" : `var(--${token})`;
            if (token === "none") swatch.textContent = "∅";
            swatch.addEventListener("click", () => {
              applyToken(`data-${kind}-token`, token);
              panel.remove();
              button.setAttribute("aria-expanded", "false");
              button.focus();
            });
            grid.appendChild(swatch);
          });
          panel.appendChild(grid);
          const hint = document.createElement("small");
          hint.textContent = "House palette · adapts to light and dark";
          panel.appendChild(hint);
          document.body.appendChild(panel);
          const box = button.getBoundingClientRect();
          panel.style.top =
            Math.min(innerHeight - panel.offsetHeight - 8, box.bottom + 8) +
            "px";
          panel.style.left =
            Math.max(
              8,
              Math.min(innerWidth - panel.offsetWidth - 8, box.left),
            ) + "px";
          button.setAttribute("aria-expanded", "true");
          grid.querySelector("button").focus();
          panel.addEventListener("keydown", (event) => {
            if (event.key === "Escape") {
              event.stopPropagation();
              panel.remove();
              button.setAttribute("aria-expanded", "false");
              button.focus();
            }
          });
        });
      }
    }
    function newPopupId() {
      let n = 1;
      while (
        state.evidence["detail-added-" + n] ||
        document.getElementById("detail-added-" + n)
      )
        n++;
      return "detail-added-" + n;
    }
    function cloneAttachedPopups(copy) {
      const targets = all("[data-detail],[data-evidence]", copy);
      if (copy.matches("[data-detail],[data-evidence]")) targets.unshift(copy);
      const mapping = new Map();
      targets.forEach((target) => {
        const attr = target.hasAttribute("data-detail")
          ? "data-detail"
          : "data-evidence";
        const original = target.getAttribute(attr);
        const pop = popupFor(original);
        if (!pop) return;
        if (!mapping.has(original)) {
          const id = newPopupId();
          const duplicate = pop.cloneNode(true);
          duplicate.id = id;
          duplicate.setAttribute("data-evidence-id", id);
          duplicate.hidden = true;
          all("[id]", duplicate).forEach((node) => {
            node.removeAttribute("id");
          });
          duplicate.removeAttribute("aria-labelledby");
          duplicate.setAttribute(
            "aria-label",
            state.evidence[original]?.popup?.title || "Detail",
          );
          if (state.evidence[original])
            state.evidence[id] = clone(state.evidence[original]);
          document.getElementById("popup-scrim").appendChild(duplicate);
          mapping.set(original, id);
        }
        target.setAttribute(attr, mapping.get(original));
        target.setAttribute("aria-controls", mapping.get(original));
      });
    }
    function addTextBox() {
      editorCheckpoint();
      const selected = topSelection()[0];
      const scope =
        activePopup() ||
        (selected && selected.closest("section.ld-unit")) ||
        all("section.ld-unit").find(visible) ||
        document.querySelector("main");
      if (!scope) return;
      if (getComputedStyle(scope).position === "static")
        scope.style.position = "relative";
      const node = document.createElement("p");
      node.className = "ld-added-text";
      node.setAttribute("data-editable", "");
      node.setAttribute("data-editor-object", "text");
      node.setAttribute("data-placeholder", "[Text explaining this point.]");
      node.textContent = "Your text";
      Object.assign(node.style, {
        position: "absolute",
        left: "24px",
        top: "64px",
        width: "240px",
        margin: "0",
        zIndex: "4",
      });
      scope.appendChild(node);
      stampEditorOrigins(node);
      commitEditorDOM([node]);
      select([node]);
      editText(node);
    }
    function addPopupSection(pop) {
      const id = pop.getAttribute("data-evidence-id");
      const item = state.evidence[id];
      if (!item?.popup) return;
      const index = item.popup.sections.length;
      item.popup.sections.push({
        heading: "Section heading",
        body: "Explain this point.",
      });
      const section = document.createElement("section");
      section.className = "ld-popup-section";
      for (const [tag, field, text] of [
        ["h3", "heading", "Section heading"],
        ["p", "body", "Explain this point."],
      ]) {
        const node = document.createElement(tag);
        node.textContent = text;
        node.setAttribute("data-editable", "");
        node.setAttribute(
          "data-evidence-field",
          `popup.sections.${index}.${field}`,
        );
        node.setAttribute(
          "data-placeholder",
          field === "heading"
            ? "[Section heading.]"
            : "[Explanation of this point.]",
        );
        section.appendChild(node);
      }
      const controls = pop.querySelector(".ld-popup-authoring");
      pop.insertBefore(section, controls || null);
      bindPopupDOM();
      stampEditorOrigins(section);
      commitEditorDOM([section]);
    }
    function popupAuthoringControls(pop) {
      const existing = pop.querySelector(".ld-popup-authoring");
      if (existing?.__bound) return;
      existing?.remove();
      const controls = document.createElement("div");
      controls.className = "ld-popup-authoring";
      controls.setAttribute("data-editor-only", "");
      controls.__bound = true;
      const add = document.createElement("button");
      add.type = "button";
      add.className = "ld-tool";
      add.textContent = "Add section";
      add.addEventListener("click", () => {
        editorCheckpoint();
        addPopupSection(pop);
      });
      controls.appendChild(add);
      pop.appendChild(controls);
    }
    function editSelectedPopup() {
      const target = topSelection().length === 1 && topSelection()[0];
      if (!target) return;
      let id =
        target.getAttribute("data-detail") ||
        target.getAttribute("data-evidence");
      if (!id || !popupFor(id)) {
        editorCheckpoint();
        id = newPopupId();
        const title =
          (target.textContent || "Detail").trim().slice(0, 120) || "Detail";
        state.evidence[id] = {
          cite: "",
          locator: "",
          excerpt: "",
          link: null,
          status: "authored",
          popup: {
            type: "detail",
            title,
            lede: "Add the detail this element should explain.",
            sections: [],
          },
        };
        target.setAttribute("data-detail", id);
        target.setAttribute("aria-controls", id);
        target.setAttribute("aria-haspopup", "dialog");
        target.setAttribute("role", "button");
        target.setAttribute("tabindex", "0");
        if (target instanceof SVGElement) target.classList.add("ld-detail");
        const pop = document.createElement("article");
        pop.id = id;
        pop.className = "pop";
        pop.hidden = true;
        pop.setAttribute("role", "dialog");
        pop.setAttribute("aria-modal", "true");
        pop.setAttribute("aria-label", title);
        pop.setAttribute("data-evidence-id", id);
        const close = document.createElement("button");
        close.type = "button";
        close.className = "ld-popup-close";
        close.textContent = "×";
        close.setAttribute("aria-label", "Close");
        pop.appendChild(close);
        for (const [tag, field, value] of [
          ["h2", "title", title],
          ["p", "lede", state.evidence[id].popup.lede],
        ]) {
          const node = document.createElement(tag);
          node.textContent = value;
          node.setAttribute("data-editable", "");
          node.setAttribute("data-evidence-field", "popup." + field);
          if (field === "lede") node.className = "ld-popup-lede";
          pop.appendChild(node);
        }
        document.getElementById("popup-scrim").appendChild(pop);
        addPopupSection(pop);
        bindPopupDOM();
        commitEditorDOM([target, pop]);
      }
      const pop = popupFor(id);
      popupAuthoringControls(pop);
      stampEditorOrigins(pop);
      select([]);
      openPopup(id, target);
    }
    function removeSelectedPopup() {
      const target = topSelection().length === 1 && topSelection()[0];
      if (!target) return;
      editorCheckpoint();
      [
        "data-detail",
        "data-evidence",
        "aria-controls",
        "aria-haspopup",
      ].forEach((attr) => {
        target.removeAttribute(attr);
      });
      target.classList.remove("ld-detail");
      if (
        target.getAttribute("role") === "button" &&
        !target.matches("button")
      ) {
        target.removeAttribute("role");
        target.removeAttribute("tabindex");
      }
      // Keep the detached content recoverable through Undo and other references.
      commitEditorDOM([target]);
    }
    function editorTargets() {
      return all(EDITOR_OBJECTS).filter(visible);
    }
    function editorObjectTargets(scope) {
      return all(EDITOR_OBJECTS, scope || document).filter(
        (node) =>
          visible(node) &&
          !node.closest("[data-editor-only],.ld-slide-index") &&
          (!activePopup() || activePopup().contains(node)),
      );
    }
    function stampEditorOrigins(scope) {
      all(".ld-diagram", scope || document).forEach((host) => {
        all('svg [data-diagram-object],svg g[role="button"]', host).forEach(
          (node, index) => {
            if (!node.hasAttribute("data-editor-node-key"))
              node.setAttribute("data-editor-node-key", "node-" + (index + 1));
          },
        );
      });
      annotateDiagramLabels(scope || document);
      const selector = EDITOR_OBJECTS;
      const nodes = all(selector, scope || document);
      if (scope && scope.matches && scope.matches(selector))
        nodes.unshift(scope);
      nodes.forEach((node) => {
        if (node.closest("[data-editor-only]")) return;
        node.setAttribute("data-editor-unit", "1");
        if (!node.hasAttribute("data-editor-origin-style-stamped")) {
          node.setAttribute("data-editor-origin-style-stamped", "true");
          node.setAttribute(
            "data-editor-origin-style-present",
            node.hasAttribute("style") ? "true" : "false",
          );
          node.setAttribute(
            "data-editor-origin-style",
            node.getAttribute("style") || "",
          );
          ["fill-token", "text-token"].forEach((key) => {
            const source = "data-" + key;
            node.setAttribute(
              "data-editor-origin-" + key + "-present",
              node.hasAttribute(source) ? "true" : "false",
            );
            node.setAttribute(
              "data-editor-origin-" + key,
              node.getAttribute(source) || "",
            );
          });
        }
      });
    }
    function topSelection() {
      return selection.filter(
        (node) =>
          !selection.some((other) => other !== node && other.contains(node)),
      );
    }
    function boxFor(nodes) {
      const boxes = nodes
        .filter(visible)
        .map((node) => node.getBoundingClientRect());
      if (!boxes.length) return null;
      const left = Math.min.apply(
        null,
        boxes.map((box) => box.left),
      );
      const top = Math.min.apply(
        null,
        boxes.map((box) => box.top),
      );
      const right = Math.max.apply(
        null,
        boxes.map((box) => box.right),
      );
      const bottom = Math.max.apply(
        null,
        boxes.map((box) => box.bottom),
      );
      return {
        left: left,
        top: top,
        right: right,
        bottom: bottom,
        width: right - left,
        height: bottom - top,
      };
    }
    function makeEditorLayer() {
      if (document.getElementById("ld-editor-layer")) return;
      const layer = document.createElement("div");
      layer.id = "ld-editor-layer";
      layer.setAttribute("data-editor-only", "");
      const box = document.createElement("div");
      box.id = "ld-selection-box";
      ["nw", "n", "ne", "e", "se", "s", "sw", "w"].forEach((name) => {
        const handle = document.createElement("span");
        handle.className = "ld-handle";
        handle.setAttribute("data-handle", name);
        box.appendChild(handle);
      });
      const marquee = document.createElement("div");
      marquee.id = "ld-marquee";
      layer.appendChild(box);
      layer.appendChild(marquee);
      document.body.appendChild(layer);
    }
    function renderSelection() {
      all(".ld-selected").forEach((node) => {
        node.classList.remove("ld-selected");
      });
      selection.forEach((node) => {
        node.classList.add("ld-selected");
      });
      // Context controls normally sit above their object. A wrapped phone
      // toolbar can occupy that space, so move only the controls below it.
      // The authored object and page geometry remain untouched.
      const barBottom = Math.max(
        0,
        ...all(".ld-bar,#ld-page-approach,.ld-save-banner")
          .filter((node) => !node.hidden && node.getClientRects().length)
          .map((node) => node.getBoundingClientRect().bottom),
      );
      all(".ld-fixed-page .ld-unit-tools").forEach((tools) => {
        tools.style.removeProperty("--ld-context-offset");
        if (!editing || !tools.getClientRects().length) return;
        const offset = barBottom + 4 - tools.getBoundingClientRect().top;
        if (offset > 0)
          tools.style.setProperty("--ld-context-offset", offset + "px");
      });
      const control = document.getElementById("ld-selection-box");
      if (!control) return;
      const bounds = boxFor(selection);
      updateEditorButtons();
      control.toggleAttribute(
        "data-diagram-label-selection",
        selection.length === 1 &&
          selection[0].hasAttribute("data-diagram-label"),
      );
      if (!bounds || !editing) {
        control.style.display = "none";
        return;
      }
      control.style.display = "block";
      control.style.left = bounds.left + "px";
      control.style.top = bounds.top + "px";
      control.style.width = bounds.width + "px";
      control.style.height = bounds.height + "px";
      const close = activePopup()?.querySelector(".ld-popup-close,.pop-close");
      const closeBox = close && close.getBoundingClientRect();
      all(".ld-handle", control).forEach((handle) => {
        const box = handle.getBoundingClientRect();
        const overlapsClose =
          closeBox &&
          box.left < closeBox.right + 4 &&
          box.right > closeBox.left - 4 &&
          box.top < closeBox.bottom + 4 &&
          box.bottom > closeBox.top - 4;
        handle.style.visibility = overlapsClose ? "hidden" : "";
      });
      updateEditorButtons();
    }
    function select(nodes, additive) {
      if (!additive) selection = [];
      nodes.forEach((node) => {
        if (node && node.isConnected && !selection.includes(node))
          selection.push(node);
      });
      renderSelection();
    }
    function editorCheckpoint() {
      checkpoint();
    }
    function translate(node, x, y) {
      x = Math.round(x * 10) / 10;
      y = Math.round(y * 10) / 10;
      node.setAttribute("data-editor-x", String(x));
      node.setAttribute("data-editor-y", String(y));
      let base = node.getAttribute("data-editor-base-transform");
      if (base == null) {
        base = node.style.transform || "";
        node.setAttribute("data-editor-base-transform", base);
      }
      const sx = Number(node.getAttribute("data-editor-scale-x") || 1);
      const sy = Number(node.getAttribute("data-editor-scale-y") || 1);
      node.style.transform =
        `translate(${x}px,${y}px) ${base} scale(${sx},${sy})`.trim();
    }
    function localDelta(node, dx, dy) {
      // Pointer coordinates are CSS pixels; SVG transforms use parent units.
      const parent = node instanceof SVGElement && node.parentElement;
      const matrix = parent && parent.getScreenCTM && parent.getScreenCTM();
      if (!matrix) {
        const scale = pageScaleFor(node);
        return { x: dx / scale, y: dy / scale };
      }
      const inverse = matrix.inverse();
      return {
        x: inverse.a * dx + inverse.c * dy,
        y: inverse.b * dx + inverse.d * dy,
      };
    }
    function deleteSelection() {
      if (!selection.length) return;
      editorCheckpoint();
      const changed = topSelection()
        .map((node) => node.closest("section.ld-unit[data-unit]"))
        .filter(Boolean);
      topSelection().forEach((node) => {
        node.remove();
      });
      selection = [];
      commitEditorDOM(changed);
    }
    function nextCopyUnitId(source) {
      const base = (source || "unit") + "-copy";
      let serial = 1;
      while (state.units && state.units[base + "-" + serial]) serial += 1;
      return base + "-" + serial;
    }
    function clearCloneMetadata(copy) {
      all("[id]", copy)
        .concat(copy.id ? [copy] : [])
        .forEach((item) => {
          item.removeAttribute("id");
        });
      const editorNames = [
        "data-editor-unit",
        "data-editor-origin-style",
        "data-editor-origin-style-present",
        "data-editor-origin-style-stamped",
        "data-editor-origin-fill-token",
        "data-editor-origin-fill-token-present",
        "data-editor-origin-text-token",
        "data-editor-origin-text-token-present",
        "data-editor-x",
        "data-editor-y",
        "data-editor-base-transform",
        "contenteditable",
        "spellcheck",
      ];
      all("*", copy)
        .concat([copy])
        .forEach((item) => {
          editorNames.forEach((name) => {
            item.removeAttribute(name);
          });
          item.classList.remove("ld-selected", "ld-selection-mark");
        });
    }
    function duplicateSelection() {
      if (!selection.length) return;
      editorCheckpoint();
      const copies = [];
      topSelection().forEach((node) => {
        const copy = node.cloneNode(true);
        clearCloneMetadata(copy);
        cloneAttachedPopups(copy);
        let sourceSections = [];
        if (node.matches && node.matches("section.ld-unit[data-unit]"))
          sourceSections.push(node);
        sourceSections = sourceSections.concat(
          all("section.ld-unit[data-unit]", node),
        );
        let copySections = [];
        if (copy.matches && copy.matches("section.ld-unit[data-unit]"))
          copySections.push(copy);
        copySections = copySections.concat(
          all("section.ld-unit[data-unit]", copy),
        );
        sourceSections.forEach((sourceSection, index) => {
          const duplicateSection = copySections[index];
          if (!duplicateSection) return;
          const sourceId = sourceSection.getAttribute("data-unit");
          const copyId = nextCopyUnitId(sourceId);
          duplicateSection.setAttribute("data-unit", copyId);
          if (state.units && state.units[sourceId])
            state.units[copyId] = clone(state.units[sourceId]);
        });
        const parent = node.parentElement;
        const box = node.getBoundingClientRect();
        const parentBox = parent.getBoundingClientRect();
        const position = getComputedStyle(node).position;
        if (node instanceof SVGElement) {
          const delta = localDelta(node, 20, 20);
          copy.setAttribute(
            "data-editor-base-transform",
            node.getAttribute("data-editor-base-transform") || "",
          );
          translate(
            copy,
            Number(node.getAttribute("data-editor-x") || 0) + delta.x,
            Number(node.getAttribute("data-editor-y") || 0) + delta.y,
          );
        } else if (position !== "absolute" && position !== "fixed") {
          if (getComputedStyle(parent).position === "static")
            parent.style.position = "relative";
          copy.style.position = "absolute";
          copy.style.left =
            box.left - parentBox.left + parent.scrollLeft + 16 + "px";
          copy.style.top =
            box.top - parentBox.top + parent.scrollTop + 16 + "px";
          copy.style.width = box.width + "px";
          copy.style.margin = "0";
          copy.style.zIndex = "4";
        } else {
          translate(
            copy,
            Number(node.getAttribute("data-editor-x") || 0) + 16,
            Number(node.getAttribute("data-editor-y") || 0) + 16,
          );
        }
        node.insertAdjacentElement("afterend", copy);
        stampEditorOrigins(copy);
        copies.push(copy);
      });
      bindUnits();
      bindDecisions();
      bindPopupDOM();
      commitEditorDOM(copies);
      select(copies);
    }
    function eachSelection(fn) {
      if (!selection.length) return;
      editorCheckpoint();
      selection.forEach(fn);
      commitEditorDOM(selection);
    }
    function tokenValue(attribute, value) {
      const allowed =
        attribute === "data-fill-token"
          ? FILL_TOKENS
          : attribute === "data-text-token"
            ? TEXT_TOKENS
            : [];
      return allowed.includes(value) ? value : "";
    }
    function applyToken(attribute, value) {
      value = tokenValue(attribute, value);
      if (!value) return;
      eachSelection((node) => {
        node.setAttribute(attribute, value);
      });
    }
    function toggleStyle(property, on, off) {
      eachSelection((node) => {
        node.style[property] = getComputedStyle(node)[property].includes(on)
          ? off
          : on;
      });
    }
    function changeFont(delta) {
      eachSelection((node) => {
        node.style.fontSize =
          Math.max(
            11,
            Math.min(
              180,
              (parseFloat(getComputedStyle(node).fontSize) || 14) + delta,
            ),
          ) + "px";
      });
    }
    function resetSelection() {
      eachSelection((node) => {
        if (node.getAttribute("data-editor-origin-style-present") === "true")
          node.setAttribute(
            "style",
            node.getAttribute("data-editor-origin-style") || "",
          );
        else node.removeAttribute("style");
        ["fill-token", "text-token"].forEach((key) => {
          if (
            node.getAttribute("data-editor-origin-" + key + "-present") ===
            "true"
          )
            node.setAttribute(
              "data-" + key,
              node.getAttribute("data-editor-origin-" + key) || "",
            );
          else node.removeAttribute("data-" + key);
        });
        [
          "data-editor-x",
          "data-editor-y",
          "data-editor-base-transform",
          "data-editor-scale-x",
          "data-editor-scale-y",
        ].forEach((name) => {
          node.removeAttribute(name);
        });
      });
    }
    function canEditText(node) {
      return (
        node &&
        node.hasAttribute("data-editable") &&
        (!(node instanceof SVGElement) ||
          node.hasAttribute("data-diagram-label")) &&
        !node.matches("img,input,select,button")
      );
    }
    function setDiagramLabelText(node, value) {
      const lines = all("tspan", node);
      if (!lines.length) {
        node.textContent = value;
        return;
      }
      const words = value.trim().split(/\s+/).filter(Boolean);
      const wanted = Math.max(1, Math.min(lines.length, words.length || 1));
      const chunks = [];
      for (let index = 0; index < wanted; index += 1) {
        const start = Math.floor((index * words.length) / wanted);
        const end = Math.floor(((index + 1) * words.length) / wanted);
        chunks.push(words.slice(start, end).join(" "));
      }
      lines.forEach((line, index) => {
        if (index < chunks.length) line.textContent = chunks[index];
        else line.remove();
      });
    }
    function syncDiagramLabelParam(node, value) {
      const pointer = node.getAttribute("data-param-path");
      const section = node.closest("section.ld-unit[data-unit]");
      const variant = node.closest(".ld-variant[data-variant]");
      if (!pointer || !section || !variant) return false;
      const unit = unitState(section.getAttribute("data-unit"));
      const spec =
        unit &&
        unit.variants &&
        unit.variants[variant.getAttribute("data-variant")];
      return Boolean(spec && pointerValue(spec.params, pointer, value));
    }
    function editDiagramLabel(node) {
      select([node]);
      editorCheckpoint();
      const old = document.getElementById("ld-svg-text-editor");
      if (old) old.remove();
      const box = node.getBoundingClientRect();
      const style = getComputedStyle(node);
      const input = document.createElement("input");
      input.id = "ld-svg-text-editor";
      input.type = "text";
      input.value = node.textContent.trim();
      input.setAttribute("aria-label", "Edit diagram label");
      Object.assign(input.style, {
        position: "fixed",
        zIndex: "1000",
        left: Math.max(8, box.left - 6) + "px",
        top: Math.max(8, box.top - 5) + "px",
        width: Math.max(160, box.width + 28) + "px",
        minHeight: Math.max(34, box.height + 10) + "px",
        border: "2px solid var(--interaction)",
        borderRadius: "4px",
        background: "var(--card)",
        color: "var(--ink)",
        padding: "5px 8px",
        fontFamily: style.fontFamily,
        fontSize: Math.max(13, parseFloat(style.fontSize) || 13) + "px",
      });
      document.body.appendChild(input);
      let finished = false;
      const finish = (commit) => {
        if (finished) return;
        finished = true;
        if (commit && input.value.trim()) {
          const value = input.value.trim();
          syncDiagramLabelParam(node, value);
          setDiagramLabelText(node, value);
          commitEditorDOM([node]);
        }
        input.remove();
        renderSelection();
      };
      input.addEventListener("blur", () => finish(true));
      input.addEventListener("keydown", (event) => {
        if (event.key === "Enter") {
          event.preventDefault();
          finish(true);
        } else if (event.key === "Escape") {
          event.preventDefault();
          finish(false);
        }
      });
      input.focus();
      input.select();
    }
    function editText(node) {
      if (!canEditText(node)) return;
      if (node.hasAttribute("data-diagram-label")) {
        editDiagramLabel(node);
        return;
      }
      all('[contenteditable="true"]').forEach((active) => {
        if (active !== node) active.blur();
      });
      select([node]);
      editorCheckpoint();
      textUndoArmed = true;
      node.setAttribute("contenteditable", "true");
      node.setAttribute("spellcheck", "false");
      node.focus();
      const range = document.createRange();
      range.selectNodeContents(node);
      const current = getSelection();
      current.removeAllRanges();
      current.addRange(range);
    }
    function updateEditorButtons() {
      [
        "editor-duplicate",
        "editor-delete",
        "editor-bold",
        "editor-italic",
        "editor-underline",
        "editor-font-up",
        "editor-font-down",
        "editor-reset",
        "editor-fill-token",
        "editor-text-token",
        "editor-fill-palette",
        "editor-text-palette",
        "editor-popup",
        "editor-remove-popup",
      ].forEach((id) => {
        const node = document.getElementById(id);
        if (node) node.disabled = !selection.length;
      });
      const popup = document.getElementById("editor-popup");
      const remove = document.getElementById("editor-remove-popup");
      const target = topSelection().length === 1 ? topSelection()[0] : null;
      const id =
        target &&
        (target.getAttribute("data-detail") ||
          target.getAttribute("data-evidence"));
      if (popup) {
        popup.disabled = !target;
        popup.textContent = id ? "Edit popup" : "Add popup";
      }
      if (remove) remove.disabled = !id;
      const u = document.getElementById("editor-undo");
      const r = document.getElementById("editor-redo");
      if (u) u.disabled = !undo.length;
      if (r) r.disabled = !redo.length;
    }
    function setMode(on) {
      if (!on)
        all('[contenteditable="true"]').forEach((node) => {
          node.blur();
        });
      editing = Boolean(on);
      root.toggleAttribute("data-mode", editing);
      if (editing) root.setAttribute("data-mode", "edit");
      const mode = document.getElementById("mode-toggle");
      if (mode) {
        mode.textContent = editing ? "Done" : "Edit";
        mode.setAttribute("aria-pressed", editing ? "true" : "false");
      }
      if (editing) {
        all("section.ld-unit[data-unit]").forEach((unit) => {
          if (!unit.hasAttribute("tabindex")) {
            unit.tabIndex = 0;
            unit.setAttribute("data-editor-added-tabindex", "");
          }
        });
        stampEditorOrigins();
        makeEditorLayer();
      } else {
        selection = [];
        root.removeAttribute("data-editor-menu");
        all("[contenteditable]").forEach((node) => {
          node.removeAttribute("contenteditable");
          node.removeAttribute("spellcheck");
        });
        all("[data-editor-added-tabindex]").forEach((unit) => {
          unit.removeAttribute("data-editor-added-tabindex");
          unit.removeAttribute("tabindex");
        });
        textUndoArmed = false;
      }
      annotateDiagramLabels(document);
      renderSelection();
      updateEditorButtons();
    }

    function bindEditor() {
      document.addEventListener(
        "pointerdown",
        () => root.setAttribute("data-input-modality", "pointer"),
        true,
      );
      document.addEventListener(
        "keydown",
        (event) => {
          if (event.key === "Tab" || event.key.startsWith("Arrow"))
            root.setAttribute("data-input-modality", "keyboard");
        },
        true,
      );
      const mode = document.getElementById("mode-toggle");
      if (mode)
        mode.addEventListener("click", () => {
          setMode(!editing);
        });
      function bind(id, fn) {
        const node = document.getElementById(id);
        if (node) node.addEventListener("click", fn);
      }
      bind("editor-undo", doUndo);
      bind("editor-redo", doRedo);
      bind("editor-duplicate", duplicateSelection);
      bind("editor-delete", deleteSelection);
      bind("editor-bold", () => {
        toggleStyle("fontWeight", "700", "400");
      });
      bind("editor-italic", () => {
        toggleStyle("fontStyle", "italic", "normal");
      });
      bind("editor-underline", () => {
        toggleStyle("textDecorationLine", "underline", "none");
      });
      bind("editor-font-up", () => {
        changeFont(1);
      });
      bind("editor-font-down", () => {
        changeFont(-1);
      });
      bind("editor-reset", resetSelection);
      bind("editor-add-text", addTextBox);
      bind("editor-popup", editSelectedPopup);
      bind("editor-remove-popup", removeSelectedPopup);
      bind("editor-overflow-toggle", () => {
        const open = root.getAttribute("data-editor-menu") === "open";
        root.toggleAttribute("data-editor-menu", !open);
        if (!open) root.setAttribute("data-editor-menu", "open");
        const button = document.getElementById("editor-overflow-toggle");
        if (button)
          button.setAttribute("aria-expanded", !open ? "true" : "false");
      });
      const fill = document.getElementById("editor-fill-token");
      if (fill)
        fill.addEventListener("change", () => {
          applyToken("data-fill-token", fill.value);
          fill.value = "";
        });
      const text = document.getElementById("editor-text-token");
      if (text)
        text.addEventListener("change", () => {
          applyToken("data-text-token", text.value);
          text.value = "";
        });
      document.addEventListener(
        "pointerdown",
        (event) => {
          if (
            !editing ||
            event.button !== 0 ||
            event.target.closest(
              '.ld-bar,.ld-unit-tools,.ld-slide-index,[data-editor-only]:not(#ld-editor-layer):not(#ld-selection-box),input,textarea,select,button:not([data-detail]):not([data-evidence]),[contenteditable="true"]',
            )
          )
            return;
          const handle = event.target.closest(".ld-handle");
          if (handle) {
            event.preventDefault();
            const bounds = boxFor(selection);
            if (!bounds) return;
            editorCheckpoint();
            gesture = {
              type: "resize",
              handle: handle.getAttribute("data-handle"),
              x: event.clientX,
              y: event.clientY,
              bounds: bounds,
              starts: topSelection().map((node) => {
                const box = node.getBoundingClientRect();
                if (!node.hasAttribute("data-editor-base-transform"))
                  node.setAttribute(
                    "data-editor-base-transform",
                    node.style.transform || "",
                  );
                return {
                  node: node,
                  box: box,
                  x: Number(node.getAttribute("data-editor-x") || 0),
                  y: Number(node.getAttribute("data-editor-y") || 0),
                  sx: Number(node.getAttribute("data-editor-scale-x") || 1),
                  sy: Number(node.getAttribute("data-editor-scale-y") || 1),
                };
              }),
            };
            return;
          }
          const diagramLabel = event.target.closest(
            ".ld-diagram [data-diagram-label][data-editable]",
          );
          let target = event.target.closest(EDITOR_OBJECTS);
          if (diagramLabel && event.detail < 2) {
            target =
              diagramLabel.closest('[data-diagram-object],g[role="button"]') ||
              diagramLabel;
          }
          const card = event.target.closest(
            '.ld-card,.sb-card,section.ld-unit[data-kind="card"]',
          );
          if (card && event.detail < 2) target = card;
          // Empty space in a figure starts an enclosure selection, not a drag
          // of its whole editable body. Existing group selection survives a grab.
          if (
            event.target.closest(".ld-diagram") &&
            !event.target.closest(
              '[data-diagram-object],g[role="button"],[data-diagram-label]',
            )
          )
            target = null;
          if (target) {
            event.preventDefault();
            if (event.detail >= 2 && diagramLabel) {
              event.stopImmediatePropagation();
              gesture = null;
              editText(diagramLabel);
              return;
            }
            const selectedOwner = selection.find(
              (node) => node === target || node.contains(target),
            );
            if (!selectedOwner || event.shiftKey)
              select([target], event.shiftKey);
            gesture = {
              type: "pending",
              x: event.clientX,
              y: event.clientY,
              starts: topSelection().map((node) => ({
                node: node,
                x: Number(node.getAttribute("data-editor-x") || 0),
                y: Number(node.getAttribute("data-editor-y") || 0),
              })),
            };
            return;
          }
          const marquee = document.getElementById("ld-marquee");
          if (marquee) {
            if (!event.shiftKey) select([]);
            gesture = {
              type: "marquee",
              x: event.clientX,
              y: event.clientY,
              base: selection.slice(),
            };
            marquee.style.display = "block";
            marquee.style.left = event.clientX + "px";
            marquee.style.top = event.clientY + "px";
          }
        },
        true,
      );
      document.addEventListener(
        "pointermove",
        (event) => {
          if (!gesture) return;
          const dx = event.clientX - gesture.x;
          const dy = event.clientY - gesture.y;
          if (gesture.type === "pending" && Math.hypot(dx, dy) > 3) {
            editorCheckpoint();
            gesture.type = "move";
          }
          if (gesture.type === "move") {
            event.preventDefault();
            gesture.starts.forEach((start) => {
              const delta = localDelta(start.node, dx, dy);
              translate(start.node, start.x + delta.x, start.y + delta.y);
            });
            renderSelection();
          }
          if (gesture.type === "resize") {
            event.preventDefault();
            const horizontal = gesture.handle.includes("e")
              ? 1
              : gesture.handle.includes("w")
                ? -1
                : 0;
            const vertical = gesture.handle.includes("s")
              ? 1
              : gesture.handle.includes("n")
                ? -1
                : 0;
            const nextWidth = Math.max(
              12,
              gesture.bounds.width + dx * horizontal,
            );
            const nextHeight = Math.max(
              12,
              gesture.bounds.height + dy * vertical,
            );
            const scaleX = nextWidth / gesture.bounds.width;
            const scaleY = nextHeight / gesture.bounds.height;
            gesture.starts.forEach((start) => {
              const node = start.node;
              const left = start.box.left - gesture.bounds.left;
              const top = start.box.top - gesture.bounds.top;
              const width = Math.max(8, start.box.width * scaleX);
              const height = Math.max(8, start.box.height * scaleY);
              if (!(node instanceof SVGElement)) {
                const pageScale = pageScaleFor(node);
                if (getComputedStyle(node).display === "inline")
                  node.style.display = "inline-block";
                if (horizontal) {
                  node.style.width = width / pageScale + "px";
                  node.style.maxWidth = "none";
                }
                if (vertical) {
                  if (
                    /^(P|H1|H2|H3|H4|SPAN|B|I|A|DEL|INS|MARK|FIGCAPTION|TD|TH)$/.test(
                      node.tagName,
                    )
                  ) {
                    node.style.minHeight = height / pageScale + "px";
                    node.style.height = "auto";
                  } else node.style.height = height / pageScale + "px";
                }
                node.style.boxSizing = "border-box";
              }
              const nextX =
                start.x + left * (scaleX - 1) + (horizontal < 0 ? dx : 0);
              const nextY =
                start.y + top * (scaleY - 1) + (vertical < 0 ? dy : 0);
              if (node instanceof SVGElement) {
                const delta = localDelta(
                  node,
                  nextX - start.x,
                  nextY - start.y,
                );
                node.style.transformBox = "fill-box";
                node.style.transformOrigin = "top left";
                node.setAttribute(
                  "data-editor-scale-x",
                  String(start.sx * scaleX),
                );
                node.setAttribute(
                  "data-editor-scale-y",
                  String(start.sy * scaleY),
                );
                translate(node, start.x + delta.x, start.y + delta.y);
              } else {
                const delta = localDelta(
                  node,
                  nextX - start.x,
                  nextY - start.y,
                );
                translate(node, start.x + delta.x, start.y + delta.y);
              }
            });
            renderSelection();
          }
          if (gesture.type === "marquee") {
            const left = Math.min(gesture.x, event.clientX);
            const top = Math.min(gesture.y, event.clientY);
            const right = Math.max(gesture.x, event.clientX);
            const bottom = Math.max(gesture.y, event.clientY);
            const marquee = document.getElementById("ld-marquee");
            marquee.style.left = left + "px";
            marquee.style.top = top + "px";
            marquee.style.width = right - left + "px";
            marquee.style.height = bottom - top + "px";
            const hits = editorObjectTargets().filter((node) => {
              const box = node.getBoundingClientRect();
              return (
                box.left >= left &&
                box.right <= right &&
                box.top >= top &&
                box.bottom <= bottom
              );
            });
            const outerHits = hits.filter(
              (node) =>
                !hits.some(
                  (parent) => parent !== node && parent.contains(node),
                ),
            );
            selection = gesture.base.concat(
              outerHits.filter((node) => !gesture.base.includes(node)),
            );
            renderSelection();
          }
        },
        true,
      );
      document.addEventListener(
        "pointerup",
        () => {
          const marquee = document.getElementById("ld-marquee");
          if (marquee) marquee.style.display = "none";
          if (
            gesture &&
            (gesture.type === "move" || gesture.type === "resize")
          ) {
            suppressEditorClick = true;
            commitEditorDOM(gesture.starts.map((start) => start.node));
            setTimeout(() => {
              suppressEditorClick = false;
            }, 0);
          }
          gesture = null;
        },
        true,
      );
      document.addEventListener(
        "dblclick",
        (event) => {
          if (!editing) return;
          const target = event.target.closest("[data-editable]");
          if (target) {
            event.preventDefault();
            event.stopImmediatePropagation();
            editText(target);
          }
        },
        true,
      );
      window.addEventListener("keydown", (event) => {
        if (!editing) return;
        const focusedUnit =
          event.target.matches &&
          event.target.matches("section.ld-unit[data-unit]")
            ? event.target
            : null;
        if (focusedUnit && (event.key === "Enter" || event.key === " ")) {
          const first = editorTargets().find((node) =>
            focusedUnit.contains(node),
          );
          if (first) {
            event.preventDefault();
            event.stopImmediatePropagation();
            if (
              event.key === "Enter" &&
              selection.length === 1 &&
              selection[0] === first &&
              canEditText(first)
            )
              editText(first);
            else select([first]);
          }
          return;
        }
        const typing =
          event.target.closest &&
          event.target.closest(
            '[contenteditable="true"],input,textarea,select',
          );
        const key = event.key.toLowerCase();
        const mod = event.metaKey || event.ctrlKey;
        if (mod && key === "z") {
          event.preventDefault();
          event.stopImmediatePropagation();
          event.shiftKey ? doRedo() : doUndo();
        } else if (mod && key === "d" && !typing) {
          event.preventDefault();
          event.stopImmediatePropagation();
          duplicateSelection();
        } else if (
          (event.key === "Delete" || event.key === "Backspace") &&
          !typing
        ) {
          event.preventDefault();
          event.stopImmediatePropagation();
          deleteSelection();
        } else if (event.key === "Escape") {
          event.preventDefault();
          event.stopImmediatePropagation();
          if (typing && typing.hasAttribute("contenteditable")) typing.blur();
          select([]);
        } else if (
          event.key.startsWith("Arrow") &&
          !typing &&
          selection.length
        ) {
          event.preventDefault();
          event.stopImmediatePropagation();
          let dx =
            event.key === "ArrowLeft" ? -1 : event.key === "ArrowRight" ? 1 : 0;
          let dy =
            event.key === "ArrowUp" ? -1 : event.key === "ArrowDown" ? 1 : 0;
          if (event.shiftKey) {
            dx *= 10;
            dy *= 10;
          }
          editorCheckpoint();
          topSelection().forEach((node) => {
            translate(
              node,
              Number(node.getAttribute("data-editor-x") || 0) + dx,
              Number(node.getAttribute("data-editor-y") || 0) + dy,
            );
          });
          commitEditorDOM(selection);
        }
      });
      document.addEventListener(
        "beforeinput",
        (event) => {
          if (
            editing &&
            event.target.closest &&
            event.target.closest('[contenteditable="true"]') &&
            !textUndoArmed
          ) {
            editorCheckpoint();
            textUndoArmed = true;
          }
        },
        true,
      );
      document.addEventListener(
        "input",
        (event) => {
          if (
            editing &&
            event.target.closest &&
            event.target.closest('[contenteditable="true"]')
          )
            commitEditorDOM([event.target]);
        },
        true,
      );
      document.addEventListener(
        "focusout",
        (event) => {
          const node = event.target;
          if (
            !editing ||
            !node.matches?.('[data-editable][contenteditable="true"]')
          )
            return;
          node.removeAttribute("contenteditable");
          node.removeAttribute("spellcheck");
          textUndoArmed = false;
          commitEditorDOM([node]);
        },
        true,
      );
      document.addEventListener("scroll", renderSelection, true);
      onLayoutResize(() => {
        // Choose responsive diagram geometry against the newly fitted page,
        // not its previous width. The fit listener schedules a later frame;
        // rendering first would leave wide/narrow mode one resize behind.
        if (pageFitFrame) cancelAnimationFrame(pageFitFrame);
        fitFixedPages();
        renderAll();
        renderSelection();
      });
    }

    function clearProtected(cloneRoot) {
      all("[data-export-protect]", cloneRoot).forEach((node) => {
        (node.getAttribute("data-export-protect") || "")
          .split(/[\s,]+/)
          .filter(Boolean)
          .forEach((name) => {
            node.style.removeProperty(name);
            node.removeAttribute(name);
          });
      });
    }
    function serializeClone(cloneRoot) {
      return (
        "<!doctype html>\n" +
        cloneRoot.outerHTML.replaceAll(
          ' xmlns="http://www.w3.org/2000/svg"',
          "",
        )
      );
    }
    function setCloneState(cloneRoot, nextState) {
      const block = cloneRoot.querySelector("#legaldesign-state");
      if (block) block.textContent = safeJSON(nextState);
    }
    function scrubTransientEditing(cloneRoot) {
      all("[contenteditable]", cloneRoot).forEach((node) => {
        node.removeAttribute("contenteditable");
        node.removeAttribute("spellcheck");
      });
      all(".ld-selected,.ld-selection-mark", cloneRoot).forEach((node) => {
        node.classList.remove("ld-selected", "ld-selection-mark");
      });
      all(
        "#ld-editor-layer,#ld-svg-text-editor,#ld-selection-popover,#ld-phone-sheet,#ld-restore-banner,.ld-save-banner,.ld-download-link,.ld-bottom-save,#ld-page-reader,#ld-read-page,#ld-page-fit-error",
        cloneRoot,
      ).forEach((node) => {
        node.remove();
      });
      all("[data-editor-added-tabindex]", cloneRoot).forEach((unit) => {
        unit.removeAttribute("data-editor-added-tabindex");
        unit.removeAttribute("tabindex");
      });
      cloneRoot.removeAttribute("data-mode");
      cloneRoot.removeAttribute("data-editor-menu");
      const mode = cloneRoot.querySelector("#mode-toggle");
      if (mode) {
        mode.textContent = "Edit";
        mode.setAttribute("aria-pressed", "false");
      }
      const overflow = cloneRoot.querySelector("#editor-overflow-toggle");
      if (overflow) overflow.setAttribute("aria-expanded", "false");
      const scrim = cloneRoot.querySelector("#popup-scrim");
      if (scrim) {
        scrim.hidden = true;
        all(".pop", scrim).forEach((pop) => {
          pop.hidden = true;
        });
      }
      const body = cloneRoot.querySelector("body");
      if (body && body.style.overflow === "hidden")
        body.style.removeProperty("overflow");
    }
    function scrubEditing(cloneRoot) {
      scrubTransientEditing(cloneRoot);
      clearProtected(cloneRoot);
    }
    function reducedClientState(cloneRoot, clientBlueprintId) {
      const brief = state.brief || {};
      const next = {
        schema: state.schema,
        artifactId: clientBlueprintId,
        savedAt: new Date().toISOString(),
        brief: {
          title: "[Descriptive title for the visible artifact.]",
          reader: "[Reader described by the visible artifact.]",
          action: "[Action described by the visible artifact.]",
          purpose: brief.purpose || "understand",
          message: "[Message stated in the visible artifact.]",
          spine: brief.spine || "sequence",
          situation: brief.situation || "laptop",
          form: brief.form || "one-page",
          sources: [],
          assumptions: [],
          gaps: [],
        },
        units: {},
        evidence: {},
        review: {
          ...(singleComposition() ? {} : { approach: currentApproach() }),
          decisions: {},
          theme: (state.review && state.review.theme) || "light",
          location:
            state.review && state.review.location != null
              ? state.review.location
              : null,
        },
      };
      if (state.sourceSchemaVersion != null)
        next.sourceSchemaVersion = state.sourceSchemaVersion;
      if (state.approaches) {
        const approach = currentApproach();
        if (state.approaches[approach])
          next.approaches = {
            [approach]: clone(state.approaches[approach]),
          };
      }
      if (state.composition) {
        next.composition = {
          strategy: state.composition.strategy,
          sections: (state.composition.sections || []).map((section) => ({
            id: section.id,
            unitIds: [...section.unitIds],
            layout: clone(section.layout),
            ...(section.issueLayout
              ? { issueLayout: clone(section.issueLayout) }
              : {}),
          })),
        };
      }
      if (state.overview) next.overview = clone(state.overview);
      if (state.style)
        next.style = { source: state.style.source || "loxoto", ref: null };
      const presentIds = [];
      all("section.ld-unit[data-unit]", cloneRoot).forEach((section) => {
        const id = section.getAttribute("data-unit");
        const sourceUnit = state.units && state.units[id];
        if (!sourceUnit) return;
        presentIds.push(id);
        const selected = sourceUnit.selected === "b" ? "b" : "a";
        const variant = section.querySelector(
          '.ld-variant[data-variant="' + selected + '"]',
        );
        const body = variant && variant.querySelector("[data-variant-body]");
        const finalHTML = body ? body.innerHTML : "";
        const axis =
          (sourceUnit.variants &&
            sourceUnit.variants[selected] &&
            sourceUnit.variants[selected].axis) ||
          "single";
        const canonicalVariant = { axis: axis, why: "" };
        if (sourceUnit.kind !== "figure") canonicalVariant.html = finalHTML;
        const unit = {
          kind: sourceUnit.kind,
          selected: selected,
          variants: { a: clone(canonicalVariant) },
          edits: { a: selected === "a" ? finalHTML : null },
          placeholder:
            sourceUnit.kind === "card"
              ? PLACEHOLDERS.cardLine
              : sourceUnit.kind === "figure"
                ? PLACEHOLDERS.figureLabel
                : sourceUnit.kind === "decision"
                  ? "[The decision the reader must make.]"
                  : PLACEHOLDERS.title,
        };
        if (selected === "b") {
          unit.variants.b = clone(canonicalVariant);
          unit.edits.b = sourceUnit.kind === "figure" ? null : finalHTML;
        }
        next.units[id] = unit;
      });
      Object.keys((state.review && state.review.decisions) || {}).forEach(
        (id) => {
          if (presentIds.includes(id))
            next.review.decisions[id] = clone(state.review.decisions[id]);
        },
      );
      return next;
    }
    function placeholderFor(component, key, path, value) {
      if (value === "") return value;
      if (TEMPLATE_STRUCTURAL_PARAM_KEYS.has(key)) return value;
      if (key === "sub" || /Sub$/.test(key))
        return "[One fact about it, under twenty words.]";

      const phrases = {
        flow: {
          title: "[Stage name.]",
          edges: "[What passes to the next stage.]",
        },
        timeline: { when: "[Date.]", title: "[What happened.]" },
        hierarchy: {
          root: "[The authority at the top.]",
          title: path.includes("leaves") ? "[Leaf.]" : "[Branch.]",
          edge: "[What connects it.]",
        },
        zones: {
          zoneA: "[Zone label.]",
          zoneB: "[Zone label.]",
          title: "[Item in this zone.]",
          crossing: "[What crosses between the zones.]",
        },
        beforeAfter: {
          beforeLabel: "[State before.]",
          afterLabel: "[State after.]",
          edge: "[What changes it.]",
        },
        hub: {
          centre: "[Central idea.]",
          title: "[Spoke.]",
          edge: "[What connects it.]",
        },
        compareTwo: {
          a: path.includes("rows") ? "[Value.]" : "[Option name.]",
          b: path.includes("rows") ? "[Value.]" : "[Option name.]",
          label: "[Criterion.]",
        },
        funnel: {
          title: "[Tier.]",
          edges: "[What passes to the next tier.]",
          outcome: "[Outcome.]",
        },
        loop: {
          centre: "[Central idea.]",
          title: "[Step.]",
          edge: "[What leads to the next step.]",
        },
        matrix: {
          xAxis: "[Axis label.]",
          yAxis: "[Axis label.]",
          title: "[Quadrant.]",
        },
        figurePath: {
          marks: "[Milestone.]",
          goal: "[Goal.]",
          caption: "[What the path shows.]",
        },
        stairSteps: {
          title: "[Step.]",
          caption: "[What the steps show.]",
        },
        pyramid: {
          title: "[Level.]",
          caption: "[What the levels show.]",
        },
        gears: {
          labels: "[Moving part.]",
          caption: "[How the parts work together.]",
        },
      };
      return (phrases[component] && phrases[component][key]) || "[Label.]";
    }
    function validTemplatePlaceholder(value) {
      if (
        typeof value !== "string" ||
        !/^\[[^[\]\r\n]{3,200}\]$/.test(value.trim())
      )
        return false;
      return new Set([
        "[0 checked · 0 need attention]",
        "[01]",
        "[AREA · STATUS]",
        "[Agreed in this draft: the settled sections, listed.]",
        "[Amount, security, valuation · investor.]",
        "[Area name, e.g. Formation]",
        "[Area name.]",
        "[Area · document and page.]",
        "[Assumption the reader should know.]",
        "[Axis label.]",
        "[Branch.]",
        "[Candidate visual treatment.]",
        "[Central idea.]",
        "[Citation or record label.]",
        "[Clip of the source page with the cited passage highlighted.]",
        "[Column heading.]",
        "[Company name, or the number of findings that matter.]",
        "[Company · transaction · report date.]",
        "[Criterion.]",
        "[Date.]",
        "[Date. What it means.]",
        "[Decision or action.]",
        "[Document name.]",
        "[Document, section, and page.]",
        "[Each date with what happened or came due on it.]",
        "[Expected record: the document, approval, or filing that would resolve the point.]",
        "[Fact label, e.g. Incorporated]",
        "[Finding not included in this sample.]",
        "[First amount.]",
        "[First position.]",
        "[Goal.]",
        "[How choices are saved.]",
        "[How position, direction, grouping, scale, or contrast expresses the relationship.]",
        "[How the parts work together.]",
        "[How to read this report.]",
        "[How to use the page, in one line.]",
        "[How to use the slide.]",
        "[Item in this zone.]",
        "[Known gap or unresolved point.]",
        "[Label.]",
        "[Leaf.]",
        "[Level.]",
        "[List item.]",
        "[Matter or deliverable title.]",
        "[Section heading.]",
        "[Finding title.]",
        "[Scope and limits.]",
        "[Required conditions.]",
        "[Options and next action.]",
        "[Review summary.]",
        "[Findings and consequences.]",
        "[Follow-up plan.]",
        "[Actions before proceeding.]",
        "[Review coverage.]",
        "[Method workflow.]",
        "[Method structure.]",
        "[Matter · document · draft · date received.]",
        "[Matter · the question this page answers.]",
        "[Milestone.]",
        "[Moving part.]",
        "[NN]",
        "[Not included in this template.]",
        "[One card or row per finding that needs attention.]",
        "[One fact about it, under twenty words.]",
        "[One line that qualifies the title.]",
        "[Open issue · section and name.]",
        "[Open item name.]",
        "[Open the document in the data room.]",
        "[Open the source.]",
        "[Option A.]",
        "[Option B.]",
        "[Option C.]",
        "[Outcome.]",
        "[PROPOSED INVESTMENT]",
        "[Page label.]",
        "[Provenance line.]",
        "[QUESTION · JURISDICTION OR MATTER]",
        "[QUESTION · MATTER]",
        "[REPORT AREA]",
        "[SECTION N OF TOTAL · COUNT DOCUMENTS CHECKED]",
        "[STATUS]",
        "[Scale end.]",
        "[Scale label and gap.]",
        "[Scale start.]",
        "[Second amount.]",
        "[Second position.]",
        "[Section and page.]",
        "[Short answer.]",
        "[Source and locator.]",
        "[Source of the figure's facts.]",
        "[Sources and the date they were read.]",
        "[Spoke.]",
        "[Stage name.]",
        "[State after.]",
        "[State before.]",
        "[Step.]",
        "[Table value.]",
        "[Term or category.]",
        "[The area, or its exceptions, in one sentence.]",
        "[The authority at the top.]",
        "[The choice the reader makes, as a question.]",
        "[The claim in one sentence, ending with a period.]",
        "[The clause, in the document’s words.]",
        "[The closing condition, cure, covenant, representation, or follow-up.]",
        "[The consequence of option A.]",
        "[The consequence of option B.]",
        "[The consequence of option C.]",
        "[The counts.]",
        "[The decision the reader must make.]",
        "[The finding in five words.]",
        "[The finding in one sentence.]",
        "[The open item in five words.]",
        "[The practical consequence for this transaction.]",
        "[The question this card answers?]",
        "[The reader and what they already know.]",
        "[The source reviewed, the controlling fact, and the exact gap, with dates and numbers.]",
        "[The value or fact this block communicates.]",
        "[Timing or provenance.]",
        "[Two sentences that tell the reader what they are looking at.]",
        "[Value from the data room.]",
        "[Value.]",
        "[Verbatim excerpt of the controlling passage, in the document's words.]",
        "[What changes it.]",
        "[What connects it.]",
        "[What crosses between the zones.]",
        "[What happened.]",
        "[What happens on this date.]",
        "[What is open, in one line.]",
        "[What it means.]",
        "[What leads to the next step.]",
        "[What passes to the next stage.]",
        "[What passes to the next tier.]",
        "[What the figure shows, as a claim.]",
        "[What the levels show.]",
        "[What the path shows.]",
        "[What the steps show.]",
        "[What the table shows.]",
        "[What the value means.]",
        "[What this report is and who it is for.]",
        "[What this section enables the reader to understand or do.]",
        "[What this source establishes, in one sentence.]",
        "[What was found, with the date.]",
        "[Why this composition best communicates the core relationship.]",
        "[Why this treatment fits the reader and purpose.]",
        "[Zone label.]",
        "[§ Section]",
        "[§N]",
      ]).has(value.trim());
    }
    function preferredTemplatePlaceholder(value, fallback) {
      return validTemplatePlaceholder(value) ? value.trim() : fallback;
    }
    function placeholderNumber(key, path) {
      const indexes = (path || []).filter((part) => typeof part === "number");
      const ordinal = (indexes.length ? indexes[indexes.length - 1] : 0) + 1;
      const fixed = {
        nowAt: 0,
        offset: 0,
        location: 0,
        min: 0,
        minValue: 0,
        scaleMin: 0,
        low: 20,
        q1: 25,
        before: 30,
        a: 30,
        start: 40,
        median: 50,
        mark: 50,
        markAt: 50,
        after: 60,
        b: 60,
        q3: 75,
        high: 80,
        max: 100,
        maxValue: 100,
        scaleMax: 100,
        delta: 10,
      };
      if (key === "value") return ordinal;
      if (Object.hasOwn(fixed, key)) return fixed[key];
      if (key === "x") return ordinal;
      if (key === "y") return ordinal * 10;
      return ordinal * 10;
    }
    function placeholderParams(value, key, evidenceMap, component, path) {
      path = path || [];
      if (Array.isArray(value))
        return value.map((item, index) =>
          placeholderParams(
            item,
            key,
            evidenceMap,
            component,
            path.concat(index),
          ),
        );
      if (value && typeof value === "object") {
        const out = {};
        Object.keys(value).forEach((name) => {
          const placeholder = placeholderParams(
            value[name],
            name,
            evidenceMap,
            component,
            path.concat(name),
          );
          if (placeholder !== undefined) out[name] = placeholder;
        });
        return out;
      }
      if (key === "detail" || /Detail$/.test(key))
        return evidenceMap[value] || undefined;
      if (TEMPLATE_STRUCTURAL_PARAM_KEYS.has(key)) return value;
      if (typeof value === "number") return placeholderNumber(key, path);
      return typeof value === "string"
        ? placeholderFor(component, key, path, value)
        : value;
    }
    function addTemplateId(order, seen, value) {
      if (typeof value !== "string" || !value || Object.hasOwn(seen, value))
        return;
      seen[value] = true;
      order.push(value);
    }
    function idMap(order, prefix) {
      const map = Object.create(null);
      order.forEach((id, index) => {
        map[id] = prefix + "-" + (index + 1);
      });
      return map;
    }
    function templateMappings() {
      const orders = {
        section: [],
        unit: [],
        claim: [],
        evidence: [],
        source: [],
      };
      const seen = {
        section: Object.create(null),
        unit: Object.create(null),
        claim: Object.create(null),
        evidence: Object.create(null),
        source: Object.create(null),
      };
      const compositions = state.approaches
        ? ["a", "b"].map(
            (key) => (state.approaches[key] || {}).composition || {},
          )
        : [state.composition || {}];
      compositions.forEach((composition) => {
        (composition.sections || []).forEach((section) => {
          addTemplateId(orders.section, seen.section, section.id);
          (section.unitIds || []).forEach((id) => {
            addTemplateId(orders.unit, seen.unit, id);
          });
          ((section.layout || {}).placements || []).forEach((placement) => {
            addTemplateId(orders.unit, seen.unit, placement.unitId);
          });
        });
      });
      Object.keys(state.units || {}).forEach((id) => {
        addTemplateId(orders.unit, seen.unit, id);
      });
      (state.claims || []).forEach((claim) => {
        addTemplateId(orders.claim, seen.claim, claim.id);
        (claim.sourceIds || []).forEach((id) => {
          addTemplateId(orders.source, seen.source, id);
        });
        (claim.evidenceIds || []).forEach((id) => {
          addTemplateId(orders.evidence, seen.evidence, id);
        });
      });
      Object.keys(state.units || {}).forEach((id) => {
        const unit = state.units[id] || {};
        (unit.claimRefs || []).forEach((claimId) => {
          addTemplateId(orders.claim, seen.claim, claimId);
        });
        (unit.evidence || []).forEach((evidenceId) => {
          addTemplateId(orders.evidence, seen.evidence, evidenceId);
        });
      });
      Object.keys(state.evidence || {}).forEach((id) => {
        addTemplateId(orders.evidence, seen.evidence, id);
        const item = state.evidence[id] || {};
        addTemplateId(orders.source, seen.source, item.sourceId);
        (item.claimRefs || []).forEach((claimId) => {
          addTemplateId(orders.claim, seen.claim, claimId);
        });
      });
      ((state.brief || {}).sources || []).forEach((source) => {
        addTemplateId(orders.source, seen.source, source.id);
      });
      const maps = {
        section: idMap(orders.section, "section"),
        unit: idMap(orders.unit, "unit"),
        claim: idMap(orders.claim, "claim"),
        evidence: idMap(orders.evidence, "evidence"),
        source: idMap(orders.source, "source"),
        options: Object.create(null),
        orders: orders,
      };
      Object.keys(state.units || {}).forEach((unitId) => {
        const optionMap = Object.create(null);
        ((state.units[unitId] || {}).options || []).forEach((option, index) => {
          if (option && option.key != null)
            optionMap[String(option.key)] = "option-" + (index + 1);
        });
        maps.options[unitId] = optionMap;
      });
      return maps;
    }
    function mappedIds(values, map) {
      return (values || []).map((id) => map[id]);
    }
    function templateUnitPlaceholder(unit) {
      let fallback = PLACEHOLDERS.title;
      if (unit.kind === "card") fallback = PLACEHOLDERS.cardLine;
      if (unit.kind === "figure") fallback = PLACEHOLDERS.figureLabel;
      if (unit.kind === "decision")
        fallback = "[The decision the reader must make.]";
      return preferredTemplatePlaceholder(unit.placeholder, fallback);
    }
    function templateHeadingPlaceholder(unit = {}, node = null) {
      // Only this finite vocabulary can survive as heading intent. A user's
      // arbitrary bracketed matter text is not a trustworthy template label.
      const headings = new Set([
        "[Matter or deliverable title.]",
        "[Section heading.]",
        "[Finding title.]",
        "[Scope and limits.]",
        "[Short answer.]",
        "[Decision or action.]",
        "[Required conditions.]",
        "[Options and next action.]",
        "[Review summary.]",
        "[Findings and consequences.]",
        "[Follow-up plan.]",
        "[Actions before proceeding.]",
        "[Review coverage.]",
        "[Method workflow.]",
        "[Method structure.]",
      ]);
      for (const candidate of [
        unit.placeholder_title,
        node?.getAttribute("data-placeholder-title"),
        node?.getAttribute("data-placeholder"),
        unit.placeholder,
      ]) {
        if (typeof candidate === "string" && headings.has(candidate.trim()))
          return candidate.trim();
      }
      const roleHeadings = {
        title: "[Matter or deliverable title.]",
        matter: "[Matter or deliverable title.]",
        answer: "[Short answer.]",
        finding: "[Finding title.]",
        scope: "[Scope and limits.]",
        action: "[Decision or action.]",
        expected: "[Required conditions.]",
      };
      return Object.hasOwn(roleHeadings, unit.role)
        ? roleHeadings[unit.role]
        : "[Section heading.]";
    }
    function templateLegacyCollection(value, name, maps) {
      const allowed = LEGACY_COLLECTION_FIELDS[name];
      if (!allowed || !Array.isArray(value)) return [];
      return value.map((item, index) => {
        if (!item || typeof item !== "object" || Array.isArray(item)) return {};
        const next = {};
        Object.keys(item).forEach((key) => {
          if (!allowed.has(key)) return;
          next[key] = placeholderParams(item[key], key, maps.evidence, null, [
            index,
            key,
          ]);
        });
        return next;
      });
    }
    function templateVariant(variant, unit, maps) {
      const next = {};
      let component =
        typeof variant.component === "string" &&
        (LD.registry || []).includes(variant.component)
          ? variant.component
          : null;
      if (component) {
        try {
          LD.render(component, variant.params, {});
        } catch (_) {
          component = null;
        }
      }
      Object.keys(variant).forEach((name) => {
        if (!VARIANT_FIELDS.has(name)) return;
        if (name === "axis") {
          next.axis = VARIANT_AXES.includes(variant.axis)
            ? variant.axis
            : unit.single
              ? "single"
              : "form";
          return;
        }
        if (name === "component") {
          if (component) next.component = component;
          return;
        }
        if (name === "params") {
          if (component) {
            next.params = placeholderParams(
              variant.params,
              "params",
              maps.evidence,
              component,
              [],
            );
            try {
              LD.render(component, next.params, {});
            } catch (error) {
              throw new TypeError(
                `template ${component} parameters became invalid during sanitization: ${error.message}`,
              );
            }
          }
          return;
        }
        if (Object.hasOwn(LEGACY_COLLECTION_FIELDS, name)) {
          next[name] = templateLegacyCollection(variant[name], name, maps);
          return;
        }
        if (name === "layout") {
          if (LEGACY_LAYOUTS.includes(variant.layout))
            next.layout = variant.layout;
          return;
        }
        if (name === "why") {
          next[name] = "[Why this treatment fits the reader and purpose.]";
          return;
        }
        if (name === "encoding") {
          next[name] =
            "[How position, direction, grouping, scale, or contrast expresses the relationship.]";
          return;
        }
        if (name === "html") {
          next[name] = templateUnitPlaceholder(unit);
          return;
        }
        if (name === "title") {
          next[name] = PLACEHOLDERS.cardTitle;
          return;
        }
        next[name] = placeholderParams(
          variant[name],
          name,
          maps.evidence,
          component,
          [],
        );
      });
      return next;
    }
    function templateUnit(unitId, maps) {
      const unit = state.units[unitId] || {};
      const next = {
        kind: UNIT_KINDS.includes(unit.kind) ? unit.kind : "text",
        selected: unit.selected === "b" ? "b" : "a",
      };
      if (UNIT_ROLES.includes(unit.role)) next.role = unit.role;
      if (["single", "multiple"].includes(unit.selection_mode))
        next.selection_mode = unit.selection_mode;
      ["allow_custom", "allow_note", "single", "sourcePreview"].forEach(
        (name) => {
          if (typeof unit[name] === "boolean") next[name] = unit[name];
        },
      );
      if (unit.claim != null)
        next.claim =
          unit.kind === "decision"
            ? "[The decision this unit presents.]"
            : templateUnitPlaceholder(unit);
      if (unit.relationship != null)
        next.relationship = RELATIONSHIPS.includes(unit.relationship)
          ? unit.relationship
          : "containment";
      if (Array.isArray(unit.candidates))
        next.candidates = unit.candidates.map(
          () => "[Candidate visual treatment.]",
        );
      if (Array.isArray(unit.claimRefs))
        next.claimRefs = mappedIds(unit.claimRefs, maps.claim);
      if (Array.isArray(unit.evidence))
        next.evidence = mappedIds(unit.evidence, maps.evidence);
      if (typeof unit.detail === "string" && maps.evidence[unit.detail])
        next.detail = maps.evidence[unit.detail];
      next.variants = {};
      Object.keys(unit.variants || {}).forEach((key) => {
        next.variants[key] = templateVariant(unit.variants[key], unit, maps);
      });
      next.edits = {};
      Object.keys(
        Object.assign({}, unit.variants || {}, unit.edits || {}),
      ).forEach((key) => {
        next.edits[key] = null;
      });
      next.placeholder = templateUnitPlaceholder(unit);
      if (unit.placeholder_title != null)
        next.placeholder_title = templateHeadingPlaceholder(unit);
      if (unit.placeholder_sub != null)
        next.placeholder_sub = "[One fact about it, under twenty words.]";
      if (unit.source_note != null) next.source_note = "[Source and locator.]";
      if (unit.tag != null) next.tag = "[Open issue · section and name.]";
      if (unit.question != null)
        next.question = "[The choice the reader makes, as a question.]";
      if (Array.isArray(unit.options)) {
        const optionMap = maps.options[unitId] || {};
        next.options = unit.options.map((option, index) => {
          const key = optionMap[String(option.key)] || "option-" + (index + 1);
          return {
            key: key,
            label: "[Option " + (index + 1) + ".]",
            consequence: "[The consequence of option " + (index + 1) + ".]",
          };
        });
      }
      return next;
    }
    function templateEvidence(item, maps) {
      const next = {
        cite: "[Citation or record label.]",
        locator: "[Section and page.]",
        excerpt:
          "[Verbatim excerpt of the controlling clause, in the document's words.]",
        link: item.link == null ? null : "#",
        status: "supplied-unverified",
      };
      if (item.sourceId != null) next.sourceId = maps.source[item.sourceId];
      if (Array.isArray(item.claimRefs))
        next.claimRefs = mappedIds(item.claimRefs, maps.claim);
      if (item.detail != null)
        next.detail = "[What this source establishes, in one sentence.]";
      if (item.popup) {
        next.popup = {
          type: ["source", "explainer", "detail"].includes(item.popup.type)
            ? item.popup.type
            : "detail",
          title: "[The question or insight this popup explains.]",
          sections: (item.popup.sections || []).map(() => ({
            heading: "[Specific subpoint.]",
            body: "[The deeper detail the reader needs here.]",
          })),
        };
        if (item.popup.lede != null)
          next.popup.lede = "[One-sentence orientation to the deeper detail.]";
      }
      if (item.original) {
        next.original = {
          availability:
            item.original.availability === "linked" ? "linked" : "unavailable",
          label: "[Original source label.]",
        };
        if (next.original.availability === "linked") next.original.href = "#";
      }
      if (item.image) {
        next.image = {
          status:
            "[Clip of the source page with the cited passage highlighted.]",
        };
        if (Object.hasOwn(item.image, "data")) next.image.data = null;
      }
      if (item.provenance != null)
        next.provenance = "[Source, version, and retrieval note.]";
      return next;
    }
    function isPackagedTemplateId(requestedId) {
      return /^legaldesign-template-[a-z0-9][a-z0-9-]{0,79}$/.test(
        requestedId || "",
      );
    }
    function templateExportId(requestedId) {
      if (isPackagedTemplateId(requestedId)) return requestedId;
      let suffix = "";
      if (window.crypto && typeof window.crypto.randomUUID === "function")
        suffix = window.crypto.randomUUID();
      else
        suffix =
          Date.now().toString(36) +
          "-" +
          Math.random().toString(36).slice(2, 14);
      return "legaldesign-template-" + suffix.toLowerCase();
    }
    function templateState(maps, templateId) {
      const brief = state.brief || {};
      const nextBrief = {
        title: "[Descriptive title for the artifact.]",
        reader: "[The reader and what they already know.]",
        action: "[What the reader should decide or do.]",
        purpose: brief.purpose,
        message: "[The answer the page should make clear.]",
        spine: brief.spine,
        situation: brief.situation,
        form: brief.form,
        sources: (brief.sources || []).map((source) => {
          const next = {
            id: maps.source[source.id],
            label: "[Source label.]",
            status: "supplied-unverified",
          };
          if (source.note != null) next.note = "[Source status note.]";
          return next;
        }),
        assumptions: (brief.assumptions || []).map(
          () => "[Assumption the reader should know.]",
        ),
        gaps: (brief.gaps || []).map(() => "[Known gap or unresolved point.]"),
      };
      if (brief.run != null) nextBrief.run = {};
      const next = {
        schema: state.schema,
        artifactId: templateId,
        savedAt: new Date().toISOString(),
        brief: nextBrief,
        units: {},
        evidence: {},
        review: {
          ...(singleComposition() ? {} : { approach: "a" }),
          decisions: {},
          theme: (state.review && state.review.theme) || "light",
          location: null,
        },
        history: [],
      };
      if (state.sourceSchemaVersion != null)
        next.sourceSchemaVersion = state.sourceSchemaVersion;
      if (state.style != null) next.style = { source: "template", ref: null };
      if (state.approaches) {
        next.approaches = {};
        ["a", "b"].forEach((key) => {
          const approach = state.approaches[key] || {};
          const composition = approach.composition || {};
          next.approaches[key] = {
            label: key === "a" ? "Page A" : "Page B",
            rationale:
              "[Why this complete page approach fits the reader and purpose.]",
            composition: {
              strategy: composition.strategy,
              shape: "[The section sequence and reading path.]",
              rationale:
                "[Why this composition best communicates the core relationship.]",
              sections: (composition.sections || []).map((section) => ({
                id: maps.section[section.id],
                purpose:
                  "[What this section enables the reader to understand or do.]",
                unitIds: mappedIds(section.unitIds, maps.unit),
                layout: {
                  columns: (section.layout || {}).columns || 12,
                  placements: ((section.layout || {}).placements || []).map(
                    (placement) => ({
                      unitId: maps.unit[placement.unitId],
                      row: placement.row,
                      column: placement.column,
                      span: placement.span,
                      ...(placement.rowSpan
                        ? { rowSpan: placement.rowSpan }
                        : {}),
                    }),
                  ),
                },
              })),
              templateRef:
                composition.strategy === "template-import"
                  ? "[Template or playbook reference.]"
                  : null,
            },
          };
        });
      }
      if (state.composition) {
        next.composition = {
          strategy: state.composition.strategy,
          shape: "[The section sequence and reading path.]",
          rationale:
            "[Why this composition best communicates the core relationship.]",
          sections: (state.composition.sections || []).map((section) => ({
            id: maps.section[section.id],
            purpose:
              "[What this section enables the reader to understand or do.]",
            unitIds: mappedIds(section.unitIds, maps.unit),
            ...(section.issueLayout
              ? {
                  issueLayout: {
                    findingUnitId: maps.unit[section.issueLayout.findingUnitId],
                    implicationUnitId:
                      maps.unit[section.issueLayout.implicationUnitId],
                    actionUnitId: maps.unit[section.issueLayout.actionUnitId],
                    supportUnitIds: mappedIds(
                      section.issueLayout.supportUnitIds,
                      maps.unit,
                    ),
                  },
                }
              : {}),
            layout: {
              columns: (section.layout || {}).columns || 12,
              placements: ((section.layout || {}).placements || []).map(
                (placement) => ({
                  unitId: maps.unit[placement.unitId],
                  row: placement.row,
                  column: placement.column,
                  span: placement.span,
                  ...(placement.rowSpan ? { rowSpan: placement.rowSpan } : {}),
                }),
              ),
            },
          })),
          templateRef:
            state.composition.strategy === "template-import"
              ? "[Template or playbook reference.]"
              : null,
        };
      }
      if (state.overview) {
        next.overview = {
          sectionId: maps.section[state.overview.sectionId],
          contextUnitIds: mappedIds(state.overview.contextUnitIds, maps.unit),
          questionUnitId: maps.unit[state.overview.questionUnitId],
          answerUnitId: maps.unit[state.overview.answerUnitId],
          topics: (state.overview.topics || []).map((topic) => ({
            unitId: maps.unit[topic.unitId],
            targetSectionId: maps.section[topic.targetSectionId],
          })),
          ...(state.overview.groups
            ? {
                groups: state.overview.groups.map((group, index) => ({
                  label: `Topic group ${index + 1}`,
                  topicUnitIds: mappedIds(group.topicUnitIds, maps.unit),
                })),
              }
            : {}),
        };
      }
      if (Array.isArray(state.claims)) {
        next.claims = state.claims.map((claim) => {
          const nextClaim = {
            id: maps.claim[claim.id],
            text:
              claim.kind === "context"
                ? "[Context the reader needs.]"
                : "[Material claim the artifact communicates.]",
            kind: claim.kind,
            sourceIds: mappedIds(claim.sourceIds, maps.source),
            placement: claim.placement,
            evidenceIds: mappedIds(claim.evidenceIds, maps.evidence),
          };
          if (claim.omissionReason != null)
            nextClaim.omissionReason =
              "[Why this claim belongs in detail or is omitted.]";
          return nextClaim;
        });
      }
      maps.orders.unit.forEach((id) => {
        if (state.units && state.units[id])
          next.units[maps.unit[id]] = templateUnit(id, maps);
      });
      maps.orders.evidence.forEach((id) => {
        if (state.evidence && state.evidence[id])
          next.evidence[maps.evidence[id]] = templateEvidence(
            state.evidence[id],
            maps,
          );
      });
      return next;
    }
    function sanitizeTemplateDOM(cloneRoot, nextState, maps) {
      const unitMap = Object.assign(Object.create(null), maps.unit);
      const sectionMap = Object.assign(Object.create(null), maps.section);
      const referenceMap = Object.assign(Object.create(null), maps.evidence);
      const classMap = Object.create(null);
      const mappedReferenceValues = Object.keys(referenceMap).reduce(
        (values, key) => {
          values[referenceMap[key]] = true;
          return values;
        },
        Object.create(null),
      );
      let nextUnitIndex = Object.keys(unitMap).length;
      let nextSectionIndex = Object.keys(sectionMap).length;
      let nextDetailIndex = 0;
      function mappedDOMId(map, oldId, prefix, nextIndex) {
        if (Object.hasOwn(map, oldId)) return map[oldId];
        const value = prefix + "-" + nextIndex();
        map[oldId] = value;
        return value;
      }
      function mappedReference(oldId) {
        if (Object.hasOwn(referenceMap, oldId)) return referenceMap[oldId];
        if (mappedReferenceValues[oldId]) return oldId;
        return mappedDOMId(referenceMap, oldId, "detail", () => {
          nextDetailIndex += 1;
          const id = nextDetailIndex;
          mappedReferenceValues["detail-" + id] = true;
          return id;
        });
      }
      function rewriteAttributeSelectorMap(css, attribute, map) {
        const staged = [];
        let prefix = "__ld_attribute_stage__";
        while (css.includes(prefix)) prefix = "_" + prefix;
        Object.keys(map).forEach((oldValue, index) => {
          const temporary = prefix + attribute + "-" + index;
          css = rewriteAttributeSelector(css, attribute, oldValue, temporary);
          staged.push([temporary, map[oldValue]]);
        });
        staged.forEach((entry) => {
          css = rewriteAttributeSelector(css, attribute, entry[0], entry[1]);
        });
        return css;
      }
      function rewriteClassSelectorMap(css, map) {
        const staged = [];
        let prefix = "__ld_class_stage__";
        while (css.includes(prefix)) prefix = "_" + prefix;
        Object.keys(map).forEach((oldValue, index) => {
          const temporary = prefix + index;
          const escaped = oldValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
          css = css.replace(
            new RegExp("\\." + escaped + "(?![A-Za-z0-9_-])", "g"),
            "." + temporary,
          );
          staged.push([temporary, map[oldValue]]);
        });
        staged.forEach((entry) => {
          // Stage 1 must not also replace stage 10, 11, ... . A substring
          // replacement silently detaches CSS from larger template class maps.
          const escaped = entry[0].replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
          css = css.replace(
            new RegExp("\\." + escaped + "(?![A-Za-z0-9_-])", "g"),
            "." + entry[1],
          );
        });
        return css;
      }
      function remapIdReferences(oldId, newId) {
        if (!oldId || oldId === newId) return;
        all(
          "[for],[aria-controls],[aria-labelledby],[aria-describedby]",
          cloneRoot,
        ).forEach((node) => {
          [
            "for",
            "aria-controls",
            "aria-labelledby",
            "aria-describedby",
          ].forEach((name) => {
            if (!node.hasAttribute(name)) return;
            const value = node
              .getAttribute(name)
              .split(/\s+/)
              .map((id) => (id === oldId ? newId : id));
            node.setAttribute(name, value.join(" "));
          });
        });
        all("[href]", cloneRoot).forEach((node) => {
          if (node.getAttribute("href") === "#" + oldId)
            node.setAttribute("href", "#" + newId);
        });
      }
      function rewriteAttributeSelector(css, attribute, oldValue, newValue) {
        [
          `[${attribute}="${oldValue}"]`,
          `[${attribute}='${oldValue}']`,
          `[${attribute}=${oldValue}]`,
        ].forEach((selector, index) => {
          const quote = index === 0 ? '"' : index === 1 ? "'" : "";
          const replacement = `[${attribute}=${quote}${newValue}${quote}]`;
          css = css.split(selector).join(replacement);
        });
        return css;
      }
      function nodePlaceholder(node, fallback) {
        return preferredTemplatePlaceholder(
          node.getAttribute("data-placeholder"),
          fallback,
        );
      }
      function semanticPlaceholder(node, fallback) {
        const name = node.localName;
        let semanticFallback = fallback;
        if (/^h[1-4]$/.test(name)) {
          const id = node.closest("[data-unit]")?.getAttribute("data-unit");
          return templateHeadingPlaceholder(
            nextState.units[id] || state.units[id],
            node,
          );
        }
        if (name === "li") semanticFallback = "[List item.]";
        if (name === "th") semanticFallback = "[Column heading.]";
        if (name === "td") semanticFallback = "[Table value.]";
        if (name === "caption") semanticFallback = "[What the table shows.]";
        if (name === "dt") semanticFallback = "[Term or category.]";
        if (name === "dd") semanticFallback = "[What it means.]";
        return nodePlaceholder(node, semanticFallback);
      }
      function scrubEditableStructure(body, heading, fallback) {
        const editables = all("[data-editable]", body);
        if (body.matches("[data-editable]")) editables.unshift(body);
        const scrubbed = new Set();
        editables.forEach((editable) => {
          if (editable === heading || scrubbed.has(editable)) return;
          if (!editable.children.length) {
            const placeholder = semanticPlaceholder(editable, fallback);
            editable.setAttribute("data-placeholder", placeholder);
            editable.textContent = placeholder;
            scrubbed.add(editable);
            return;
          }
          let semantic = all("h1,h2,h3,h4,p,li,td,th,caption,dt,dd", editable);
          if (editable.matches("h1,h2,h3,h4,p,li,td,th,caption,dt,dd"))
            semantic.unshift(editable);
          semantic = semantic.filter((node) => {
            if (node === editable) return true;
            const ancestor = node.parentElement.closest(
              "h1,h2,h3,h4,p,li,td,th,caption,dt,dd",
            );
            return !ancestor || !editable.contains(ancestor);
          });
          if (!semantic.length)
            semantic = all("span,small,strong,em", editable).filter(
              (node) => !node.querySelector("span,small,strong,em"),
            );
          if (!semantic.length) {
            const placeholder = nodePlaceholder(editable, fallback);
            editable.setAttribute("data-placeholder", placeholder);
            editable.textContent = placeholder;
            scrubbed.add(editable);
            return;
          }
          semantic.forEach((node) => {
            if (node === heading || scrubbed.has(node)) return;
            const placeholder = semanticPlaceholder(node, fallback);
            node.setAttribute("data-placeholder", placeholder);
            node.textContent = placeholder;
            scrubbed.add(node);
          });
        });
        const remaining = all("h1,h2,h3,h4,p,li,td,th,caption,dt,dd", body);
        if (body.matches("h1,h2,h3,h4,p,li,td,th,caption,dt,dd"))
          remaining.unshift(body);
        remaining
          .filter((node) => {
            if (node === heading || scrubbed.has(node)) return false;
            if (node === body) return true;
            const ancestor = node.parentElement.closest(
              "h1,h2,h3,h4,p,li,td,th,caption,dt,dd",
            );
            return !ancestor || !body.contains(ancestor);
          })
          .forEach((node) => {
            const placeholder = semanticPlaceholder(node, fallback);
            node.setAttribute("data-placeholder", placeholder);
            node.textContent = placeholder;
            scrubbed.add(node);
          });
        const walker = document.createTreeWalker(body, NodeFilter.SHOW_TEXT);
        const textNodes = [];
        while (walker.nextNode()) textNodes.push(walker.currentNode);
        textNodes.forEach((textNode) => {
          if (!textNode.textContent.trim()) return;
          const parent = textNode.parentElement;
          if (
            !parent ||
            parent.closest("script,style,.ld-diagram") ||
            parent.closest("h1,h2,h3,h4,p,li,td,th,caption,dt,dd") ||
            validTemplatePlaceholder(textNode.textContent.trim())
          )
            return;
          textNode.textContent = nodePlaceholder(parent, fallback);
        });
      }
      function editedFigureRecords(host) {
        if (!host || !host.hasAttribute("data-editor-preserve-dom"))
          return null;
        const records = all(
          'svg [data-diagram-object],svg g[role="button"]',
          host,
        ).map((node, index) => ({
          key:
            node.getAttribute("data-editor-node-key") || "node-" + (index + 1),
          transform: node.style.transform || "",
          transformBox: node.style.transformBox || "",
          transformOrigin: node.style.transformOrigin || "",
          detail: node.getAttribute("data-detail") || null,
          x: node.getAttribute("data-editor-x") || "0",
          y: node.getAttribute("data-editor-y") || "0",
          sx: node.getAttribute("data-editor-scale-x") || "1",
          sy: node.getAttribute("data-editor-scale-y") || "1",
          fill: FILL_TOKENS.includes(node.getAttribute("data-fill-token"))
            ? node.getAttribute("data-fill-token")
            : null,
          text: TEXT_TOKENS.includes(node.getAttribute("data-text-token"))
            ? node.getAttribute("data-text-token")
            : null,
          labels: ownedFigureLabels(node).map(figureLabelPresentation),
        }));
        records.looseLabels = all("svg text", host)
          .filter(
            (label) => !label.closest('[data-diagram-object],g[role="button"]'),
          )
          .map(figureLabelPresentation);
        return records;
      }
      function ownedFigureLabels(node) {
        return all("text", node).filter(
          (label) =>
            label.closest('[data-diagram-object],g[role="button"]') === node,
        );
      }
      function figureLabelPresentation(label) {
        const styles = {};
        for (const property of [
          "transform",
          "transform-box",
          "transform-origin",
          "font-size",
          "font-weight",
          "font-style",
          "text-decoration-line",
        ]) {
          const value = label.style.getPropertyValue(property);
          if (value && safeTemplateStyleValue(property, value))
            styles[property] = value;
        }
        const geometry = {};
        for (const name of ["x", "y", "scale-x", "scale-y"]) {
          const value = label.getAttribute("data-editor-" + name);
          if (value !== null && Number.isFinite(Number(value)))
            geometry[name] = String(Number(value));
        }
        return {
          styles,
          geometry,
          fill: FILL_TOKENS.includes(label.getAttribute("data-fill-token"))
            ? label.getAttribute("data-fill-token")
            : null,
          text: TEXT_TOKENS.includes(label.getAttribute("data-text-token"))
            ? label.getAttribute("data-text-token")
            : null,
        };
      }
      function restoreFigureLabelPresentation(labels, records) {
        (records || []).forEach((record, index) => {
          const label = labels[index];
          if (!label) return;
          Object.entries(record.styles).forEach(([property, value]) => {
            label.style.setProperty(property, value);
          });
          Object.entries(record.geometry).forEach(([name, value]) => {
            label.setAttribute("data-editor-" + name, value);
          });
          if (record.styles.transform)
            label.setAttribute("data-editor-base-transform", "");
          if (record.fill) label.setAttribute("data-fill-token", record.fill);
          if (record.text) label.setAttribute("data-text-token", record.text);
        });
      }
      function applyEditedFigureRecords(host, records) {
        if (!records) return;
        const clean = all(
          'svg [data-diagram-object],svg g[role="button"]',
          host,
        );
        const grouped = Object.create(null);
        records.forEach((record) => {
          if (!grouped[record.key]) grouped[record.key] = [];
          grouped[record.key].push(record);
        });
        clean.forEach((node, index) => {
          const key = "node-" + (index + 1);
          const matches = grouped[key] || [];
          if (!matches.length) {
            node.remove();
            return;
          }
          let cursor = node;
          matches.forEach((record, copyIndex) => {
            const target = copyIndex ? node.cloneNode(true) : node;
            if (copyIndex) cursor.insertAdjacentElement("afterend", target);
            target.setAttribute("data-editor-node-key", key);
            target.style.transform = record.transform;
            target.style.transformBox = record.transformBox;
            target.style.transformOrigin = record.transformOrigin;
            target.setAttribute("data-editor-x", record.x);
            target.setAttribute("data-editor-y", record.y);
            target.setAttribute("data-editor-scale-x", record.sx);
            target.setAttribute("data-editor-scale-y", record.sy);
            target.setAttribute("data-editor-base-transform", "");
            if (record.detail) {
              target.setAttribute("data-detail", record.detail);
              target.setAttribute("aria-controls", record.detail);
              target.setAttribute("role", "button");
              target.setAttribute("tabindex", "0");
              target.classList.add("ld-detail");
            } else {
              [
                "data-detail",
                "aria-controls",
                "aria-haspopup",
                "role",
                "tabindex",
              ].forEach((attr) => {
                target.removeAttribute(attr);
              });
              target.classList.remove("ld-detail");
            }
            if (record.fill)
              target.setAttribute("data-fill-token", record.fill);
            else target.removeAttribute("data-fill-token");
            if (record.text)
              target.setAttribute("data-text-token", record.text);
            else target.removeAttribute("data-text-token");
            restoreFigureLabelPresentation(
              ownedFigureLabels(target),
              record.labels,
            );
            cursor = target;
          });
        });
        restoreFigureLabelPresentation(
          all("svg text", host).filter(
            (label) => !label.closest('[data-diagram-object],g[role="button"]'),
          ),
          records.looseLabels,
        );
        host.setAttribute("data-editor-preserve-dom", "true");
      }
      const documentTitle = cloneRoot.matches(
        "[data-document-title-placeholder]",
      )
        ? cloneRoot
        : cloneRoot.querySelector("[data-document-title-placeholder]");
      if (documentTitle)
        documentTitle.setAttribute(
          "data-document-title-placeholder",
          "[Matter] · LegalDesign template",
        );
      const title = cloneRoot.querySelector("title");
      if (title)
        title.textContent = documentTitle
          ? documentTitle.getAttribute("data-document-title-placeholder")
          : "[Matter] · LegalDesign template";
      const matter = cloneRoot.querySelector(".ld-matter");
      if (matter) {
        matter.setAttribute(
          "data-placeholder",
          "[Matter or deliverable title.]",
        );
        matter.textContent = "[Matter or deliverable title.]";
      }
      all("[data-placeholder]", cloneRoot).forEach((node) => {
        const fallback = node.hasAttribute("data-card-title")
          ? PLACEHOLDERS.cardTitle
          : node.closest(".ld-diagram")
            ? PLACEHOLDERS.figureLabel
            : PLACEHOLDERS.title;
        const unitId = node.closest("[data-unit]")?.getAttribute("data-unit");
        const placeholder = /^h[1-4]$/.test(node.localName)
          ? templateHeadingPlaceholder(state.units[unitId], node)
          : nodePlaceholder(node, fallback);
        node.setAttribute("data-placeholder", placeholder);
        node.textContent = placeholder;
      });
      all("[data-placeholder-title]", cloneRoot).forEach((node) => {
        const unitId = node.closest("[data-unit]")?.getAttribute("data-unit");
        node.setAttribute(
          "data-placeholder-title",
          templateHeadingPlaceholder(state.units[unitId], node),
        );
      });
      all("[data-placeholder-sub]", cloneRoot).forEach((node) => {
        node.setAttribute(
          "data-placeholder-sub",
          preferredTemplatePlaceholder(
            node.getAttribute("data-placeholder-sub"),
            "[One fact about it, under twenty words.]",
          ),
        );
      });
      all(
        "[data-evidence],[data-detail],[data-evidence-ids]",
        cloneRoot,
      ).forEach((node) => {
        if (node.hasAttribute("data-evidence"))
          node.setAttribute(
            "data-evidence",
            mappedReference(node.getAttribute("data-evidence")),
          );
        if (node.hasAttribute("data-detail"))
          node.setAttribute(
            "data-detail",
            mappedReference(node.getAttribute("data-detail")),
          );
        if (node.hasAttribute("data-evidence-ids"))
          node.setAttribute(
            "data-evidence-ids",
            node
              .getAttribute("data-evidence-ids")
              .split(/\s+/)
              .filter(Boolean)
              .map(mappedReference)
              .join(" "),
          );
      });
      all("[data-section-target]", cloneRoot).forEach((node) => {
        const mapped = sectionMap[node.getAttribute("data-section-target")];
        if (mapped) node.setAttribute("data-section-target", mapped);
        else node.removeAttribute("data-section-target");
      });
      all("[data-composition-section]", cloneRoot).forEach((section) => {
        const oldId = section.getAttribute("data-composition-section");
        if (!Object.hasOwn(sectionMap, oldId)) {
          section.remove();
          return;
        }
        const newId = mappedDOMId(sectionMap, oldId, "section", () => {
          nextSectionIndex += 1;
          return nextSectionIndex;
        });
        section.setAttribute("data-composition-section", newId);
        if (section.hasAttribute("data-index-label"))
          section.setAttribute("data-index-label", "[Section topic]");
        section.setAttribute(
          "aria-label",
          "[What this section enables the reader to understand or do.]",
        );
      });
      all("section.ld-unit[data-unit]", cloneRoot).forEach((section) => {
        const oldId = section.getAttribute("data-unit");
        if (!Object.hasOwn(unitMap, oldId)) {
          section.remove();
          return;
        }
        const id = mappedDOMId(unitMap, oldId, "unit", () => {
          nextUnitIndex += 1;
          return nextUnitIndex;
        });
        section.setAttribute("data-unit", id);
        const unit = nextState.units[id];
        if (!unit) return;
        section.setAttribute("aria-label", unit.claim || unit.placeholder);
        all(".ld-variant", section).forEach((variant) => {
          const key = variant.getAttribute("data-variant");
          const spec = unit.variants && unit.variants[key];
          const existingHosts = all(".ld-diagram", variant);
          const bodies = all("[data-variant-body]", variant).filter(
            (node) => node.parentElement === variant,
          );
          let body = bodies.shift();
          if (!body) {
            body = document.createElement("div");
            body.setAttribute("data-variant-body", "");
            body.setAttribute("data-editable", "");
            variant.appendChild(body);
          }
          let host =
            unit.kind === "figure" ? body.querySelector(".ld-diagram") : null;
          if (!host && unit.kind === "figure") host = existingHosts[0] || null;
          const figureRecords = editedFigureRecords(host);
          if (host && !body.contains(host)) body.appendChild(host);
          bodies.forEach((extra) => {
            extra.remove();
          });
          all(".ld-diagram", variant).forEach((candidate) => {
            if (candidate !== host) candidate.remove();
          });
          if (unit.kind === "figure") {
            Array.from(body.childNodes).forEach((child) => {
              if (child !== host) child.remove();
            });
          }
          if (body && spec && unit.kind !== "figure") {
            const inlineDetails = all(
              ".ld-inline-detail[data-detail]",
              body,
            ).map((link) => ({
              detail: link.getAttribute("data-detail"),
              parent: link.closest("p,li,td,th,dt,dd"),
            }));
            const heading = body.querySelector("[data-card-title]");
            if (heading)
              heading.textContent = templateHeadingPlaceholder(unit, heading);
            scrubEditableStructure(
              body,
              heading,
              unit.placeholder ||
                (unit.kind === "card"
                  ? PLACEHOLDERS.cardLine
                  : PLACEHOLDERS.title),
            );
            // Scrubbing a paragraph replaces its text nodes, including nested
            // phrase links. Restore only the existing targets, with neutral
            // presentation; never resurrect a user-removed popup or its copy.
            inlineDetails.forEach(({ detail, parent }) => {
              const existing = parent && body.contains(parent);
              const line = existing ? parent : document.createElement("p");
              const link = document.createElement("button");
              link.type = "button";
              link.className = "ld-inline-detail";
              link.setAttribute("data-detail", detail);
              link.setAttribute("aria-haspopup", "dialog");
              link.textContent = existing
                ? line.textContent
                : "[Open the supporting explanation.]";
              // Keep the original paragraph slot. Appending a second copy
              // adds unplanned rows and can overflow a previously fitted page.
              line.replaceChildren(link);
              if (!existing) {
                line.className = "ld-detail-linkline";
                body.appendChild(line);
              }
            });
          }
          if (!host && spec && unit.kind === "figure") {
            host = document.createElement("div");
            host.className = "ld-diagram";
            body.appendChild(host);
          }
          if (host && unit.kind === "figure") {
            const oldHostId = host.id;
            const nextHostId = "diagram-" + id + "-" + (key || "a");
            host.id = nextHostId;
            remapIdReferences(oldHostId, nextHostId);
          }
          if (host && spec && spec.component) {
            host.innerHTML = LD.render(spec.component, spec.params, {
              ariaLabel: unit.placeholder || PLACEHOLDERS.figureLabel,
            });
            applyEditedFigureRecords(host, figureRecords);
            annotateDiagramLabels(host, spec.params);
          } else if (host && spec && unit.kind === "figure") {
            host.removeAttribute("data-editor-preserve-dom");
            host.innerHTML =
              '<svg viewBox="0 0 800 420" role="img" aria-label="[Illustration or diagram supplied for this field.]"><rect width="799" height="419" x=".5" y=".5" fill="var(--card)" stroke="var(--line-strong)"/><text x="400" y="210" text-anchor="middle" fill="currentColor" font-family="Helvetica,Arial,sans-serif" font-size="20">[Illustration or diagram supplied for this field.]</text></svg>';
          }
        });
        const why = section.querySelector(".ld-why");
        const selected = unit.variants && unit.variants[unit.selected];
        if (why && selected && selected.why) why.textContent = selected.why;
        all("[data-editable]", section).forEach((node) => {
          if (!node.closest(".ld-variant"))
            node.textContent =
              node.getAttribute("data-placeholder") ||
              (node.hasAttribute("data-card-title")
                ? unit.placeholder_title || PLACEHOLDERS.cardTitle
                : unit.placeholder || PLACEHOLDERS.title);
        });
        if (unit.kind === "decision") {
          section.removeAttribute("data-answered");
          const tag = section.querySelector(".tag");
          if (tag && unit.tag) tag.textContent = unit.tag;
          const question = section.querySelector(".ld-decision h3,h2");
          if (question && unit.question) question.textContent = unit.question;
          all("[data-decision-option]", section).forEach((button, index) => {
            const option = (unit.options || [])[index];
            if (!option) return;
            button.setAttribute("value", option.key);
            button.setAttribute("aria-pressed", "false");
            const key = button.querySelector("strong");
            const legacyLabel = button.querySelector("b");
            const label = button.querySelector("span");
            const consequence = button.querySelector("small");
            if (key) key.textContent = "Option " + (index + 1);
            if (legacyLabel) legacyLabel.textContent = option.label;
            if (label)
              label.textContent = consequence
                ? option.label
                : option.consequence;
            if (consequence) consequence.textContent = option.consequence;
            if (!key && !legacyLabel && !label && !consequence)
              button.textContent = option.label;
          });
        }
        all(".ld-evidence-trigger", section).forEach((trigger) => {
          trigger.textContent = "Source: [Citation or record label.]";
        });
        const walker = document.createTreeWalker(section, NodeFilter.SHOW_TEXT);
        const textNodes = [];
        while (walker.nextNode()) textNodes.push(walker.currentNode);
        textNodes.forEach((textNode) => {
          const value = textNode.textContent.trim();
          if (!value || validTemplatePlaceholder(value)) return;
          const parent = textNode.parentElement;
          if (
            !parent ||
            parent.closest(
              "script,style,.ld-unit-tools,.ld-diagram,.ld-why,[data-decision-option],.ld-evidence-trigger",
            ) ||
            parent.closest("[data-placeholder]")
          )
            return;
          textNode.textContent = unit.placeholder || PLACEHOLDERS.title;
        });
      });
      all("[data-variant-body] [class]", cloneRoot).forEach((node) => {
        if (node.closest(".ld-diagram")) return;
        const next = node
          .getAttribute("class")
          .split(/\s+/)
          .filter(Boolean)
          .map((name) => {
            if (
              name.startsWith("ld-") ||
              ["cite", "tag", "action"].includes(name)
            )
              return name;
            if (!Object.hasOwn(classMap, name))
              classMap[name] =
                "template-class-" + (Object.keys(classMap).length + 1);
            return classMap[name];
          });
        if (next.length) node.setAttribute("class", next.join(" "));
        else node.removeAttribute("class");
      });
      all("section.ld-unit [data-variant-body] img", cloneRoot).forEach(
        (image) => {
          const replacement = document.createElementNS(
            "http://www.w3.org/2000/svg",
            "svg",
          );
          const sourcePreview = image.closest(".ld-source-preview");
          const originalImage = sourcePreview
            ? all(".ld-source-preview img").find(
                (node) =>
                  node.getAttribute("src") === image.getAttribute("src"),
              )
            : null;
          const width = originalImage?.naturalWidth || 800;
          const height = originalImage?.naturalHeight || 420;
          replacement.setAttribute("viewBox", `0 0 ${width} ${height}`);
          replacement.setAttribute("role", "img");
          replacement.setAttribute(
            "aria-label",
            "[Illustration or source image supplied for this field.]",
          );
          replacement.innerHTML = `<rect width="${width - 1}" height="${height - 1}" x=".5" y=".5" fill="var(--card)" stroke="var(--line-strong)"/><text x="${width / 2}" y="${height / 2}" dominant-baseline="middle" text-anchor="middle" fill="currentColor" font-family="Helvetica,Arial,sans-serif" font-size="${Math.min(20, height / 2)}">${sourcePreview ? "[Source clip.]" : "[Illustration or source image supplied for this field.]"}</text>`;
          image.replaceWith(replacement);
        },
      );
      const safeInternalPages = new Set(
        all("[id][data-frame]", cloneRoot)
          .map((node) => node.id)
          .filter((id) => /^p\d+$/.test(id)),
      );
      all("[data-page]", cloneRoot).forEach((node) => {
        if (!safeInternalPages.has(node.getAttribute("data-page")))
          node.removeAttribute("data-page");
      });
      const allowedMatterData = new Set([
        "data-card-title",
        "data-decision-custom",
        "data-decision-note",
        "data-decision-option",
        "data-detail",
        "data-editable",
        "data-diagram-label",
        "data-evidence-field",
        "data-evidence-id",
        "data-editor-node-key",
        "data-editor-object",
        "data-diagram-object",
        "data-editor-preserve-dom",
        "data-evidence",
        "data-fill-token",
        "data-placeholder",
        "data-placeholder-sub",
        "data-placeholder-title",
        "data-page",
        "data-text-token",
        "data-variant-body",
      ]);
      all(
        "section.ld-unit [data-variant-body],section.ld-unit [data-variant-body] *",
        cloneRoot,
      ).forEach((node) => {
        if (node.closest(".ld-diagram")) return;
        Array.from(node.attributes).forEach((attribute) => {
          const name = attribute.name.toLowerCase();
          if (name === "id") {
            node.removeAttribute(name);
            return;
          }
          if (name === "title" || name === "aria-description") {
            node.setAttribute(name, "[Accessible description.]");
            return;
          }
          if (name === "alt") {
            node.setAttribute(name, "[Accessible description.]");
            return;
          }
          if (name === "src" || name === "srcset") {
            node.removeAttribute(name);
            return;
          }
          if (name.startsWith("data-") && !allowedMatterData.has(name))
            node.removeAttribute(name);
        });
      });
      const allowedUnitData = new Set([
        "data-answered",
        "data-detail",
        "data-editor-preserve-dom",
        "data-editor-x",
        "data-editor-y",
        "data-evidence",
        "data-fill-token",
        "data-kind",
        "data-role",
        "data-section-target",
        "data-single",
        "data-text-token",
        "data-unit",
      ]);
      all("section.ld-unit[data-unit]", cloneRoot).forEach((section) => {
        Array.from(section.attributes).forEach((attribute) => {
          const name = attribute.name.toLowerCase();
          if (name === "data-role" && !UNIT_ROLES.includes(attribute.value)) {
            section.removeAttribute(name);
            return;
          }
          if (name === "id") {
            section.removeAttribute(name);
            return;
          }
          if (name === "title" || name === "aria-description") {
            section.setAttribute(name, "[Accessible description.]");
            return;
          }
          if (name.startsWith("data-") && !allowedUnitData.has(name))
            section.removeAttribute(name);
        });
      });
      all("section.ld-unit .ld-variant", cloneRoot).forEach((variant) => {
        Array.from(variant.attributes).forEach((attribute) => {
          const name = attribute.name.toLowerCase();
          if (name === "id") {
            variant.removeAttribute(name);
            return;
          }
          if (name === "title" || name === "aria-description") {
            variant.setAttribute(name, "[Accessible description.]");
            return;
          }
          if (name.startsWith("data-") && name !== "data-variant")
            variant.removeAttribute(name);
        });
      });
      all("main [title],#popup-scrim [title]", cloneRoot).forEach((node) => {
        node.setAttribute("title", "[Accessible description.]");
      });
      const main = cloneRoot.querySelector("main");
      if (main) {
        const walker = document.createTreeWalker(main, NodeFilter.SHOW_TEXT);
        const textNodes = [];
        while (walker.nextNode()) textNodes.push(walker.currentNode);
        textNodes.forEach((textNode) => {
          const value = textNode.textContent.trim();
          if (!value || validTemplatePlaceholder(value)) return;
          const parent = textNode.parentElement;
          if (
            !parent ||
            parent.closest(
              "script,style,section.ld-unit,#ld-form-navigation,#ld-page-approach,.ld-diagram",
            ) ||
            parent.closest("[data-placeholder]")
          )
            return;
          textNode.textContent = "[The value or fact this block communicates.]";
        });
      }
      // Reconstruct navigation chrome after generic matter-text scrubbing.
      // Do not preserve an edited label or count as a purported safe value.
      all(".ld-index-group", cloneRoot).forEach((group, index) => {
        group.open = false;
        const summary = group.querySelector(":scope > summary");
        if (summary) summary.textContent = `Topic group ${index + 1}`;
      });
      all(".ld-overview-groups", cloneRoot).forEach((container) => {
        all(":scope > .ld-overview-group", container).forEach(
          (group, index) => {
            group.open = index === 0;
            group.setAttribute("data-overview-group", String(index));
            const label = group.querySelector(".ld-overview-group-label");
            if (label) {
              label.textContent = `Topic group ${index + 1}`;
              label.setAttribute("data-overview-group-label", String(index));
            }
            const count = group.querySelector(".ld-overview-group-count");
            const total =
              nextState.overview?.groups?.[index]?.topicUnitIds.length || 0;
            if (count)
              count.textContent = `${total} ${total === 1 ? "topic" : "topics"}`;
          },
        );
      });
      // The issue anatomy teaches the same three jobs after matter removal.
      // Fixed semantic labels, not arbitrary author headings, survive export.
      all(".ld-issue-analysis", cloneRoot).forEach((column) => {
        const labels = [
          "[Statement or finding.]",
          "[Why it matters.]",
          "[Recommendation or next check.]",
        ];
        all(":scope > section.ld-unit", column).forEach((unit, index) => {
          const heading = unit.querySelector("h2,h3");
          if (!heading || !labels[index]) return;
          heading.textContent = labels[index];
          heading.setAttribute("data-placeholder", labels[index]);
        });
      });
      all(".ld-source-preview", cloneRoot).forEach((preview) => {
        const heading = preview.querySelector("h2,h3");
        const quote = preview.querySelector("blockquote");
        const caption = preview.querySelector("figcaption");
        for (const [node, label] of [
          [heading, "[Source passage.]"],
          [quote, "[Exact supporting excerpt, with its qualification.]"],
          [caption, "[Source, locator and review status.]"],
        ]) {
          if (!node) continue;
          node.textContent = label;
          node.setAttribute("data-placeholder", label);
        }
      });
      restoreTrustedTemplateStyles(cloneRoot);
      all("style", cloneRoot).forEach((style) => {
        let css = style.textContent;
        css = rewriteAttributeSelectorMap(css, "data-unit", unitMap);
        css = rewriteAttributeSelectorMap(
          css,
          "data-composition-section",
          sectionMap,
        );
        css = rewriteClassSelectorMap(css, classMap);
        style.textContent = css;
      });
      all("#popup-scrim .pop", cloneRoot).forEach((pop) => {
        const oldId = pop.id;
        const newId = mappedReference(oldId);
        const tag = pop.querySelector(".tag");
        const heading = pop.querySelector("h2");
        const desc = pop.querySelector(".desc");
        remapIdReferences(oldId, newId);
        remapIdReferences(oldId + "-title", newId + "-title");
        pop.id = newId;
        pop.setAttribute("data-evidence-id", newId);
        pop.setAttribute("aria-labelledby", newId + "-title");
        if (tag) tag.textContent = "[Citation or record label.]";
        if (heading) {
          heading.id = newId + "-title";
          heading.textContent =
            "[What this source establishes, in one sentence.]";
        }
        if (desc)
          desc.textContent =
            "[Two sentences that tell the reader what they are looking at.]";
        all(".doc-text", pop).forEach((node) => {
          node.textContent =
            "[Verbatim excerpt of the controlling clause, in the document's words.]";
        });
        all("blockquote", pop).forEach((node) => {
          node.textContent =
            "[Verbatim excerpt of the controlling clause, in the document's words.]";
        });
        all(".doc-cl", pop).forEach((node) => {
          node.textContent = "[Section and page.]";
        });
        all(".doc-foot,.ld-popup-source", pop).forEach((node) => {
          node.textContent = "[Source, version, date retrieved.]";
        });
        const evidencePlaceholders = {
          cite: "[Citation or record label.]",
          locator: "[Section and page.]",
          detail:
            "[Verbatim excerpt of the controlling clause, in the document's words.]",
          "popup.title": "[The question or insight this popup explains.]",
          "popup.lede": "[One-sentence orientation to the deeper detail.]",
        };
        all("[data-evidence-field]", pop).forEach((node) => {
          const field = node.getAttribute("data-evidence-field");
          const sectionField = /^popup\.sections\.\d+\.(heading|body)$/.exec(
            field,
          );
          const placeholder =
            evidencePlaceholders[field] ||
            (sectionField && sectionField[1] === "heading"
              ? "[Specific subpoint.]"
              : sectionField
                ? "[The deeper detail the reader needs here.]"
                : null);
          if (!placeholder) return;
          node.setAttribute("data-placeholder", placeholder);
          node.textContent = placeholder;
        });
        all("img", pop).forEach((image) => {
          const svg = document.createElementNS(
            "http://www.w3.org/2000/svg",
            "svg",
          );
          svg.setAttribute("viewBox", "0 0 800 420");
          svg.setAttribute("role", "img");
          svg.setAttribute(
            "aria-label",
            "[Clip of the source page with the cited passage highlighted.]",
          );
          svg.innerHTML =
            '<rect width="799" height="419" x=".5" y=".5" fill="var(--card)" stroke="var(--line-strong)"/><text x="400" y="210" text-anchor="middle" fill="currentColor" font-family="Helvetica,Arial,sans-serif" font-size="20">[Clip of the source page with the cited passage highlighted.]</text>';
          image.replaceWith(svg);
        });
        all("a", pop).forEach((link) => {
          link.setAttribute("href", "#");
          link.removeAttribute("target");
          link.removeAttribute("rel");
          link.textContent = "[Open the clause in the source document.]";
        });
        all(".out,.ld-source-unavailable", pop).forEach((source) => {
          if (!source.querySelector("a"))
            source.textContent =
              "[Original source unavailable in this template.]";
        });
      });
      const safeLabels = [
        "Close source popup",
        "Theme",
        "Edit",
        "Export HTML",
        "Export template",
        "Save",
        "Undo",
        "Redo",
        "Duplicate",
        "Delete",
        "Bold",
        "Italic",
        "Underline",
        "Increase type",
        "Decrease type",
        "Reset",
        "More",
        "Treatment",
        "Treatment A",
        "Treatment B",
        "Fill token",
        "Text token",
        "Custom position",
        "Note",
        "Previous slide",
        "Next slide",
        "Slide navigation",
        "Select slide",
        "Report sections",
        "Contents",
        "Collapse contents",
        "Expand contents",
        "Open contents",
        "Previous page",
        "Next page",
        "Previous section",
        "Next section",
      ];
      all("[aria-label]", cloneRoot).forEach((node) => {
        const value = node.getAttribute("aria-label");
        if (value && !safeLabels.includes(value))
          node.setAttribute("aria-label", "[Accessible description.]");
      });
      all("a[href]", cloneRoot).forEach((link) => {
        link.setAttribute("href", "#");
        link.removeAttribute("target");
        link.removeAttribute("rel");
        if (link.closest("#popup-scrim")) return;
        const internal =
          link.hasAttribute("data-page") ||
          link.hasAttribute("data-frame-button") ||
          link.hasAttribute("data-evidence") ||
          link.hasAttribute("data-detail");
        if (internal && link.children.length) return;
        if (
          link.hasAttribute("data-evidence") ||
          link.hasAttribute("data-detail")
        )
          link.textContent = "[Open the source.]";
        else if (internal) link.textContent = "[Open the relevant section.]";
        else link.textContent = "[Link.]";
      });
      all("[data-decision-custom],[data-decision-note]", cloneRoot).forEach(
        (input) => {
          input.value = "";
          input.setAttribute(
            "aria-label",
            input.hasAttribute("data-decision-custom")
              ? "Custom position"
              : "Note",
          );
        },
      );
      all("[data-report-section]", cloneRoot).forEach((button, index) => {
        button.textContent = "[Section " + (index + 1) + ".]";
      });
      all("#ld-page-approach [data-select-approach]", cloneRoot).forEach(
        (button) => {
          const key =
            button.getAttribute("data-select-approach") === "b" ? "B" : "A";
          button.innerHTML = "<span>" + key + "</span>Page " + key;
        },
      );
      all("[data-zone-doc]", cloneRoot).forEach((node) => {
        node.removeAttribute("data-zone-doc");
      });
      scrubTemplateMatterAttributes(cloneRoot);
    }
    function download(text, name) {
      const blob = new Blob([text], { type: "text/html;charset=utf-8" });
      const link = document.createElement("a");
      link.className = "ld-download-link";
      const url = URL.createObjectURL(blob);
      link.href = url;
      link.download = name;
      link.hidden = true;
      link.setAttribute("data-editor-chrome", "");
      document.body.appendChild(link);
      try {
        link.click();
      } catch (error) {
        link.remove();
        URL.revokeObjectURL(url);
        throw error;
      }
      // The browser consumes a Blob asynchronously. Do not revoke it in the
      // same task that requests the download, or assume the request reached disk.
      setTimeout(() => {
        link.remove();
        URL.revokeObjectURL(url);
      }, 60000);
    }
    let fileWriteInProgress = false;
    let clientPreviewURL = null;
    function clearSaveBanner() {
      const old = document.querySelector(".ld-save-banner");
      if (old) old.remove();
      if (clientPreviewURL) {
        URL.revokeObjectURL(clientPreviewURL);
        clientPreviewURL = null;
      }
    }
    async function persistHTML(
      html,
      name,
      reuseWorkingHandle = false,
      clientPreview = false,
    ) {
      if (fileWriteInProgress) {
        showSaveBanner(
          "A save is already in progress. Finish that save first.",
          8000,
        );
        return "busy";
      }
      fileWriteInProgress = true;
      clearSaveBanner();
      let writable;
      try {
        if (typeof window.showSaveFilePicker === "function") {
          try {
            // Invoke the picker before the first await, while the button's
            // user activation is still available. Exports never reuse the
            // working-copy handle or overwrite it without a fresh choice.
            const handle =
              reuseWorkingHandle && fileHandle
                ? fileHandle
                : await window.showSaveFilePicker({
                    suggestedName: name,
                    types: [
                      {
                        description: "HTML file",
                        accept: { "text/html": [".html"] },
                      },
                    ],
                  });
            writable = await handle.createWritable();
            await writable.write(html);
            await writable.close();
            writable = null;
            if (reuseWorkingHandle) fileHandle = handle;
            showSaveBanner(
              `Saved ${handle.name || name} to the location you chose.${clientPreview ? " Open a preview of this exact exported copy below." : ""}`,
              clientPreview ? 0 : 8000,
              clientPreview ? html : null,
            );
            return "saved";
          } catch (error) {
            if (writable && typeof writable.abort === "function") {
              try {
                await writable.abort();
              } catch {
                /* Preserve the original failure. */
              }
              writable = null;
            }
            if (error && error.name === "AbortError") {
              showSaveBanner(
                "Save canceled. Your open work is unchanged.",
                8000,
              );
              return "canceled";
            }
            if (reuseWorkingHandle) fileHandle = null;
          }
        }
        download(html, name);
        showSaveBanner(
          `Download requested: ${name}. Check your browser's download location; this page cannot confirm it reached disk. Your open work is unchanged.${clientPreview ? " The link opens a preview of this exact exported copy, not the saved disk file." : ""}`,
          clientPreview ? 0 : 16000,
          clientPreview ? html : null,
        );
        return "download-requested";
      } catch {
        showSaveBanner(
          "The file could not be saved. Your open work is unchanged. Try Save or Export again and choose a writable location.",
          16000,
        );
        return "failed";
      } finally {
        fileWriteInProgress = false;
      }
    }
    function stripAuthoringData(cloneRoot) {
      // data-variant-body is a presentation wrapper: shared copy spacing and
      // popup-text affordances depend on it even in a read-only client export.
      const names = [
        "data-card-title",
        "data-document-title-placeholder",
        "data-editable",
        "data-diagram-label",
        "data-evidence-field",
        "data-evidence-id",
        "data-editor-base-transform",
        "data-editor-scale-x",
        "data-editor-scale-y",
        "data-editor-node-key",
        "data-editor-origin-fill-token",
        "data-editor-origin-fill-token-present",
        "data-editor-origin-style",
        "data-editor-origin-style-present",
        "data-editor-origin-style-stamped",
        "data-editor-origin-text-token",
        "data-editor-origin-text-token-present",
        "data-editor-only",
        "data-editor-preserve-dom",
        "data-editor-unit",
        "data-editor-x",
        "data-editor-y",
        "data-export-chrome",
        "data-export-protect",
        "data-placeholder",
        "data-param-path",
        "data-single",
        "data-zone-doc",
      ];
      all("*", cloneRoot)
        .concat([cloneRoot])
        .forEach((node) => {
          names.forEach((name) => {
            node.removeAttribute(name);
          });
        });
    }
    function ensureClientResponseControl(scope) {
      if (
        scope.getAttribute("data-exported") !== "client" ||
        scope.querySelector("#ld-response-save")
      )
        return;
      const controls = scope.querySelector(".ld-controls");
      if (!controls) return;
      const button = document.createElement("button");
      button.className = "ld-control";
      button.id = "ld-response-save";
      button.type = "button";
      button.textContent = "Save responses";
      controls.appendChild(button);
    }
    LD.exportHTML = (shouldDownload) => {
      all('[contenteditable="true"]').forEach((node) => {
        node.blur();
      });
      // The previous transfer's notice is editor chrome, not part of the next
      // deliverable. Release its reserved space before checking page geometry.
      clearSaveBanner();
      fitFixedPages();
      if (!validateExportPageFit(false))
        throw new Error(
          "Page exceeds the available window. Move detail to a popup or another page before exporting.",
        );
      const cloneRoot = document.documentElement.cloneNode(true);
      scrubEditing(cloneRoot);
      cloneRoot.setAttribute("data-exported", "client");
      const clientBlueprintId =
        "legaldesign-client-" + stableLocationHash(state.artifactId);
      cloneRoot.setAttribute("data-client-blueprint-id", clientBlueprintId);
      all("#export-html,#export-template", cloneRoot).forEach((node) => {
        node.remove();
      });
      all("[data-placeholder],[data-placeholder-title]", cloneRoot).forEach(
        (node) => {
          node.removeAttribute("data-placeholder");
          node.removeAttribute("data-placeholder-title");
        },
      );
      if (state.approaches) {
        const selectedApproach = currentApproach();
        all("[data-approach]", cloneRoot).forEach((page) => {
          if (page.getAttribute("data-approach") !== selectedApproach)
            page.remove();
          else page.hidden = false;
        });
        const approachControl = cloneRoot.querySelector("#ld-page-approach");
        if (approachControl) approachControl.remove();
      }
      all("section.ld-unit[data-unit]", cloneRoot).forEach((section) => {
        const sourceUnit = unitState(section.getAttribute("data-unit"));
        if (!sourceUnit) {
          section.remove();
          return;
        }
        const selected = sourceUnit.selected;
        all(".ld-variant", section).forEach((variant) => {
          if (variant.getAttribute("data-variant") !== selected)
            variant.remove();
          else variant.hidden = false;
        });
        all(
          ".ld-ab,.ld-why,.ld-unit-tools,.ld-review,.ld-sheet-open",
          section,
        ).forEach((node) => {
          node.remove();
        });
      });
      all("[data-editor-only]", cloneRoot).forEach((node) => {
        node.remove();
      });
      const referenced = new Set(
        all("[data-evidence],[data-detail],[data-evidence-ids]", cloneRoot)
          .filter((node) => !node.closest("#popup-scrim"))
          .flatMap((node) => {
            const grouped = node.getAttribute("data-evidence-ids");
            if (grouped) return grouped.split(/\s+/).filter(Boolean);
            return [
              node.getAttribute("data-evidence") ||
                node.getAttribute("data-detail"),
            ].filter(Boolean);
          }),
      );
      // A purposeful detail popup can link onward to an original source popup.
      // Keep that whole reachable chain, not only the first surface hop.
      for (const id of referenced) {
        const pop = all("#popup-scrim .pop[id]", cloneRoot).find(
          (node) => node.id === id,
        );
        if (!pop) continue;
        all("[data-detail],[data-evidence],[data-evidence-ids]", pop).forEach(
          (node) => {
            const ids = node.getAttribute("data-evidence-ids");
            (ids
              ? ids.split(/\s+/)
              : [
                  node.getAttribute("data-detail") ||
                    node.getAttribute("data-evidence"),
                ]
            )
              .filter(Boolean)
              .forEach((target) => {
                referenced.add(target);
              });
          },
        );
      }
      all("#popup-scrim .pop[id]", cloneRoot).forEach((pop) => {
        if (!referenced.has(pop.id)) pop.remove();
      });
      const clientState = reducedClientState(cloneRoot, clientBlueprintId);
      stripAuthoringData(cloneRoot);
      ensureClientResponseControl(cloneRoot);
      setCloneState(cloneRoot, clientState);
      const html = serializeClone(cloneRoot);
      if (shouldDownload !== false)
        void persistHTML(html, state.artifactId + ".html", false, true);
      return html;
    };
    LD.exportTemplate = (shouldDownload, requestedId) => {
      all('[contenteditable="true"]').forEach((node) => {
        node.blur();
      });
      clearSaveBanner();
      fitFixedPages();
      if (!validateExportPageFit(true))
        throw new Error(
          "Page exceeds the available window. Move detail to a popup or another page before exporting.",
        );
      const templateId = templateExportId(requestedId);
      const cloneRoot = document.documentElement.cloneNode(true);
      cloneRoot.dataset.templateId = templateId;
      if (isPackagedTemplateId(requestedId)) {
        cloneRoot.dataset.templateKind = "packaged";
      } else {
        cloneRoot.dataset.templateKind = "exported";
      }
      cloneRoot.dataset.templateBlueprintId = templateId;
      scrubEditing(cloneRoot);
      cloneRoot.setAttribute("data-exported", "template");
      const maps = templateMappings();
      const nextState = templateState(maps, templateId);
      sanitizeTemplateDOM(cloneRoot, nextState, maps);
      setCloneState(cloneRoot, nextState);
      const html = serializeClone(cloneRoot);
      if (shouldDownload !== false)
        void persistHTML(html, templateId + ".html");
      return html;
    };

    async function saveFile() {
      all('[contenteditable="true"]').forEach((node) => {
        node.blur();
      });
      state.savedAt = new Date().toISOString();
      mirror();
      storeEditorDOM();
      const cloneRoot = document.documentElement.cloneNode(true);
      scrubTransientEditing(cloneRoot);
      setCloneState(cloneRoot, state);
      const html = serializeClone(cloneRoot);
      const saveName =
        root.getAttribute("data-exported") === "client"
          ? state.artifactId + "-responses.html"
          : state.artifactId + ".html";
      const result = await persistHTML(html, saveName, true);
      if (result === "saved") clearRestore();
      return result === "canceled" || result === "failed" || result === "busy"
        ? null
        : html;
    }
    function clearRestore() {
      const banner = document.getElementById("ld-restore-banner");
      if (banner) banner.remove();
    }
    function showSaveBanner(
      message,
      duration = 2400,
      clientPreviewHTML = null,
    ) {
      clearSaveBanner();
      const banner = document.createElement("div");
      banner.className = "ld-save-banner";
      banner.setAttribute("role", "status");
      banner.setAttribute("aria-live", "polite");
      banner.setAttribute("data-editor-chrome", "");
      banner.textContent = message;
      if (clientPreviewHTML !== null) {
        clientPreviewURL = URL.createObjectURL(
          new Blob([clientPreviewHTML], { type: "text/html;charset=utf-8" }),
        );
        const link = document.createElement("a");
        link.className = "ld-export-preview";
        link.href = clientPreviewURL;
        link.target = "_blank";
        link.rel = "noopener noreferrer";
        link.textContent = "Open exported HTML";
        banner.appendChild(link);
      }
      const bar = document.querySelector(".ld-bar");
      (bar ? bar.parentNode : document.body).insertBefore(
        banner,
        bar ? bar.nextSibling : document.body.firstChild,
      );
      if (duration > 0)
        setTimeout(() => {
          if (banner.isConnected) clearSaveBanner();
        }, duration);
    }

    function bindChrome() {
      const theme = document.getElementById("theme-toggle");
      if (theme)
        theme.addEventListener("click", () => {
          scheduleReady(true);
          applyTheme(
            root.getAttribute("data-theme") === "dark" ? "light" : "dark",
            true,
          );
        });
      const save = document.getElementById("ld-save");
      if (save) save.addEventListener("click", saveFile);
      const responseSave = document.getElementById("ld-response-save");
      if (responseSave) responseSave.addEventListener("click", saveFile);
      const html = document.getElementById("export-html");
      if (html)
        html.addEventListener("click", () => {
          LD.exportHTML(true);
        });
      const template = document.getElementById("export-template");
      if (template)
        template.addEventListener("click", () => {
          LD.exportTemplate(true);
        });
    }

    LD.state = () => clone(state);
    LD.setState = (next) => {
      if (!next || next.schema !== "legaldesign.state.v1")
        throw new TypeError("state must use legaldesign.state.v1");
      checkpoint();
      state = clone(next);
      mirror();
      renderAll({ sync: false });
      storeEditorDOM();
    };
    LD.save = saveFile;
    LD.setMode = setMode;
    LD.setApproach = setApproach;
    LD.openPopup = openPopup;
    LD.closePopup = closePopup;
    LD.bindDecisions = bindDecisions;
    LD.placeholderDerivation = clone(PLACEHOLDERS);

    scrubTransientEditing(root);
    normalizeChrome();
    applyTheme(initialTheme(), false);
    removeLegacyReviewChrome();
    ensureClientResponseControl(root);
    installFixedPages();
    renderAll();
    bindApproaches();
    bindUnits();
    bindDecisions();
    bindPopups();
    installComposedForm();
    bindEditor();
    bindChrome();
    installRestoreBanner();
    updateEditorButtons();
    scheduleReady(false);
    // Phones land in the existing readable view, not an illegible miniature.
    // Run once at load; deliberately closing it keeps the canvas available.
    if (
      singleComposition() &&
      window.matchMedia("(max-width: 760px)").matches
    ) {
      requestAnimationFrame(() => {
        if (!editing && !activePopup())
          openPageReader(document.getElementById("ld-read-page"));
      });
    }
  }

  if (document.readyState === "loading")
    document.addEventListener("DOMContentLoaded", boot, { once: true });
  else boot();
})();
</script>
<!-- /legaldesign:runtime -->
<style id="stacked-layout">
.ld-page { width: min(1380px, 100%); margin: 0 auto; padding: clamp(18px, 3vw, 42px); }
.ld-kicker, .ld-foot { display: flex; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
.ld-kicker { margin-bottom: 14px; }
.ld-kicker p { margin: 0; color: var(--faint); font-size: 11px; line-height: 1.35; }
.ld-kicker p:first-child { color: var(--red); letter-spacing: .06em; }
.ld-title h1 { max-width: 1050px; margin: 0; font-size: clamp(30px, 4.1vw, 54px); line-height: 1.03; letter-spacing: -.022em; }
.ld-answer { max-width: 1050px; margin-top: 14px; }
.ld-answer p { margin: 0; color: var(--muted); font-size: 15px; line-height: 1.55; }
.ld-answer b, .ld-next b { color: var(--ink); }
.ld-core { display: grid; grid-template-columns: minmax(210px, .86fr) minmax(390px, 1.5fr) minmax(210px, .86fr); grid-template-areas: "cover figure never" "adopt figure why"; gap: 14px; align-items: stretch; margin-top: 26px; }
.ld-figure { grid-area: figure; min-width: 0; border: 1px solid var(--line-strong); border-radius: 5px; background: var(--card); padding: 18px; }
.ld-figure h2 { margin: 0 105px 12px 0; font-size: 17px; line-height: 1.25; }
.ld-diagram svg { width: 100%; max-height: 440px; }
.ld-source-note { margin: 8px 0 0; color: var(--faint); font-size: 11px; line-height: 1.4; }
.ld-card { min-width: 0; border: 1px solid var(--line-strong); border-radius: 4px; background: var(--card-strong); padding: 14px; }
html[data-mode="edit"] .ld-card, html[data-mode="edit"] .ld-figure { padding-top: 46px; }
.ld-card:hover { border-color: var(--red); }
[data-unit="u-card-questions"] { grid-area: cover; } [data-unit="u-card-form"] { grid-area: adopt; } [data-unit="u-card-ab"] { grid-area: never; } [data-unit="u-card-exports"] { grid-area: why; }
.ld-card h3 { margin: 0; font-size: 15px; line-height: 1.25; }
.ld-card-copy { color: var(--muted); font-size: 12px; line-height: 1.48; }
.ld-card-copy p { margin: 8px 0 0; }
.ld-card-copy b { color: var(--ink); }
.ld-steps { margin: 8px 0 0; padding-left: 20px; }
.ld-steps li + li { margin-top: 5px; }
.ld-next { margin-top: 16px; border-top: 1px solid var(--line-strong); border-bottom: 1px solid var(--line); padding: 14px 105px 14px 0; }
.ld-next p { margin: 0; font-size: 14px; line-height: 1.5; }
.ld-scope { margin-top: 13px; padding-right: 54px; }
.ld-scope p { margin: 0; color: var(--faint); font-size: 11px; line-height: 1.5; }
.ld-foot { margin-top: 24px; border-top: 1px solid var(--line); padding-top: 12px; }
.ld-foot p { margin: 0; color: var(--muted); font-size: 11px; }
.cite { font-size: 12px; }
@media (max-width: 900px) { .ld-core { grid-template-columns: 1fr 1fr; grid-template-areas: "figure figure" "cover never" "adopt why"; } }
@media (max-width: 480px) {
  .ld-page { width: 100%; padding: 16px 12px calc(20px + env(safe-area-inset-bottom)); overflow-x: clip; }
  .ld-title h1 { font-size: 31px; }
  .ld-answer p { font-size: 14px; }
  .ld-core { display: flex; flex-direction: column; gap: 12px; margin-top: 18px; }
  .ld-figure, .ld-card { width: 100%; }
  .ld-figure { padding: 12px 10px; }
  .ld-figure h2 { margin-right: 44px; }
  .ld-card { padding: 12px; }
  html[data-mode="edit"] .ld-card, html[data-mode="edit"] .ld-figure { padding-top: 58px; }
  .ld-next { padding: 14px 44px 12px 0; }
  html[data-mode="edit"] .ld-next { padding-top: 58px; }
  .ld-scope { padding: 0 44px 0 0; }
  html[data-mode="edit"] .ld-scope { padding-top: 58px; }
  .ld-diagram svg { max-height: none; }
  .ld-foot { padding-bottom: 8px; }
}
</style>
</head>
<body name="stacked-explainer-build">
<header class="ld-bar" data-export-chrome="">
  <p class="ld-matter" data-placeholder="[Matter · the question this page answers.]">LegalDesign · how the skill works</p>
  <div class="ld-controls">
    <button class="ld-control" id="mode-toggle" type="button" aria-pressed="false" data-editor-only="">Edit</button>
    <button class="ld-control" id="theme-toggle" type="button" aria-label="Theme">Theme</button>
    <button class="ld-control" id="export-html" type="button" data-editor-only>Export HTML</button>
    <button class="ld-control" id="export-template" type="button" data-editor-only>Export template</button>
  </div>
  <div class="ld-editor-tools" data-editor-only="">
    <button class="ld-tool" id="ld-save" type="button">Save</button>
    <button class="ld-tool" id="editor-undo" type="button">Undo</button><button class="ld-tool" id="editor-redo" type="button" disabled="">Redo</button><button class="ld-tool" id="editor-duplicate" type="button" disabled="">Duplicate</button><button class="ld-tool" id="editor-delete" type="button" disabled="">Delete</button>
    <button class="ld-tool" id="editor-bold" type="button" disabled="">Bold</button><button class="ld-tool" id="editor-italic" type="button" disabled="">Italic</button><button class="ld-tool" id="editor-underline" type="button" disabled="">Underline</button>
    <span class="ld-overflow-wrap"><button class="ld-tool" id="editor-overflow-toggle" type="button" aria-expanded="false" aria-controls="editor-overflow-menu">»</button><span class="ld-overflow-menu" id="editor-overflow-menu"><button class="ld-tool" id="editor-font-up" type="button" disabled="">Increase type</button><button class="ld-tool" id="editor-font-down" type="button" disabled="">Decrease type</button><button class="ld-tool" id="editor-reset" type="button" disabled="">Reset</button><select class="ld-palette" id="editor-fill-token" aria-label="Fill token" disabled=""><option value="">Fill</option><option value="ink">ink</option><option value="muted">muted</option><option value="faint">faint</option><option value="red">red</option><option value="red-strong">red-strong</option><option value="line">line</option><option value="card">card</option><option value="card-strong">card-strong</option><option value="tint">tint</option><option value="bg">bg</option><option value="none">none</option></select><select class="ld-palette" id="editor-text-token" aria-label="Text token" disabled=""><option value="">Text</option><option value="ink">ink</option><option value="muted">muted</option><option value="faint">faint</option><option value="red">red</option><option value="red-strong">red-strong</option><option value="line">line</option><option value="card">card</option><option value="card-strong">card-strong</option><option value="tint">tint</option><option value="bg">bg</option><option value="none">none</option></select></span></span>
  </div>
</header>
<main class="ld-page">

  <section class="ld-unit ld-title" data-unit="u-title" data-kind="text"><div class="ld-unit-tools" data-editor-only=""><div class="ld-ab" role="group" tabindex="0" aria-label="Treatment"><button type="button" data-select-variant="a" aria-label="Treatment A" aria-pressed="true">A</button><button type="button" data-select-variant="b" aria-label="Treatment B" aria-pressed="false">B</button></div><button class="ld-why-toggle" type="button" aria-expanded="false">Why</button></div><div class="ld-variant" data-variant="a"><div data-variant-body=""><h1 data-editable="">How LegalDesign works</h1></div></div><div class="ld-variant" data-variant="b" hidden=""><div data-variant-body=""><h1 data-editable="">Designing a legal explanation</h1></div></div><aside class="ld-why" hidden="">A concise explanation of the current rule; this legacy component variant is not a separate whole-page architecture.</aside></section>
  <section class="ld-unit ld-answer" data-unit="u-answer" data-kind="text"><div class="ld-unit-tools" data-editor-only=""><div class="ld-ab" role="group" tabindex="0" aria-label="Treatment"><button type="button" data-select-variant="a" aria-label="Treatment A" aria-pressed="true">A</button><button type="button" data-select-variant="b" aria-label="Treatment B" aria-pressed="false">B</button></div><button class="ld-why-toggle" type="button" aria-expanded="false">Why</button></div><div class="ld-variant" data-variant="a"><div data-variant-body=""><p data-editable="">The skill uses the audience and source material to choose a form and encode its meaning. It builds two complete approaches with the same design components, then checks the result.</p></div></div><div class="ld-variant" data-variant="b" hidden=""><div data-variant-body=""><p data-editable="">Compare two complete approaches, edit the one you prefer, and export it for the reader or as a reusable template. The source material controls the meaning.</p></div></div><aside class="ld-why" hidden="">A concise explanation of the current rule; this legacy component variant is not a separate whole-page architecture.</aside></section>
  <div class="ld-core">
    <section class="ld-unit ld-figure" data-unit="u-figure" data-kind="figure"><div class="ld-unit-tools" data-editor-only=""><div class="ld-ab" role="group" tabindex="0" aria-label="Treatment"><button type="button" data-select-variant="a" aria-label="Treatment A" aria-pressed="true">A</button><button type="button" data-select-variant="b" aria-label="Treatment B" aria-pressed="false">B</button></div><button class="ld-why-toggle" type="button" aria-expanded="false">Why</button></div><h2 data-editable="" data-placeholder="[What the figure shows, as a claim.]">Three design decisions</h2><div class="ld-variant" data-variant="a"><div class="ld-diagram" id="diagram-host"><svg xmlns="http://www.w3.org/2000/svg" class="library-viz diagram-viz narrow-viz" viewBox="0 0 400 384" width="100%" role="group" aria-label="Three design decisions">
    <style>
      .diagram-viz{display:block;color:var(--ink);overflow:visible}.diagram-viz text{font-family:var(--sans);font-size:14px;font-weight:600;fill:var(--ink);stroke:none}.diagram-viz .sub{font-size:12.25px;font-weight:400;fill:var(--muted)}.diagram-viz .zone,.diagram-viz .edge{font-family:var(--sans);font-size:12.25px;font-weight:650;letter-spacing:0;text-transform:none}.diagram-viz .edge{fill:var(--muted)}.diagram-viz .line,.diagram-viz .hair{fill:none;stroke:var(--line-strong);stroke-width:1;vector-effect:non-scaling-stroke}.diagram-viz .box{fill:var(--card);stroke:transparent;stroke-width:1.5;filter:drop-shadow(0 1px 2px rgba(0,0,0,.06)) drop-shadow(0 7px 16px rgba(0,0,0,.07));vector-effect:non-scaling-stroke}.diagram-viz .box.tint{fill:var(--tint);stroke:var(--line-strong)}.diagram-viz .box.accent{fill:var(--red-wash);stroke:var(--red)}.diagram-viz .tint{fill:var(--tint)}.diagram-viz .inkfill{fill:var(--ink);fill-opacity:.1;stroke:var(--ink);stroke-width:1;vector-effect:non-scaling-stroke}.diagram-viz .band{fill:var(--tint);stroke:var(--line-strong);stroke-width:1;vector-effect:non-scaling-stroke}.diagram-viz .edge-band{fill:var(--tint);stroke:none}.diagram-viz .open{stroke:var(--red);fill:var(--red-wash)}.diagram-viz .leaf{fill:var(--tint);stroke:transparent;stroke-width:1;vector-effect:non-scaling-stroke}.diagram-viz .ld-detail{cursor:pointer;outline:none}.diagram-viz .ld-detail text{user-select:none}
      .chart-viz text{font-size:11.75px}.chart-viz .chart-title{font-size:12.5px;font-weight:650;fill:var(--ink)}.chart-viz .axis-title{font-size:11.75px;font-weight:600;fill:var(--ink)}.chart-viz .value{font-size:12px;font-weight:500;fill:var(--ink)}.chart-viz .muted,.chart-viz .source{fill:var(--muted)}.chart-viz .source{font-size:11.75px}.chart-viz .quiet{fill:none;stroke:var(--muted);stroke-width:1;vector-effect:non-scaling-stroke}.chart-viz .faint{fill:none;stroke:currentColor;stroke-opacity:.18;stroke-width:1;vector-effect:non-scaling-stroke}.chart-viz .fill,.chart-viz .tone-1,.chart-viz .tone-2,.chart-viz .tone-3,.chart-viz .tone-4{stroke:currentColor;stroke-width:1;vector-effect:non-scaling-stroke}.chart-viz .label-pad{stroke:none}.chart-viz .fill,.chart-viz .tone-1{fill:color-mix(in srgb,currentColor 14%,var(--card))}.chart-viz .tone-2{fill:color-mix(in srgb,currentColor 28%,var(--card))}.chart-viz .tone-3{fill:color-mix(in srgb,currentColor 48%,var(--card))}.chart-viz .tone-4{fill:color-mix(in srgb,currentColor 72%,var(--card))}.chart-viz .open{stroke:var(--red);fill:var(--red-wash)}.chart-viz .open-line{stroke:var(--red)}
      .diagram-viz .ld-detail:hover&gt;.box:first-child,.diagram-viz .ld-detail:hover&gt;.leaf:first-child,html:not([data-input-modality="pointer"]) .diagram-viz .ld-detail:focus-visible&gt;.box:first-child,html:not([data-input-modality="pointer"]) .diagram-viz .ld-detail:focus-visible&gt;.leaf:first-child{stroke:var(--interaction)}
      :is(html[data-theme="dark"] .diagram-viz:not([data-theme="light"]),.diagram-viz[data-theme="dark"]){--ld-node-fill:#000;--ld-node-ink:#fff;--ld-node-muted:#fff;--ld-node-accent-ink:#fff;--ld-node-outline:#999}
      :is(html[data-theme="dark"] .diagram-viz:not([data-theme="light"]),.diagram-viz[data-theme="dark"]):not(.chart-viz) :is(.box,.leaf,.band,.tint,.inkfill){fill:var(--ld-node-fill);fill-opacity:1;stroke:var(--ld-node-outline)}
      :is(html[data-theme="dark"] .diagram-viz:not([data-theme="light"]),.diagram-viz[data-theme="dark"]):not(.chart-viz) :is(.box,.inkfill)+circle.line{stroke:var(--ld-node-muted)}
      :is(html[data-theme="dark"] .diagram-viz:not([data-theme="light"]),.diagram-viz[data-theme="dark"]):not(.chart-viz) :is(.box,.band,.leaf).accent,:is(html[data-theme="dark"] .diagram-viz:not([data-theme="light"]),.diagram-viz[data-theme="dark"]):not(.chart-viz) .open{fill:var(--ld-node-fill);fill-opacity:1;stroke:var(--red)}
      :is(html[data-theme="dark"] .diagram-viz:not([data-theme="light"]),.diagram-viz[data-theme="dark"]):not(.chart-viz) text{fill:var(--ld-node-ink)}
      :is(html[data-theme="dark"] .diagram-viz:not([data-theme="light"]),.diagram-viz[data-theme="dark"]):not(.chart-viz) :is(.line,.hair){stroke:#fff}
      :is(html[data-theme="dark"] .diagram-viz:not([data-theme="light"]),.diagram-viz[data-theme="dark"]):not(.chart-viz) [data-diagram-object]:has(&gt; :is(.box,.leaf,.band,.tint,.inkfill))&gt;text,:is(html[data-theme="dark"] .diagram-viz:not([data-theme="light"]),.diagram-viz[data-theme="dark"]):not(.chart-viz) .surface-text{fill:var(--ld-node-ink)}
      :is(html[data-theme="dark"] .diagram-viz:not([data-theme="light"]),.diagram-viz[data-theme="dark"]):not(.chart-viz) [data-diagram-object]:has(&gt; :is(.box,.leaf,.band,.tint))&gt;text.sub{fill:var(--ld-node-muted)}
      :is(html[data-theme="dark"] .diagram-viz:not([data-theme="light"]),.diagram-viz[data-theme="dark"]):not(.chart-viz) [data-diagram-object]:has(&gt; :is(.accent,.open))&gt;text:is(.sub,:not(.sub)){fill:var(--ld-node-accent-ink)}
      html[data-theme="dark"] .chart-viz .fill.open,html[data-theme="dark"] .chart-viz .tone-1.open,.chart-viz[data-theme="dark"] .fill.open,.chart-viz[data-theme="dark"] .tone-1.open{fill:color-mix(in srgb,currentColor 14%,var(--card))}
      html[data-theme="dark"] .chart-viz .tone-2.open,.chart-viz[data-theme="dark"] .tone-2.open{fill:color-mix(in srgb,currentColor 28%,var(--card))}
      html[data-theme="dark"] .chart-viz .tone-3.open,.chart-viz[data-theme="dark"] .tone-3.open{fill:color-mix(in srgb,currentColor 48%,var(--card))}
      html[data-theme="dark"] .chart-viz .tone-4.open,.chart-viz[data-theme="dark"] .tone-4.open{fill:color-mix(in srgb,currentColor 72%,var(--card))}
    </style><g data-diagram-object="" class="ld-detail" role="button" tabindex="0" aria-haspopup="dialog" aria-controls="src-relationship" data-detail="src-relationship"><rect class="box" x="40" y="28" width="320" height="68" rx="7"></rect><text class="" x="200" y="54" text-anchor="middle"><tspan x="200" dy="0">Audience + relationship</tspan></text><text class="sub" x="200" y="81" text-anchor="middle"><tspan x="200" dy="0">Reader, purpose and facts</tspan></text></g><path class="line" d="M200 102V145"></path><path class="line" d="M196 145L200 152L204 145"></path><text class="edge" x="218" y="123" text-anchor="start"><tspan x="218" dy="0">guides the</tspan><tspan x="218" dy="14">choice</tspan></text><g data-diagram-object="" class="ld-detail" role="button" tabindex="0" aria-haspopup="dialog" aria-controls="src-form" data-detail="src-form"><rect class="box" x="40" y="158" width="320" height="68" rx="7"></rect><text class="" x="200" y="184" text-anchor="middle"><tspan x="200" dy="0">Representation</tspan></text><text class="sub" x="200" y="211" text-anchor="middle"><tspan x="200" dy="0">Diagram, table or text</tspan></text></g><path class="line" d="M200 232V275"></path><path class="line" d="M196 275L200 282L204 275"></path><text class="edge" x="218" y="253" text-anchor="start"><tspan x="218" dy="0">defines the</tspan><tspan x="218" dy="14">mapping</tspan></text><g data-diagram-object="" class="ld-detail" role="button" tabindex="0" aria-haspopup="dialog" aria-controls="src-brief" data-detail="src-brief"><rect class="box" x="40" y="288" width="320" height="68" rx="7"></rect><text class="" x="200" y="314" text-anchor="middle"><tspan x="200" dy="0">Visual encoding</tspan></text><text class="sub" x="200" y="341" text-anchor="middle"><tspan x="200" dy="0">Position, order and scale</tspan></text></g></svg></div></div><div class="ld-variant" data-variant="b" hidden=""><div class="ld-diagram" id="diagram-host-b"><svg xmlns="http://www.w3.org/2000/svg" class="library-viz diagram-viz narrow-viz" viewBox="0 0 400 384" width="100%" role="group" aria-label="Three design decisions">
    <style>
      .diagram-viz{display:block;color:var(--ink);overflow:visible}.diagram-viz text{font-family:var(--sans);font-size:14px;font-weight:600;fill:var(--ink);stroke:none}.diagram-viz .sub{font-size:12.25px;font-weight:400;fill:var(--muted)}.diagram-viz .zone,.diagram-viz .edge{font-family:var(--sans);font-size:12.25px;font-weight:650;letter-spacing:0;text-transform:none}.diagram-viz .edge{fill:var(--muted)}.diagram-viz .line,.diagram-viz .hair{fill:none;stroke:var(--line-strong);stroke-width:1;vector-effect:non-scaling-stroke}.diagram-viz .box{fill:var(--card);stroke:transparent;stroke-width:1.5;filter:drop-shadow(0 1px 2px rgba(0,0,0,.06)) drop-shadow(0 7px 16px rgba(0,0,0,.07));vector-effect:non-scaling-stroke}.diagram-viz .box.tint{fill:var(--tint);stroke:var(--line-strong)}.diagram-viz .box.accent{fill:var(--red-wash);stroke:var(--red)}.diagram-viz .tint{fill:var(--tint)}.diagram-viz .inkfill{fill:var(--ink);fill-opacity:.1;stroke:var(--ink);stroke-width:1;vector-effect:non-scaling-stroke}.diagram-viz .band{fill:var(--tint);stroke:var(--line-strong);stroke-width:1;vector-effect:non-scaling-stroke}.diagram-viz .edge-band{fill:var(--tint);stroke:none}.diagram-viz .open{stroke:var(--red);fill:var(--red-wash)}.diagram-viz .leaf{fill:var(--tint);stroke:transparent;stroke-width:1;vector-effect:non-scaling-stroke}.diagram-viz .ld-detail{cursor:pointer;outline:none}.diagram-viz .ld-detail text{user-select:none}
      .chart-viz text{font-size:11.75px}.chart-viz .chart-title{font-size:12.5px;font-weight:650;fill:var(--ink)}.chart-viz .axis-title{font-size:11.75px;font-weight:600;fill:var(--ink)}.chart-viz .value{font-size:12px;font-weight:500;fill:var(--ink)}.chart-viz .muted,.chart-viz .source{fill:var(--muted)}.chart-viz .source{font-size:11.75px}.chart-viz .quiet{fill:none;stroke:var(--muted);stroke-width:1;vector-effect:non-scaling-stroke}.chart-viz .faint{fill:none;stroke:currentColor;stroke-opacity:.18;stroke-width:1;vector-effect:non-scaling-stroke}.chart-viz .fill,.chart-viz .tone-1,.chart-viz .tone-2,.chart-viz .tone-3,.chart-viz .tone-4{stroke:currentColor;stroke-width:1;vector-effect:non-scaling-stroke}.chart-viz .label-pad{stroke:none}.chart-viz .fill,.chart-viz .tone-1{fill:color-mix(in srgb,currentColor 14%,var(--card))}.chart-viz .tone-2{fill:color-mix(in srgb,currentColor 28%,var(--card))}.chart-viz .tone-3{fill:color-mix(in srgb,currentColor 48%,var(--card))}.chart-viz .tone-4{fill:color-mix(in srgb,currentColor 72%,var(--card))}.chart-viz .open{stroke:var(--red);fill:var(--red-wash)}.chart-viz .open-line{stroke:var(--red)}
      .diagram-viz .ld-detail:hover&gt;.box:first-child,.diagram-viz .ld-detail:hover&gt;.leaf:first-child,html:not([data-input-modality="pointer"]) .diagram-viz .ld-detail:focus-visible&gt;.box:first-child,html:not([data-input-modality="pointer"]) .diagram-viz .ld-detail:focus-visible&gt;.leaf:first-child{stroke:var(--interaction)}
      :is(html[data-theme="dark"] .diagram-viz:not([data-theme="light"]),.diagram-viz[data-theme="dark"]){--ld-node-fill:#000;--ld-node-ink:#fff;--ld-node-muted:#fff;--ld-node-accent-ink:#fff;--ld-node-outline:#999}
      :is(html[data-theme="dark"] .diagram-viz:not([data-theme="light"]),.diagram-viz[data-theme="dark"]):not(.chart-viz) :is(.box,.leaf,.band,.tint,.inkfill){fill:var(--ld-node-fill);fill-opacity:1;stroke:var(--ld-node-outline)}
      :is(html[data-theme="dark"] .diagram-viz:not([data-theme="light"]),.diagram-viz[data-theme="dark"]):not(.chart-viz) :is(.box,.inkfill)+circle.line{stroke:var(--ld-node-muted)}
      :is(html[data-theme="dark"] .diagram-viz:not([data-theme="light"]),.diagram-viz[data-theme="dark"]):not(.chart-viz) :is(.box,.band,.leaf).accent,:is(html[data-theme="dark"] .diagram-viz:not([data-theme="light"]),.diagram-viz[data-theme="dark"]):not(.chart-viz) .open{fill:var(--ld-node-fill);fill-opacity:1;stroke:var(--red)}
      :is(html[data-theme="dark"] .diagram-viz:not([data-theme="light"]),.diagram-viz[data-theme="dark"]):not(.chart-viz) text{fill:var(--ld-node-ink)}
      :is(html[data-theme="dark"] .diagram-viz:not([data-theme="light"]),.diagram-viz[data-theme="dark"]):not(.chart-viz) :is(.line,.hair){stroke:#fff}
      :is(html[data-theme="dark"] .diagram-viz:not([data-theme="light"]),.diagram-viz[data-theme="dark"]):not(.chart-viz) [data-diagram-object]:has(&gt; :is(.box,.leaf,.band,.tint,.inkfill))&gt;text,:is(html[data-theme="dark"] .diagram-viz:not([data-theme="light"]),.diagram-viz[data-theme="dark"]):not(.chart-viz) .surface-text{fill:var(--ld-node-ink)}
      :is(html[data-theme="dark"] .diagram-viz:not([data-theme="light"]),.diagram-viz[data-theme="dark"]):not(.chart-viz) [data-diagram-object]:has(&gt; :is(.box,.leaf,.band,.tint))&gt;text.sub{fill:var(--ld-node-muted)}
      :is(html[data-theme="dark"] .diagram-viz:not([data-theme="light"]),.diagram-viz[data-theme="dark"]):not(.chart-viz) [data-diagram-object]:has(&gt; :is(.accent,.open))&gt;text:is(.sub,:not(.sub)){fill:var(--ld-node-accent-ink)}
      html[data-theme="dark"] .chart-viz .fill.open,html[data-theme="dark"] .chart-viz .tone-1.open,.chart-viz[data-theme="dark"] .fill.open,.chart-viz[data-theme="dark"] .tone-1.open{fill:color-mix(in srgb,currentColor 14%,var(--card))}
      html[data-theme="dark"] .chart-viz .tone-2.open,.chart-viz[data-theme="dark"] .tone-2.open{fill:color-mix(in srgb,currentColor 28%,var(--card))}
      html[data-theme="dark"] .chart-viz .tone-3.open,.chart-viz[data-theme="dark"] .tone-3.open{fill:color-mix(in srgb,currentColor 48%,var(--card))}
      html[data-theme="dark"] .chart-viz .tone-4.open,.chart-viz[data-theme="dark"] .tone-4.open{fill:color-mix(in srgb,currentColor 72%,var(--card))}
    </style><g data-diagram-object="" class="ld-detail" role="button" tabindex="0" aria-haspopup="dialog" aria-controls="src-relationship" data-detail="src-relationship"><rect class="box" x="40" y="28" width="320" height="68" rx="7"></rect><text class="" x="200" y="54" text-anchor="middle"><tspan x="200" dy="0">Understand the task</tspan></text><text class="sub" x="200" y="81" text-anchor="middle"><tspan x="200" dy="0">What must this reader know?</tspan></text></g><path class="line" d="M200 102V145"></path><path class="line" d="M196 145L200 152L204 145"></path><text class="edge" x="218" y="123" text-anchor="start"><tspan x="218" dy="0">choose for</tspan><tspan x="218" dy="14">that task</tspan></text><g data-diagram-object="" class="ld-detail" role="button" tabindex="0" aria-haspopup="dialog" aria-controls="src-form" data-detail="src-form"><rect class="box" x="40" y="158" width="320" height="68" rx="7"></rect><text class="" x="200" y="184" text-anchor="middle"><tspan x="200" dy="0">Choose the form</tspan></text><text class="sub" x="200" y="211" text-anchor="middle"><tspan x="200" dy="0">Which form makes it clear?</tspan></text></g><path class="line" d="M200 232V275"></path><path class="line" d="M196 275L200 282L204 275"></path><text class="edge" x="218" y="253" text-anchor="start"><tspan x="218" dy="0">encode its</tspan><tspan x="218" dy="14">meaning</tspan></text><g data-diagram-object="" class="ld-detail" role="button" tabindex="0" aria-haspopup="dialog" aria-controls="src-brief" data-detail="src-brief"><rect class="box" x="40" y="288" width="320" height="68" rx="7"></rect><text class="" x="200" y="314" text-anchor="middle"><tspan x="200" dy="0">Show the relationship</tspan></text><text class="sub" x="200" y="341" text-anchor="middle"><tspan x="200" dy="0">What does the geometry mean?</tspan></text></g></svg></div></div><p class="ld-source-note" data-editable="" data-placeholder="[Source of the figure's facts.]">Design method: audience, form and encoding.</p><aside class="ld-why" hidden="">The three connected decisions show how meaning determines representation and then geometry.</aside></section>
    <section class="ld-unit ld-card" data-unit="u-card-questions" data-kind="card" data-evidence="src-questions"><div class="ld-unit-tools" data-editor-only=""><div class="ld-ab" role="group" tabindex="0" aria-label="Treatment"><button type="button" data-select-variant="a" aria-label="Treatment A" aria-pressed="true">A</button><button type="button" data-select-variant="b" aria-label="Treatment B" aria-pressed="false">B</button></div><button class="ld-why-toggle" type="button" aria-expanded="false">Why</button></div><div class="ld-variant" data-variant="a"><div data-variant-body=""><h3 data-card-title="" data-editable="" data-placeholder-title="[Name the topic.]">Intake</h3><div class="ld-card-copy" data-editable=""><p>Up to three unanswered questions clarify the reader, purpose, sources or first-use colors. It uses structured choices when available, otherwise concise chat.</p></div></div></div><div class="ld-variant" data-variant="b" hidden=""><div data-variant-body=""><h3 data-card-title="" data-editable="" data-placeholder-title="[Name the topic.]">Reader and purpose</h3><div class="ld-card-copy" data-editable=""><p>The record answers what it can first. You can answer, skip, or ask the agent to proceed with stated assumptions.</p></div></div></div><aside class="ld-why" hidden="">A concise explanation of the current rule; this legacy component variant is not a separate whole-page architecture.</aside></section>
    <section class="ld-unit ld-card" data-unit="u-card-form" data-kind="card" data-evidence="src-form"><div class="ld-unit-tools" data-editor-only=""><div class="ld-ab" role="group" tabindex="0" aria-label="Treatment"><button type="button" data-select-variant="a" aria-label="Treatment A" aria-pressed="true">A</button><button type="button" data-select-variant="b" aria-label="Treatment B" aria-pressed="false">B</button></div><button class="ld-why-toggle" type="button" aria-expanded="false">Why</button></div><div class="ld-variant" data-variant="a"><div data-variant-body=""><h3 data-card-title="" data-editable="" data-placeholder-title="[Name the topic.]">Design decisions</h3><div class="ld-card-copy" data-editable=""><p>Compare plausible forms, including text or a table. Choose the one that expresses the relationship, then specify what position, order, scale or enclosure means.</p></div></div></div><div class="ld-variant" data-variant="b" hidden=""><div data-variant-body=""><h3 data-card-title="" data-editable="" data-placeholder-title="[Name the topic.]">Component selection</h3><div class="ld-card-copy" data-editable=""><p>Reuse the existing typography, cards, diagrams and popups. Templates show those components; they do not dictate the page layout.</p></div></div></div><aside class="ld-why" hidden="">A concise explanation of the current rule; this legacy component variant is not a separate whole-page architecture.</aside></section>
    <section class="ld-unit ld-card" data-unit="u-card-ab" data-kind="card" data-evidence="src-treatments"><div class="ld-unit-tools" data-editor-only=""><div class="ld-ab" role="group" tabindex="0" aria-label="Treatment"><button type="button" data-select-variant="a" aria-label="Treatment A" aria-pressed="true">A</button><button type="button" data-select-variant="b" aria-label="Treatment B" aria-pressed="false">B</button></div><button class="ld-why-toggle" type="button" aria-expanded="false">Why</button></div><div class="ld-variant" data-variant="a"><div data-variant-body=""><h3 data-card-title="" data-editable="" data-placeholder-title="[Name the topic.]">Two complete approaches</h3><div class="ld-card-copy" data-editable=""><p>A and B organize the same material differently. One global selector switches the complete approach; individual cards and figures have no A/B controls.</p></div></div></div><div class="ld-variant" data-variant="b" hidden=""><div data-variant-body=""><h3 data-card-title="" data-editable="" data-placeholder-title="[Name the topic.]">Compare page structures</h3><div class="ld-card-copy" data-editable=""><p>Compare different information structures or reading paths. Changing color or rewording the same cards does not create a second approach.</p></div></div></div><aside class="ld-why" hidden="">A concise explanation of the current rule; this legacy component variant is not a separate whole-page architecture.</aside></section>
    <section class="ld-unit ld-card" data-unit="u-card-exports" data-kind="card" data-evidence="src-exports"><div class="ld-unit-tools" data-editor-only=""><div class="ld-ab" role="group" tabindex="0" aria-label="Treatment"><button type="button" data-select-variant="a" aria-label="Treatment A" aria-pressed="true">A</button><button type="button" data-select-variant="b" aria-label="Treatment B" aria-pressed="false">B</button></div><button class="ld-why-toggle" type="button" aria-expanded="false">Why</button></div><div class="ld-variant" data-variant="a"><div data-variant-body=""><h3 data-card-title="" data-editable="" data-placeholder-title="[Name the topic.]">Export options</h3><div class="ld-card-copy" data-editable=""><p>Export HTML keeps the selected approach, edits and intended interactions. Export template keeps both structures with descriptive placeholders instead of matter content.</p></div></div></div><div class="ld-variant" data-variant="b" hidden=""><div data-variant-body=""><h3 data-card-title="" data-editable="" data-placeholder-title="[Name the topic.]">Deliver or reuse</h3><div class="ld-card-copy" data-editable=""><p>Use HTML for delivery. Use a template to reuse the design without private names, source images, URLs or saved responses.</p></div></div></div><aside class="ld-why" hidden="">A concise explanation of the current rule; this legacy component variant is not a separate whole-page architecture.</aside></section>
  </div>
  <section class="ld-unit ld-next" data-unit="u-next" data-kind="text"><div class="ld-unit-tools" data-editor-only=""><div class="ld-ab" role="group" tabindex="0" aria-label="Treatment"><button type="button" data-select-variant="a" aria-label="Treatment A" aria-pressed="true">A</button><button type="button" data-select-variant="b" aria-label="Treatment B" aria-pressed="false">B</button></div><button class="ld-why-toggle" type="button" aria-expanded="false">Why</button></div><div class="ld-variant" data-variant="a"><div data-variant-body=""><p data-editable="">Attach the completed work and name its audience. The output is one responsive page or a slide sequence, with explanations in popups and links to original sources.</p></div></div><div class="ld-variant" data-variant="b" hidden=""><div data-variant-body=""><p data-editable="">Choose the default colors or your firm’s palette once. Accessible color mapping is saved in DESIGN.md; typography, geometry and component behavior stay fixed.</p></div></div><aside class="ld-why" hidden="">A concise explanation of the current rule; this legacy component variant is not a separate whole-page architecture.</aside></section>
  <section class="ld-unit ld-scope" data-unit="u-scope" data-kind="text" data-single=""><div class="ld-variant" data-variant="a"><div data-variant-body=""><p data-editable="">This maintained component reference describes the current skill. New artifacts use two complete approaches; the saved variants here preserve legacy reference compatibility.</p></div></div></section>
  <footer class="ld-foot"><p data-placeholder="[How to use the page, in one line.]">Select a design decision or card to read its rule.</p><p data-placeholder="[Provenance line.]">Canonical skill instructions · 4 September 2026</p></footer>
</main>
<div class="ld-scrim" id="popup-scrim" hidden=""><div class="pop" id="src-questions" data-evidence-id="src-questions" hidden="" role="dialog" aria-modal="true" aria-labelledby="src-questions-t"><button class="ld-popup-close" type="button" aria-label="Close source popup">×</button><h2 id="src-questions-t" data-editable="" data-evidence-field="popup.title">Questions only where information is missing</h2><p class="ld-popup-lede" data-editable="" data-evidence-field="popup.lede">The skill reads the context first, then asks only what it needs to understand the audience, task or source material.</p><section class="ld-popup-section"><h3 data-editable="" data-evidence-field="popup.sections.0.heading">Current rule</h3><p class="doc-text" data-editable="" data-evidence-field="popup.sections.0.body">Ask only unresolved questions, together, using an exposed and permitted structured user-input tool where available.</p></section><p class="ld-popup-source">references/method.md · Intake: at most three questions</p><p class="out ld-source-unavailable">Public original link not supplied.</p></div><div class="pop" id="src-brief" data-evidence-id="src-brief" hidden="" role="dialog" aria-modal="true" aria-labelledby="src-brief-t"><button class="ld-popup-close" type="button" aria-label="Close source popup">×</button><h2 id="src-brief-t" data-editable="" data-evidence-field="popup.title">What the geometry means</h2><p class="ld-popup-lede" data-editable="" data-evidence-field="popup.lede">Position and connectors must carry a specific meaning. A decorative outline or arrow does not explain a relationship.</p><section class="ld-popup-section"><h3 data-editable="" data-evidence-field="popup.sections.0.heading">Current rule</h3><p class="doc-text" data-editable="" data-evidence-field="popup.sections.0.body">Specify the technical mapping: which order expresses time or dependency, which enclosure means membership, which connection means responsibility, which alignment permits comparison, or which axis/length encodes a measured value.</p></section><p class="ld-popup-source">references/method.md · How does the geometry encode it?</p><p class="out ld-source-unavailable">Public original link not supplied.</p></div><div class="pop" id="src-relationship" data-evidence-id="src-relationship" hidden="" role="dialog" aria-modal="true" aria-labelledby="src-relationship-t"><button class="ld-popup-close" type="button" aria-label="Close source popup">×</button><h2 id="src-relationship-t" data-editable="" data-evidence-field="popup.title">Audience and the core relationship</h2><p class="ld-popup-lede" data-editable="" data-evidence-field="popup.lede">Identify what the reader must understand or do, and which relationship in the actual material supports that task.</p><section class="ld-popup-section"><h3 data-editable="" data-evidence-field="popup.sections.0.heading">Current rule</h3><p class="doc-text" data-editable="" data-evidence-field="popup.sections.0.body">Identify which facts establish it and why it matters to this audience's action.</p></section><p class="ld-popup-source">references/method.md · What must this reader understand?</p><p class="out ld-source-unavailable">Public original link not supplied.</p></div><div class="pop" id="src-form" data-evidence-id="src-form" hidden="" role="dialog" aria-modal="true" aria-labelledby="src-form-t"><button class="ld-popup-close" type="button" aria-label="Close source popup">×</button><h2 id="src-form-t" data-editable="" data-evidence-field="popup.title">Choosing a representation</h2><p class="ld-popup-lede" data-editable="" data-evidence-field="popup.lede">A diagram is not automatic. Compare plausible forms and use the one that communicates the supported relationship most directly.</p><section class="ld-popup-section"><h3 data-editable="" data-evidence-field="popup.sections.0.heading">Current rule</h3><p class="doc-text" data-editable="" data-evidence-field="popup.sections.0.body">Consider two or three plausible forms, including a plain sentence, row, or table.</p></section><p class="ld-popup-source">references/method.md · Which representation communicates it best?</p><p class="out ld-source-unavailable">Public original link not supplied.</p></div><div class="pop" id="src-treatments" data-evidence-id="src-treatments" hidden="" role="dialog" aria-modal="true" aria-labelledby="src-treatments-t"><button class="ld-popup-close" type="button" aria-label="Close source popup">×</button><h2 id="src-treatments-t" data-editable="" data-evidence-field="popup.title">Two complete approaches</h2><p class="ld-popup-lede" data-editable="" data-evidence-field="popup.lede">New artifacts compare full information structures with one global selector. The same material and legal meaning are preserved in both.</p><section class="ld-popup-section"><h3 data-editable="" data-evidence-field="popup.sections.0.heading">Current rule</h3><p class="doc-text" data-editable="" data-evidence-field="popup.sections.0.body">Each approach has its own complete structure and independent edits; there are no per-element A/B selectors.</p></section><p class="ld-popup-source">SKILL.md · Deliverable contract</p><p class="out ld-source-unavailable">Public original link not supplied.</p></div><div class="pop" id="src-exports" data-evidence-id="src-exports" hidden="" role="dialog" aria-modal="true" aria-labelledby="src-exports-t"><button class="ld-popup-close" type="button" aria-label="Close source popup">×</button><h2 id="src-exports-t" data-editable="" data-evidence-field="popup.title">Client and template exports</h2><p class="ld-popup-lede" data-editable="" data-evidence-field="popup.lede">The client file retains the selected approach. The reusable template retains both structures and descriptive placeholders without private matter.</p><section class="ld-popup-section"><h3 data-editable="" data-evidence-field="popup.sections.0.heading">Current rule</h3><p class="doc-text" data-editable="" data-evidence-field="popup.sections.0.body">Export HTML retains only the selected approach and intended reader interactions, without authoring chrome or rationales.</p></section><p class="ld-popup-source">SKILL.md · Deliverable contract</p><p class="out ld-source-unavailable">Public original link not supplied.</p></div></div>





</body></html>

SHA-256: 47218e81db7e608d1f6ec9640763ccc3f9c4e7a6db34da7c04f9bb200556d925