@canopy-iiif/app 1.5.9 → 1.5.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/lib/build/iiif.js +7 -2
  2. package/lib/build/pages.js +20 -2
  3. package/lib/components/navigation.js +21 -0
  4. package/package.json +1 -1
  5. package/ui/dist/index.mjs +524 -204
  6. package/ui/dist/index.mjs.map +4 -4
  7. package/ui/dist/server.mjs +602 -309
  8. package/ui/dist/server.mjs.map +4 -4
  9. package/ui/styles/base/_heading.scss +1 -1
  10. package/ui/styles/base/_markdown.scss +1 -1
  11. package/ui/styles/components/_article-card.scss +1 -1
  12. package/ui/styles/components/_bibliography.scss +1 -2
  13. package/ui/styles/components/_card.scss +1 -1
  14. package/ui/styles/components/_diagram.scss +1 -1
  15. package/ui/styles/components/_footer.scss +1 -1
  16. package/ui/styles/components/_interstitial-hero.scss +3 -3
  17. package/ui/styles/components/_layout.scss +16 -2
  18. package/ui/styles/components/_map.scss +4 -4
  19. package/ui/styles/components/_nav-tree.scss +67 -0
  20. package/ui/styles/components/_referenced-items.scss +3 -3
  21. package/ui/styles/components/_sub-navigation.scss +42 -21
  22. package/ui/styles/components/_timeline.scss +10 -10
  23. package/ui/styles/components/_work.scss +1 -1
  24. package/ui/styles/components/header/_header.scss +4 -2
  25. package/ui/styles/components/header/_navbar.scss +65 -50
  26. package/ui/styles/components/iiif/_image.scss +1 -1
  27. package/ui/styles/components/index.scss +1 -0
  28. package/ui/styles/components/modal/_modal.scss +1 -1
  29. package/ui/styles/components/search/_filters.scss +15 -19
  30. package/ui/styles/components/search/_form.scss +1 -1
  31. package/ui/styles/components/search/_results.scss +4 -4
  32. package/ui/styles/index.css +217 -123
  33. package/ui/styles/settings/_breakpoints.scss +7 -0
  34. package/ui/theme.js +9 -9
@@ -843,52 +843,140 @@ function Hero({
843
843
  }
844
844
 
845
845
  // ui/src/layout/SubNavigation.jsx
846
- import React10 from "react";
846
+ import React11 from "react";
847
847
  import navigationHelpers2 from "@canopy-iiif/app/lib/components/navigation.js";
848
- function resolveRelativeCandidate(page, current) {
849
- if (page && typeof page.relativePath === "string" && page.relativePath)
850
- return page.relativePath;
851
- if (page && typeof page.slug === "string" && page.slug) return page.slug;
852
- if (typeof current === "string" && current) return current;
853
- return "";
848
+
849
+ // ui/src/layout/NavigationTree.jsx
850
+ import React10 from "react";
851
+ function normalizeDepth(depth) {
852
+ if (typeof depth !== "number") return 0;
853
+ return Math.max(0, Math.min(5, depth));
854
854
  }
855
- function renderNodes(nodes, parentKey = "node") {
855
+ function NavigationTreeList({ nodes, depth, parentKey }) {
856
856
  if (!Array.isArray(nodes) || !nodes.length) return null;
857
- return nodes.map((node, index) => {
858
- if (!node) return null;
859
- const key = node.slug || node.relativePath || `${parentKey}-${index}`;
860
- const hasChildren = Array.isArray(node.children) && node.children.length > 0;
861
- const showChildren = hasChildren && (node.isExpanded || node.depth === 0);
862
- const depth = typeof node.depth === "number" ? Math.max(0, node.depth) : 0;
863
- const depthClass = `depth-${Math.min(depth, 5)}`;
864
- const isRoadmap = !!node.isRoadmap;
865
- const isInteractive = !!(node.href && !isRoadmap);
866
- const classes = ["canopy-sub-navigation__link", depthClass];
867
- if (!node.href && !isRoadmap) classes.push("is-label");
868
- if (isRoadmap) classes.push("is-disabled");
869
- if (node.isActive) classes.push("is-active");
870
- const linkClass = classes.join(" ");
871
- const Tag = isInteractive ? "a" : "span";
872
- const badge = isRoadmap ? /* @__PURE__ */ React10.createElement("span", { className: "canopy-sub-navigation__badge" }, "Roadmap") : null;
873
- return /* @__PURE__ */ React10.createElement("li", { key, className: "canopy-sub-navigation__item", "data-depth": depth }, /* @__PURE__ */ React10.createElement(
857
+ const listClasses = ["canopy-nav-tree__list"];
858
+ if (depth > 0) listClasses.push("canopy-nav-tree__list--nested");
859
+ return /* @__PURE__ */ React10.createElement("ul", { className: listClasses.join(" "), role: "list" }, nodes.map((node, index) => /* @__PURE__ */ React10.createElement(
860
+ NavigationTreeItem,
861
+ {
862
+ key: node.slug || node.href || node.title || `${parentKey}-${index}`,
863
+ node,
864
+ depth,
865
+ nodeKey: `${parentKey}-${index}`
866
+ }
867
+ )));
868
+ }
869
+ function NavigationTreeItem({ node, depth, nodeKey }) {
870
+ if (!node) return null;
871
+ const hasChildren = Array.isArray(node.children) && node.children.length > 0;
872
+ const isRoadmap = !!node.isRoadmap;
873
+ const isInteractive = !!(node.href && !isRoadmap);
874
+ const Tag = isInteractive ? "a" : "span";
875
+ const depthClass = `depth-${normalizeDepth(depth + 1)}`;
876
+ const classes = ["canopy-nav-tree__link", depthClass];
877
+ if (!isInteractive && !isRoadmap) classes.push("is-label");
878
+ if (isRoadmap) classes.push("is-disabled");
879
+ if (node.isActive) classes.push("is-active");
880
+ const isRootLevel = depth < 0;
881
+ const panelId = hasChildren ? `canopy-section-${nodeKey}` : null;
882
+ const allowToggle = hasChildren && !isRootLevel;
883
+ const defaultExpanded = allowToggle ? !!node.isExpanded : true;
884
+ const toggleLabel = node.title ? `Toggle ${node.title} menu` : "Toggle section menu";
885
+ return /* @__PURE__ */ React10.createElement(
886
+ "li",
887
+ {
888
+ className: "canopy-nav-tree__item",
889
+ "data-depth": depth,
890
+ "data-canopy-nav-item": allowToggle ? "true" : void 0,
891
+ "data-expanded": allowToggle ? defaultExpanded ? "true" : "false" : void 0,
892
+ "data-default-expanded": allowToggle && defaultExpanded ? "true" : void 0
893
+ },
894
+ /* @__PURE__ */ React10.createElement("div", { className: "canopy-nav-tree__row" }, /* @__PURE__ */ React10.createElement(
874
895
  Tag,
875
896
  {
876
- className: linkClass,
897
+ className: classes.join(" "),
877
898
  href: isInteractive ? node.href : void 0,
878
899
  "aria-current": node.isActive ? "page" : void 0,
879
900
  tabIndex: isInteractive ? void 0 : -1
880
901
  },
881
902
  node.title || node.slug,
882
- badge
883
- ), showChildren ? /* @__PURE__ */ React10.createElement(
884
- "ul",
903
+ isRoadmap ? /* @__PURE__ */ React10.createElement("span", { className: "canopy-nav-tree__badge" }, "Roadmap") : null
904
+ ), allowToggle ? /* @__PURE__ */ React10.createElement(
905
+ "button",
885
906
  {
886
- className: "canopy-sub-navigation__list canopy-sub-navigation__list--nested",
887
- role: "list"
907
+ type: "button",
908
+ className: "canopy-nav-tree__toggle",
909
+ "aria-expanded": defaultExpanded ? "true" : "false",
910
+ "aria-controls": panelId || void 0,
911
+ "aria-label": toggleLabel,
912
+ "data-canopy-nav-item-toggle": panelId || void 0
888
913
  },
889
- renderNodes(node.children, key)
890
- ) : null);
891
- });
914
+ /* @__PURE__ */ React10.createElement(
915
+ "svg",
916
+ {
917
+ xmlns: "http://www.w3.org/2000/svg",
918
+ viewBox: "0 0 24 24",
919
+ fill: "none",
920
+ stroke: "currentColor",
921
+ strokeWidth: "1.5",
922
+ className: "canopy-nav-tree__toggle-icon"
923
+ },
924
+ /* @__PURE__ */ React10.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M5 9l7 7 7-7" })
925
+ ),
926
+ /* @__PURE__ */ React10.createElement("span", { className: "sr-only" }, toggleLabel)
927
+ ) : null),
928
+ hasChildren ? /* @__PURE__ */ React10.createElement(
929
+ "div",
930
+ {
931
+ id: panelId || void 0,
932
+ className: "canopy-nav-tree__children",
933
+ "aria-hidden": allowToggle ? defaultExpanded ? "false" : "true" : "false",
934
+ hidden: allowToggle ? !defaultExpanded : void 0
935
+ },
936
+ /* @__PURE__ */ React10.createElement(
937
+ NavigationTreeList,
938
+ {
939
+ nodes: node.children,
940
+ depth: depth + 1,
941
+ parentKey: nodeKey
942
+ }
943
+ )
944
+ ) : null
945
+ );
946
+ }
947
+ function NavigationTree({
948
+ root,
949
+ className = "",
950
+ parentKey = "nav",
951
+ includeRoot = false,
952
+ heading,
953
+ headingClassName = "canopy-nav-tree__heading",
954
+ component: Component = "div",
955
+ ...rest
956
+ }) {
957
+ if (!root) return null;
958
+ const nodes = includeRoot ? [root] : root.children;
959
+ if (!Array.isArray(nodes) || !nodes.length) return null;
960
+ const combinedClassName = ["canopy-nav-tree", className].filter(Boolean).join(" ");
961
+ return /* @__PURE__ */ React10.createElement(
962
+ Component,
963
+ {
964
+ className: combinedClassName,
965
+ "data-canopy-nav-tree": "true",
966
+ ...rest
967
+ },
968
+ heading ? /* @__PURE__ */ React10.createElement("div", { className: headingClassName }, heading) : null,
969
+ /* @__PURE__ */ React10.createElement(NavigationTreeList, { nodes, depth: includeRoot ? -1 : 0, parentKey })
970
+ );
971
+ }
972
+
973
+ // ui/src/layout/SubNavigation.jsx
974
+ function resolveRelativeCandidate(page, current) {
975
+ if (page && typeof page.relativePath === "string" && page.relativePath)
976
+ return page.relativePath;
977
+ if (page && typeof page.slug === "string" && page.slug) return page.slug;
978
+ if (typeof current === "string" && current) return current;
979
+ return "";
892
980
  }
893
981
  function SubNavigation({
894
982
  navigation: navigationProp,
@@ -900,12 +988,12 @@ function SubNavigation({
900
988
  ariaLabel
901
989
  }) {
902
990
  const PageContext = navigationHelpers2 && navigationHelpers2.getPageContext ? navigationHelpers2.getPageContext() : null;
903
- const context = PageContext ? React10.useContext(PageContext) : null;
991
+ const context = PageContext ? React11.useContext(PageContext) : null;
904
992
  const contextNavigation = context && context.navigation ? context.navigation : null;
905
993
  const contextPage = context && context.page ? context.page : null;
906
994
  const effectiveNavigation = navigationProp || contextNavigation;
907
995
  const effectivePage = page || contextPage;
908
- const resolvedNavigation = React10.useMemo(() => {
996
+ const resolvedNavigation = React11.useMemo(() => {
909
997
  if (effectiveNavigation && effectiveNavigation.root)
910
998
  return effectiveNavigation;
911
999
  const candidate = resolveRelativeCandidate(effectivePage, current);
@@ -930,24 +1018,33 @@ function SubNavigation({
930
1018
  if (!Object.prototype.hasOwnProperty.call(inlineStyle, "--sub-nav-indent")) {
931
1019
  inlineStyle["--sub-nav-indent"] = "0.85rem";
932
1020
  }
933
- return /* @__PURE__ */ React10.createElement(
1021
+ return /* @__PURE__ */ React11.createElement(
934
1022
  "nav",
935
1023
  {
936
1024
  className: combinedClassName,
937
1025
  style: inlineStyle,
938
1026
  "aria-label": navLabel
939
1027
  },
940
- finalHeading ? /* @__PURE__ */ React10.createElement("div", { className: "canopy-sub-navigation__heading" }, finalHeading) : null,
941
- /* @__PURE__ */ React10.createElement("ul", { className: "canopy-sub-navigation__list", role: "list" }, renderNodes([rootNode], rootNode.slug || "root"))
1028
+ /* @__PURE__ */ React11.createElement(
1029
+ NavigationTree,
1030
+ {
1031
+ root: rootNode,
1032
+ includeRoot: true,
1033
+ component: "div",
1034
+ className: "canopy-sub-navigation__tree",
1035
+ parentKey: rootNode.slug || "root",
1036
+ heading: finalHeading || void 0
1037
+ }
1038
+ )
942
1039
  );
943
1040
  }
944
1041
 
945
1042
  // ui/src/layout/Layout.jsx
946
- import React12 from "react";
1043
+ import React13 from "react";
947
1044
  import navigationHelpers3 from "@canopy-iiif/app/lib/components/navigation.js";
948
1045
 
949
1046
  // ui/src/layout/ContentNavigation.jsx
950
- import React11 from "react";
1047
+ import React12 from "react";
951
1048
  var SCROLL_OFFSET_REM = 1.618;
952
1049
  var MAX_HEADING_DEPTH = 3;
953
1050
  function depthIndex(depth) {
@@ -966,9 +1063,9 @@ function ContentNavigation({
966
1063
  ariaLabel
967
1064
  }) {
968
1065
  const isBrowser = typeof window !== "undefined" && typeof document !== "undefined";
969
- const savedDepthsRef = React11.useRef(null);
970
- const [isExpanded, setIsExpanded] = React11.useState(false);
971
- const handleToggle = React11.useCallback(() => {
1066
+ const savedDepthsRef = React12.useRef(null);
1067
+ const [isExpanded, setIsExpanded] = React12.useState(false);
1068
+ const handleToggle = React12.useCallback(() => {
972
1069
  setIsExpanded((prev) => !prev);
973
1070
  }, []);
974
1071
  if ((!items || !items.length) && !headingId) return null;
@@ -979,7 +1076,7 @@ function ContentNavigation({
979
1076
  ].filter(Boolean).join(" ");
980
1077
  const effectiveHeading = heading || pageTitle || null;
981
1078
  const navLabel = ariaLabel || (effectiveHeading ? `${effectiveHeading} navigation` : "Section navigation");
982
- const getSavedDepth = React11.useCallback(
1079
+ const getSavedDepth = React12.useCallback(
983
1080
  (id, fallback) => {
984
1081
  if (!id) return fallback;
985
1082
  if (!savedDepthsRef.current) savedDepthsRef.current = /* @__PURE__ */ new Map();
@@ -990,7 +1087,7 @@ function ContentNavigation({
990
1087
  },
991
1088
  []
992
1089
  );
993
- const headingEntries = React11.useMemo(() => {
1090
+ const headingEntries = React12.useMemo(() => {
994
1091
  const entries = [];
995
1092
  const seen = /* @__PURE__ */ new Set();
996
1093
  if (headingId) {
@@ -1018,12 +1115,12 @@ function ContentNavigation({
1018
1115
  return entries;
1019
1116
  }, [headingId, items, getSavedDepth]);
1020
1117
  const fallbackId = headingEntries.length ? headingEntries[0].id : headingId || null;
1021
- const [activeId, setActiveId] = React11.useState(fallbackId);
1022
- const activeIdRef = React11.useRef(activeId);
1023
- React11.useEffect(() => {
1118
+ const [activeId, setActiveId] = React12.useState(fallbackId);
1119
+ const activeIdRef = React12.useRef(activeId);
1120
+ React12.useEffect(() => {
1024
1121
  activeIdRef.current = activeId;
1025
1122
  }, [activeId]);
1026
- React11.useEffect(() => {
1123
+ React12.useEffect(() => {
1027
1124
  if (!headingEntries.length) return;
1028
1125
  if (!headingEntries.some((entry) => entry.id === activeIdRef.current)) {
1029
1126
  const next = headingEntries[0].id;
@@ -1031,7 +1128,7 @@ function ContentNavigation({
1031
1128
  setActiveId(next);
1032
1129
  }
1033
1130
  }, [headingEntries]);
1034
- const computeOffsetPx = React11.useCallback(() => {
1131
+ const computeOffsetPx = React12.useCallback(() => {
1035
1132
  if (!isBrowser) return 0;
1036
1133
  try {
1037
1134
  const root = document.documentElement;
@@ -1041,8 +1138,8 @@ function ContentNavigation({
1041
1138
  return 0;
1042
1139
  }
1043
1140
  }, [isBrowser]);
1044
- const headingElementsRef = React11.useRef([]);
1045
- const updateActiveFromElements = React11.useCallback(
1141
+ const headingElementsRef = React12.useRef([]);
1142
+ const updateActiveFromElements = React12.useCallback(
1046
1143
  (elements) => {
1047
1144
  if (!elements || !elements.length) return;
1048
1145
  const offset = computeOffsetPx();
@@ -1062,7 +1159,7 @@ function ContentNavigation({
1062
1159
  },
1063
1160
  [computeOffsetPx]
1064
1161
  );
1065
- React11.useEffect(() => {
1162
+ React12.useEffect(() => {
1066
1163
  if (!isBrowser) return void 0;
1067
1164
  const elements = headingEntries.map((entry) => {
1068
1165
  const element = document.getElementById(entry.id);
@@ -1088,7 +1185,7 @@ function ContentNavigation({
1088
1185
  window.removeEventListener("resize", handle);
1089
1186
  };
1090
1187
  }, [headingEntries, isBrowser, updateActiveFromElements]);
1091
- const handleAnchorClick = React11.useCallback(
1188
+ const handleAnchorClick = React12.useCallback(
1092
1189
  (event, targetId, options = {}) => {
1093
1190
  var _a;
1094
1191
  try {
@@ -1119,7 +1216,7 @@ function ContentNavigation({
1119
1216
  },
1120
1217
  [computeOffsetPx, headingEntries, headingId, isBrowser]
1121
1218
  );
1122
- const renderNodes2 = React11.useCallback(
1219
+ const renderNodes = React12.useCallback(
1123
1220
  (nodes) => {
1124
1221
  if (!nodes || !nodes.length) return null;
1125
1222
  return nodes.map((node) => {
@@ -1132,8 +1229,8 @@ function ContentNavigation({
1132
1229
  if (depth > MAX_HEADING_DEPTH) return null;
1133
1230
  const idx = depthIndex(depth);
1134
1231
  const isActive = id && activeId === id;
1135
- const childNodes = depth < MAX_HEADING_DEPTH ? renderNodes2(node.children) : null;
1136
- return /* @__PURE__ */ React11.createElement("li", { key: id || node.title, className: "canopy-sub-navigation__item", "data-depth": idx }, /* @__PURE__ */ React11.createElement(
1232
+ const childNodes = depth < MAX_HEADING_DEPTH ? renderNodes(node.children) : null;
1233
+ return /* @__PURE__ */ React12.createElement("li", { key: id || node.title, className: "canopy-sub-navigation__item", "data-depth": idx }, /* @__PURE__ */ React12.createElement(
1137
1234
  "a",
1138
1235
  {
1139
1236
  className: `canopy-sub-navigation__link depth-${idx}${isActive ? " is-active" : ""}`,
@@ -1142,7 +1239,7 @@ function ContentNavigation({
1142
1239
  "aria-current": isActive ? "location" : void 0
1143
1240
  },
1144
1241
  node.title
1145
- ), childNodes ? /* @__PURE__ */ React11.createElement(
1242
+ ), childNodes ? /* @__PURE__ */ React12.createElement(
1146
1243
  "ul",
1147
1244
  {
1148
1245
  className: "canopy-sub-navigation__list canopy-sub-navigation__list--nested",
@@ -1154,8 +1251,8 @@ function ContentNavigation({
1154
1251
  },
1155
1252
  [handleAnchorClick, activeId, getSavedDepth]
1156
1253
  );
1157
- const nestedItems = React11.useMemo(() => renderNodes2(items), [items, renderNodes2]);
1158
- const topLink = headingId ? /* @__PURE__ */ React11.createElement("li", { className: "canopy-sub-navigation__item", "data-depth": 0 }, /* @__PURE__ */ React11.createElement(
1254
+ const nestedItems = React12.useMemo(() => renderNodes(items), [items, renderNodes]);
1255
+ const topLink = headingId ? /* @__PURE__ */ React12.createElement("li", { className: "canopy-sub-navigation__item", "data-depth": 0 }, /* @__PURE__ */ React12.createElement(
1159
1256
  "a",
1160
1257
  {
1161
1258
  className: `canopy-sub-navigation__link depth-0${activeId === headingId ? " is-active" : ""}`,
@@ -1164,7 +1261,7 @@ function ContentNavigation({
1164
1261
  "aria-current": activeId === headingId ? "location" : void 0
1165
1262
  },
1166
1263
  effectiveHeading || pageTitle || headingId
1167
- ), nestedItems ? /* @__PURE__ */ React11.createElement(
1264
+ ), nestedItems ? /* @__PURE__ */ React12.createElement(
1168
1265
  "ul",
1169
1266
  {
1170
1267
  className: "canopy-sub-navigation__list canopy-sub-navigation__list--nested",
@@ -1172,7 +1269,7 @@ function ContentNavigation({
1172
1269
  },
1173
1270
  nestedItems
1174
1271
  ) : null) : null;
1175
- return /* @__PURE__ */ React11.createElement(
1272
+ return /* @__PURE__ */ React12.createElement(
1176
1273
  "nav",
1177
1274
  {
1178
1275
  className: combinedClassName,
@@ -1180,7 +1277,7 @@ function ContentNavigation({
1180
1277
  "aria-label": navLabel,
1181
1278
  "data-canopy-content-nav": "true"
1182
1279
  },
1183
- /* @__PURE__ */ React11.createElement(
1280
+ /* @__PURE__ */ React12.createElement(
1184
1281
  "button",
1185
1282
  {
1186
1283
  type: "button",
@@ -1197,7 +1294,7 @@ function ContentNavigation({
1197
1294
  },
1198
1295
  isExpanded ? "Hide" : "Show"
1199
1296
  ),
1200
- /* @__PURE__ */ React11.createElement("ul", { className: "canopy-sub-navigation__list", role: "list" }, topLink || nestedItems)
1297
+ /* @__PURE__ */ React12.createElement("ul", { className: "canopy-sub-navigation__list", role: "list" }, topLink || nestedItems)
1201
1298
  );
1202
1299
  }
1203
1300
 
@@ -1230,10 +1327,10 @@ function buildHeadingTree(headings) {
1230
1327
  }
1231
1328
  function buildNavigationAside(sidebar, className) {
1232
1329
  if (!sidebar) {
1233
- return /* @__PURE__ */ React12.createElement(SubNavigation, { className });
1330
+ return /* @__PURE__ */ React13.createElement(SubNavigation, { className });
1234
1331
  }
1235
1332
  if (typeof sidebar === "function") {
1236
- return React12.createElement(sidebar);
1333
+ return React13.createElement(sidebar);
1237
1334
  }
1238
1335
  return sidebar;
1239
1336
  }
@@ -1335,7 +1432,7 @@ function ContentNavigationScript() {
1335
1432
  });
1336
1433
  })();
1337
1434
  `;
1338
- return /* @__PURE__ */ React12.createElement("script", { dangerouslySetInnerHTML: { __html: code } });
1435
+ return /* @__PURE__ */ React13.createElement("script", { dangerouslySetInnerHTML: { __html: code } });
1339
1436
  }
1340
1437
  function Layout({
1341
1438
  children,
@@ -1350,26 +1447,26 @@ function Layout({
1350
1447
  ...rest
1351
1448
  }) {
1352
1449
  const PageContext = navigationHelpers3 && typeof navigationHelpers3.getPageContext === "function" ? navigationHelpers3.getPageContext() : null;
1353
- const context = PageContext ? React12.useContext(PageContext) : null;
1354
- const pageHeadings = React12.useMemo(() => {
1450
+ const context = PageContext ? React13.useContext(PageContext) : null;
1451
+ const pageHeadings = React13.useMemo(() => {
1355
1452
  const headings = context && context.page ? context.page.headings : null;
1356
1453
  return Array.isArray(headings) ? headings : [];
1357
1454
  }, [context]);
1358
- const contentHeading = React12.useMemo(() => {
1455
+ const contentHeading = React13.useMemo(() => {
1359
1456
  const first = pageHeadings.find((heading) => {
1360
1457
  const depth = heading && (heading.depth || heading.level);
1361
1458
  return depth === 1;
1362
1459
  });
1363
1460
  return first && first.title ? first.title : null;
1364
1461
  }, [pageHeadings]);
1365
- const headingAnchorId = React12.useMemo(() => {
1462
+ const headingAnchorId = React13.useMemo(() => {
1366
1463
  const first = pageHeadings.find((heading) => {
1367
1464
  const depth = heading && (heading.depth || heading.level);
1368
1465
  return depth === 1;
1369
1466
  });
1370
1467
  return first && first.id ? first.id : null;
1371
1468
  }, [pageHeadings]);
1372
- const headingTree = React12.useMemo(
1469
+ const headingTree = React13.useMemo(
1373
1470
  () => buildHeadingTree(pageHeadings),
1374
1471
  [pageHeadings]
1375
1472
  );
@@ -1394,13 +1491,13 @@ function Layout({
1394
1491
  contentNavigationClassName
1395
1492
  ].filter(Boolean).join(" ");
1396
1493
  const sidebarNode = showLeftColumn ? buildNavigationAside(sidebar, sidebarClassName) : null;
1397
- return /* @__PURE__ */ React12.createElement("div", { className: containerClassName, ...rest }, showLeftColumn ? /* @__PURE__ */ React12.createElement("aside", { className: leftAsideClassName }, sidebarNode) : null, /* @__PURE__ */ React12.createElement("div", { className: contentClassNames }, children), hasContentNavigation ? /* @__PURE__ */ React12.createElement(React12.Fragment, null, /* @__PURE__ */ React12.createElement(
1494
+ return /* @__PURE__ */ React13.createElement("div", { className: containerClassName, ...rest }, showLeftColumn ? /* @__PURE__ */ React13.createElement("aside", { className: leftAsideClassName }, sidebarNode) : null, /* @__PURE__ */ React13.createElement("div", { className: contentClassNames }, children), hasContentNavigation ? /* @__PURE__ */ React13.createElement(React13.Fragment, null, /* @__PURE__ */ React13.createElement(
1398
1495
  "aside",
1399
1496
  {
1400
1497
  className: contentNavigationAsideClassName,
1401
1498
  "data-canopy-content-nav-root": "true"
1402
1499
  },
1403
- /* @__PURE__ */ React12.createElement(
1500
+ /* @__PURE__ */ React13.createElement(
1404
1501
  ContentNavigation,
1405
1502
  {
1406
1503
  items: headingTree,
@@ -1409,21 +1506,21 @@ function Layout({
1409
1506
  pageTitle: context && context.page ? context.page.title : void 0
1410
1507
  }
1411
1508
  )
1412
- ), /* @__PURE__ */ React12.createElement(ContentNavigationScript, null)) : null);
1509
+ ), /* @__PURE__ */ React13.createElement(ContentNavigationScript, null)) : null);
1413
1510
  }
1414
1511
 
1415
1512
  // ui/src/layout/CanopyHeader.jsx
1416
- import React19 from "react";
1513
+ import React20 from "react";
1417
1514
 
1418
1515
  // ui/src/search/SearchPanel.jsx
1419
- import React16 from "react";
1516
+ import React17 from "react";
1420
1517
 
1421
1518
  // ui/src/Icons.jsx
1422
- import React13 from "react";
1423
- var MagnifyingGlassIcon = (props) => /* @__PURE__ */ React13.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 512 512", "aria-hidden": "true", focusable: "false", ...props }, /* @__PURE__ */ React13.createElement("path", { d: "M456.69 421.39L362.6 327.3a173.81 173.81 0 0034.84-104.58C397.44 126.38 319.06 48 222.72 48S48 126.38 48 222.72s78.38 174.72 174.72 174.72A173.81 173.81 0 00327.3 362.6l94.09 94.09a25 25 0 0035.3-35.3zM97.92 222.72a124.8 124.8 0 11124.8 124.8 124.95 124.95 0 01-124.8-124.8z" }));
1519
+ import React14 from "react";
1520
+ var MagnifyingGlassIcon = (props) => /* @__PURE__ */ React14.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 512 512", "aria-hidden": "true", focusable: "false", ...props }, /* @__PURE__ */ React14.createElement("path", { d: "M456.69 421.39L362.6 327.3a173.81 173.81 0 0034.84-104.58C397.44 126.38 319.06 48 222.72 48S48 126.38 48 222.72s78.38 174.72 174.72 174.72A173.81 173.81 0 00327.3 362.6l94.09 94.09a25 25 0 0035.3-35.3zM97.92 222.72a124.8 124.8 0 11124.8 124.8 124.95 124.95 0 01-124.8-124.8z" }));
1424
1521
 
1425
1522
  // ui/src/search/SearchPanelForm.jsx
1426
- import React14 from "react";
1523
+ import React15 from "react";
1427
1524
  function readBasePath() {
1428
1525
  const normalize = (val) => {
1429
1526
  const raw = typeof val === "string" ? val.trim() : "";
@@ -1486,18 +1583,18 @@ function SearchPanelForm(props = {}) {
1486
1583
  clearLabel = "Clear search"
1487
1584
  } = props || {};
1488
1585
  const text = typeof label === "string" && label.trim() ? label.trim() : buttonLabel;
1489
- const action = React14.useMemo(
1586
+ const action = React15.useMemo(
1490
1587
  () => resolveSearchPath(searchPath),
1491
1588
  [searchPath]
1492
1589
  );
1493
- const autoId = typeof React14.useId === "function" ? React14.useId() : void 0;
1494
- const [fallbackId] = React14.useState(
1590
+ const autoId = typeof React15.useId === "function" ? React15.useId() : void 0;
1591
+ const [fallbackId] = React15.useState(
1495
1592
  () => `canopy-search-form-${Math.random().toString(36).slice(2, 10)}`
1496
1593
  );
1497
1594
  const inputId = inputIdProp || autoId || fallbackId;
1498
- const inputRef = React14.useRef(null);
1499
- const [hasValue, setHasValue] = React14.useState(false);
1500
- const focusInput = React14.useCallback(() => {
1595
+ const inputRef = React15.useRef(null);
1596
+ const [hasValue, setHasValue] = React15.useState(false);
1597
+ const focusInput = React15.useCallback(() => {
1501
1598
  const el = inputRef.current;
1502
1599
  if (!el) return;
1503
1600
  if (document.activeElement === el) return;
@@ -1510,7 +1607,7 @@ function SearchPanelForm(props = {}) {
1510
1607
  }
1511
1608
  }
1512
1609
  }, []);
1513
- const handlePointerDown = React14.useCallback(
1610
+ const handlePointerDown = React15.useCallback(
1514
1611
  (event) => {
1515
1612
  const target = event.target;
1516
1613
  if (target && typeof target.closest === "function") {
@@ -1522,23 +1619,23 @@ function SearchPanelForm(props = {}) {
1522
1619
  },
1523
1620
  [focusInput]
1524
1621
  );
1525
- React14.useEffect(() => {
1622
+ React15.useEffect(() => {
1526
1623
  const el = inputRef.current;
1527
1624
  if (!el) return;
1528
1625
  if (el.value && el.value.trim()) {
1529
1626
  setHasValue(true);
1530
1627
  }
1531
1628
  }, []);
1532
- const handleInputChange = React14.useCallback((event) => {
1629
+ const handleInputChange = React15.useCallback((event) => {
1533
1630
  var _a;
1534
1631
  const nextHasValue = Boolean(
1535
1632
  ((_a = event == null ? void 0 : event.target) == null ? void 0 : _a.value) && event.target.value.trim()
1536
1633
  );
1537
1634
  setHasValue(nextHasValue);
1538
1635
  }, []);
1539
- const handleClear = React14.useCallback((event) => {
1636
+ const handleClear = React15.useCallback((event) => {
1540
1637
  }, []);
1541
- const handleClearKey = React14.useCallback(
1638
+ const handleClearKey = React15.useCallback(
1542
1639
  (event) => {
1543
1640
  if (event.key === "Enter" || event.key === " ") {
1544
1641
  event.preventDefault();
@@ -1547,7 +1644,7 @@ function SearchPanelForm(props = {}) {
1547
1644
  },
1548
1645
  [handleClear]
1549
1646
  );
1550
- return /* @__PURE__ */ React14.createElement(
1647
+ return /* @__PURE__ */ React15.createElement(
1551
1648
  "form",
1552
1649
  {
1553
1650
  action,
@@ -1559,7 +1656,7 @@ function SearchPanelForm(props = {}) {
1559
1656
  onPointerDown: handlePointerDown,
1560
1657
  "data-has-value": hasValue ? "1" : "0"
1561
1658
  },
1562
- /* @__PURE__ */ React14.createElement("label", { htmlFor: inputId, className: "canopy-search-form__label" }, /* @__PURE__ */ React14.createElement(MagnifyingGlassIcon, { className: "canopy-search-form__icon" }), /* @__PURE__ */ React14.createElement("span", { className: "sr-only" }, "Search"), /* @__PURE__ */ React14.createElement(
1659
+ /* @__PURE__ */ React15.createElement("label", { htmlFor: inputId, className: "canopy-search-form__label" }, /* @__PURE__ */ React15.createElement(MagnifyingGlassIcon, { className: "canopy-search-form__icon" }), /* @__PURE__ */ React15.createElement("span", { className: "sr-only" }, "Search"), /* @__PURE__ */ React15.createElement(
1563
1660
  "input",
1564
1661
  {
1565
1662
  id: inputId,
@@ -1574,7 +1671,7 @@ function SearchPanelForm(props = {}) {
1574
1671
  onInput: handleInputChange
1575
1672
  }
1576
1673
  )),
1577
- hasValue ? /* @__PURE__ */ React14.createElement(
1674
+ hasValue ? /* @__PURE__ */ React15.createElement(
1578
1675
  "button",
1579
1676
  {
1580
1677
  type: "button",
@@ -1587,7 +1684,7 @@ function SearchPanelForm(props = {}) {
1587
1684
  },
1588
1685
  "\xD7"
1589
1686
  ) : null,
1590
- /* @__PURE__ */ React14.createElement(
1687
+ /* @__PURE__ */ React15.createElement(
1591
1688
  "button",
1592
1689
  {
1593
1690
  type: "submit",
@@ -1600,11 +1697,11 @@ function SearchPanelForm(props = {}) {
1600
1697
  }
1601
1698
 
1602
1699
  // ui/src/search/SearchPanelTeaserResults.jsx
1603
- import React15 from "react";
1700
+ import React16 from "react";
1604
1701
  function SearchPanelTeaserResults(props = {}) {
1605
1702
  const { style, className } = props || {};
1606
1703
  const classes = ["canopy-search-teaser", "is-empty", className].filter(Boolean).join(" ");
1607
- return /* @__PURE__ */ React15.createElement(
1704
+ return /* @__PURE__ */ React16.createElement(
1608
1705
  "div",
1609
1706
  {
1610
1707
  "data-canopy-search-form-panel": true,
@@ -1612,7 +1709,7 @@ function SearchPanelTeaserResults(props = {}) {
1612
1709
  className: classes || void 0,
1613
1710
  style
1614
1711
  },
1615
- /* @__PURE__ */ React15.createElement("div", { id: "cplist", className: "canopy-search-teaser__list" })
1712
+ /* @__PURE__ */ React16.createElement("div", { id: "cplist", className: "canopy-search-teaser__list" })
1616
1713
  );
1617
1714
  }
1618
1715
 
@@ -1633,11 +1730,11 @@ function SearchPanel(props = {}) {
1633
1730
  const text = typeof label === "string" && label.trim() ? label.trim() : buttonLabel;
1634
1731
  const resolvedSearchPath = resolveSearchPath(searchPath);
1635
1732
  const data = { placeholder, hotkey, maxResults, groupOrder, label: text, searchPath: resolvedSearchPath };
1636
- return /* @__PURE__ */ React16.createElement("div", { "data-canopy-search-form": true, className: "flex-1 min-w-0" }, /* @__PURE__ */ React16.createElement("div", { className: "relative w-full" }, /* @__PURE__ */ React16.createElement(SearchPanelForm, { placeholder, buttonLabel, label, searchPath: resolvedSearchPath }), /* @__PURE__ */ React16.createElement(SearchPanelTeaserResults, null)), /* @__PURE__ */ React16.createElement("script", { type: "application/json", dangerouslySetInnerHTML: { __html: JSON.stringify(data) } }));
1733
+ return /* @__PURE__ */ React17.createElement("div", { "data-canopy-search-form": true, className: "flex-1 min-w-0" }, /* @__PURE__ */ React17.createElement("div", { className: "relative w-full" }, /* @__PURE__ */ React17.createElement(SearchPanelForm, { placeholder, buttonLabel, label, searchPath: resolvedSearchPath }), /* @__PURE__ */ React17.createElement(SearchPanelTeaserResults, null)), /* @__PURE__ */ React17.createElement("script", { type: "application/json", dangerouslySetInnerHTML: { __html: JSON.stringify(data) } }));
1637
1734
  }
1638
1735
 
1639
1736
  // ui/src/layout/CanopyBrand.jsx
1640
- import React17 from "react";
1737
+ import React18 from "react";
1641
1738
  function CanopyBrand(props = {}) {
1642
1739
  const {
1643
1740
  labelId,
@@ -1648,11 +1745,11 @@ function CanopyBrand(props = {}) {
1648
1745
  } = props || {};
1649
1746
  const spanProps = labelId ? { id: labelId } : {};
1650
1747
  const classes = ["canopy-logo", className].filter(Boolean).join(" ");
1651
- return /* @__PURE__ */ React17.createElement("a", { href, className: classes }, typeof Logo === "function" ? /* @__PURE__ */ React17.createElement(Logo, null) : null, /* @__PURE__ */ React17.createElement("span", { ...spanProps }, label));
1748
+ return /* @__PURE__ */ React18.createElement("a", { href, className: classes }, typeof Logo === "function" ? /* @__PURE__ */ React18.createElement(Logo, null) : null, /* @__PURE__ */ React18.createElement("span", { ...spanProps }, label));
1652
1749
  }
1653
1750
 
1654
1751
  // ui/src/layout/CanopyModal.jsx
1655
- import React18 from "react";
1752
+ import React19 from "react";
1656
1753
  function CanopyModal(props = {}) {
1657
1754
  const {
1658
1755
  id,
@@ -1703,7 +1800,7 @@ function CanopyModal(props = {}) {
1703
1800
  if (padded) bodyClasses.push("canopy-modal__body--padded");
1704
1801
  if (bodyClassName) bodyClasses.push(bodyClassName);
1705
1802
  const bodyClassNameValue = bodyClasses.join(" ");
1706
- return /* @__PURE__ */ React18.createElement("div", { ...modalProps }, /* @__PURE__ */ React18.createElement("div", { className: "canopy-modal__panel" }, /* @__PURE__ */ React18.createElement("button", { ...closeButtonProps }, /* @__PURE__ */ React18.createElement(
1803
+ return /* @__PURE__ */ React19.createElement("div", { ...modalProps }, /* @__PURE__ */ React19.createElement("div", { className: "canopy-modal__panel" }, /* @__PURE__ */ React19.createElement("button", { ...closeButtonProps }, /* @__PURE__ */ React19.createElement(
1707
1804
  "svg",
1708
1805
  {
1709
1806
  xmlns: "http://www.w3.org/2000/svg",
@@ -1713,8 +1810,8 @@ function CanopyModal(props = {}) {
1713
1810
  strokeWidth: "1.5",
1714
1811
  className: "canopy-modal__close-icon"
1715
1812
  },
1716
- /* @__PURE__ */ React18.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M6 6l12 12M6 18L18 6" })
1717
- ), /* @__PURE__ */ React18.createElement("span", { className: "sr-only" }, closeLabel)), /* @__PURE__ */ React18.createElement("div", { className: bodyClassNameValue }, label ? /* @__PURE__ */ React18.createElement("div", { className: "canopy-modal__brand" }, /* @__PURE__ */ React18.createElement(
1813
+ /* @__PURE__ */ React19.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M6 6l12 12M6 18L18 6" })
1814
+ ), /* @__PURE__ */ React19.createElement("span", { className: "sr-only" }, closeLabel)), /* @__PURE__ */ React19.createElement("div", { className: bodyClassNameValue }, label ? /* @__PURE__ */ React19.createElement("div", { className: "canopy-modal__brand" }, /* @__PURE__ */ React19.createElement(
1718
1815
  CanopyBrand,
1719
1816
  {
1720
1817
  labelId: resolvedLabelId,
@@ -1736,11 +1833,24 @@ function HeaderScript() {
1736
1833
  var body = doc.body;
1737
1834
  var root = doc.documentElement;
1738
1835
 
1836
+ function desktopBreakpointQuery() {
1837
+ if (typeof window === 'undefined') return '(min-width: 70rem)';
1838
+ try {
1839
+ var styles = window.getComputedStyle ? window.getComputedStyle(root) : null;
1840
+ var value = styles ? styles.getPropertyValue('--canopy-desktop-breakpoint') : '';
1841
+ if (typeof value === 'string') value = value.trim();
1842
+ if (!value) value = '70rem';
1843
+ return '(min-width: ' + value + ')';
1844
+ } catch (error) {
1845
+ return '(min-width: 70rem)';
1846
+ }
1847
+ }
1848
+
1739
1849
  function ready(fn) {
1740
1850
  if (doc.readyState === 'loading') {
1741
1851
  doc.addEventListener('DOMContentLoaded', fn, { once: true });
1742
1852
  } else {
1743
- fn();
1853
+ fn();
1744
1854
  }
1745
1855
  }
1746
1856
 
@@ -1750,6 +1860,8 @@ function HeaderScript() {
1750
1860
 
1751
1861
  var NAV_ATTR = 'data-mobile-nav';
1752
1862
  var SEARCH_ATTR = 'data-mobile-search';
1863
+ var NAV_ITEM_ATTR = 'data-canopy-nav-item';
1864
+ var NAV_ITEM_TOGGLE_ATTR = 'data-canopy-nav-item-toggle';
1753
1865
 
1754
1866
  function modalFor(type) {
1755
1867
  return doc.querySelector('[data-canopy-modal="' + type + '"]');
@@ -1831,6 +1943,51 @@ function HeaderScript() {
1831
1943
  });
1832
1944
  }
1833
1945
 
1946
+ function forEachNavTree(scope, fn) {
1947
+ if (typeof fn !== 'function') return;
1948
+ var rootNode = scope || doc;
1949
+ var trees = rootNode.querySelectorAll('[data-canopy-nav-tree]');
1950
+ each(trees, function (tree) {
1951
+ fn(tree);
1952
+ });
1953
+ }
1954
+
1955
+ function resetNavItemToggles(scope) {
1956
+ forEachNavTree(scope, function (tree) {
1957
+ var toggles = tree.querySelectorAll('[' + NAV_ITEM_TOGGLE_ATTR + ']');
1958
+ each(toggles, function (btn) {
1959
+ btn.setAttribute('aria-expanded', 'false');
1960
+ var targetId = btn.getAttribute(NAV_ITEM_TOGGLE_ATTR);
1961
+ var panel = targetId ? doc.getElementById(targetId) : null;
1962
+ var parent = btn.closest('[' + NAV_ITEM_ATTR + ']');
1963
+ if (panel) {
1964
+ panel.hidden = true;
1965
+ panel.setAttribute('aria-hidden', 'true');
1966
+ panel.setAttribute('hidden', '');
1967
+ }
1968
+ if (parent) parent.setAttribute('data-expanded', 'false');
1969
+ });
1970
+ });
1971
+ }
1972
+
1973
+ function applyDefaultNavItemState(scope) {
1974
+ forEachNavTree(scope, function (tree) {
1975
+ var defaults = tree.querySelectorAll('[data-default-expanded="true"]');
1976
+ each(defaults, function (item) {
1977
+ var toggle = item.querySelector('[' + NAV_ITEM_TOGGLE_ATTR + ']');
1978
+ var targetId = toggle ? toggle.getAttribute(NAV_ITEM_TOGGLE_ATTR) : null;
1979
+ var panel = targetId ? doc.getElementById(targetId) : null;
1980
+ item.setAttribute('data-expanded', 'true');
1981
+ if (toggle) toggle.setAttribute('aria-expanded', 'true');
1982
+ if (panel) {
1983
+ panel.hidden = false;
1984
+ panel.removeAttribute('hidden');
1985
+ panel.setAttribute('aria-hidden', 'false');
1986
+ }
1987
+ });
1988
+ });
1989
+ }
1990
+
1834
1991
  function setState(type, next) {
1835
1992
  if (type === 'nav') header.setAttribute(NAV_ATTR, next);
1836
1993
  if (type === 'search') header.setAttribute(SEARCH_ATTR, next);
@@ -1838,6 +1995,13 @@ function HeaderScript() {
1838
1995
  var navOpen = header.getAttribute(NAV_ATTR) === 'open';
1839
1996
  var searchOpen = header.getAttribute(SEARCH_ATTR) === 'open';
1840
1997
  lockScroll(navOpen || searchOpen);
1998
+ if (type === 'nav') {
1999
+ if (next !== 'open') {
2000
+ resetNavItemToggles(modalFor('nav'));
2001
+ } else {
2002
+ applyDefaultNavItemState(modalFor('nav'));
2003
+ }
2004
+ }
1841
2005
  }
1842
2006
 
1843
2007
  function toggle(type, force) {
@@ -1850,6 +2014,35 @@ function HeaderScript() {
1850
2014
  if (type === 'nav' && shouldOpen) focusNavMenu();
1851
2015
  }
1852
2016
 
2017
+ function setupNavItemToggles() {
2018
+ var toggles = doc.querySelectorAll('[' + NAV_ITEM_TOGGLE_ATTR + ']');
2019
+ each(toggles, function (btn) {
2020
+ if (btn.__canopyNavReady) return;
2021
+ btn.__canopyNavReady = true;
2022
+ btn.addEventListener('click', function (event) {
2023
+ event.preventDefault();
2024
+ event.stopPropagation();
2025
+ var targetId = btn.getAttribute(NAV_ITEM_TOGGLE_ATTR);
2026
+ if (!targetId) return;
2027
+ var panel = doc.getElementById(targetId);
2028
+ var parent = btn.closest('[' + NAV_ITEM_ATTR + ']');
2029
+ var expanded = btn.getAttribute('aria-expanded') === 'true';
2030
+ var next = !expanded;
2031
+ btn.setAttribute('aria-expanded', next ? 'true' : 'false');
2032
+ if (panel) {
2033
+ panel.hidden = !next;
2034
+ panel.setAttribute('aria-hidden', next ? 'false' : 'true');
2035
+ if (next) {
2036
+ panel.removeAttribute('hidden');
2037
+ } else {
2038
+ panel.setAttribute('hidden', '');
2039
+ }
2040
+ }
2041
+ if (parent) parent.setAttribute('data-expanded', next ? 'true' : 'false');
2042
+ });
2043
+ });
2044
+ }
2045
+
1853
2046
  each(header.querySelectorAll('[data-canopy-header-toggle]'), function (btn) {
1854
2047
  btn.addEventListener('click', function (event) {
1855
2048
  event.preventDefault();
@@ -1897,7 +2090,7 @@ function HeaderScript() {
1897
2090
  toggle('search', false);
1898
2091
  });
1899
2092
 
1900
- var mq = window.matchMedia('(min-width: 48rem)');
2093
+ var mq = window.matchMedia(desktopBreakpointQuery());
1901
2094
  function syncDesktopState() {
1902
2095
  if (mq.matches) {
1903
2096
  setState('nav', 'closed');
@@ -1914,11 +2107,13 @@ function HeaderScript() {
1914
2107
  mq.addListener(syncDesktopState);
1915
2108
  }
1916
2109
 
2110
+ setupNavItemToggles();
2111
+ applyDefaultNavItemState(null);
1917
2112
  syncDesktopState();
1918
2113
  });
1919
2114
  })();
1920
2115
  `;
1921
- return /* @__PURE__ */ React19.createElement(
2116
+ return /* @__PURE__ */ React20.createElement(
1922
2117
  "script",
1923
2118
  {
1924
2119
  dangerouslySetInnerHTML: {
@@ -1937,7 +2132,7 @@ function getSharedRoot() {
1937
2132
  function getSafePageContext() {
1938
2133
  const root = getSharedRoot();
1939
2134
  if (root && root[CONTEXT_KEY]) return root[CONTEXT_KEY];
1940
- const ctx = React19.createContext({ navigation: null, page: null });
2135
+ const ctx = React20.createContext({ navigation: null, page: null });
1941
2136
  if (root) root[CONTEXT_KEY] = ctx;
1942
2137
  return ctx;
1943
2138
  }
@@ -1947,29 +2142,55 @@ function ensureArray(navLinks) {
1947
2142
  (link) => link && typeof link === "object" && typeof link.href === "string"
1948
2143
  );
1949
2144
  }
1950
- function SectionNavList({ root }) {
1951
- if (!root || !Array.isArray(root.children) || !root.children.length) return null;
1952
- return /* @__PURE__ */ React19.createElement("ul", { className: "canopy-modal__section-list", role: "list" }, root.children.map((node) => /* @__PURE__ */ React19.createElement(SectionNavItem, { key: node.slug || node.href || node.title, node, depth: 0 })));
1953
- }
1954
- function SectionNavItem({ node, depth }) {
1955
- if (!node) return null;
1956
- const hasChildren = Array.isArray(node.children) && node.children.length > 0;
1957
- const Tag = node.href ? "a" : "span";
1958
- const classes = [
1959
- "canopy-modal__section-link",
1960
- `depth-${Math.min(5, Math.max(0, depth + 1))}`
1961
- ];
1962
- if (!node.href) classes.push("is-label");
1963
- if (node.isActive) classes.push("is-active");
1964
- return /* @__PURE__ */ React19.createElement("li", { className: "canopy-modal__section-item", "data-depth": depth }, /* @__PURE__ */ React19.createElement(
1965
- Tag,
1966
- {
1967
- className: classes.join(" "),
1968
- href: node.href || void 0,
1969
- "aria-current": node.isActive ? "page" : void 0
1970
- },
1971
- node.title || node.slug
1972
- ), hasChildren ? /* @__PURE__ */ React19.createElement("ul", { className: "canopy-modal__section-list canopy-modal__section-list--nested", role: "list" }, node.children.map((child) => /* @__PURE__ */ React19.createElement(SectionNavItem, { key: child.slug || child.href || child.title, node: child, depth: depth + 1 }))) : null);
2145
+ function normalizeHref(href) {
2146
+ if (typeof href !== "string") return "";
2147
+ let next = href.trim();
2148
+ if (!next) return "";
2149
+ try {
2150
+ const parsed = new URL(next, "https://canopy.local");
2151
+ next = parsed.pathname || "/";
2152
+ } catch (_) {
2153
+ next = next.replace(/[?#].*$/, "");
2154
+ }
2155
+ next = next.replace(/[?#].*$/, "");
2156
+ if (next.length > 1) {
2157
+ next = next.replace(/\/+$/, "");
2158
+ }
2159
+ if (!next) return "/";
2160
+ return next;
2161
+ }
2162
+ function doesLinkMatchSection(linkHref, sectionNavigation) {
2163
+ if (!sectionNavigation || !sectionNavigation.root || !linkHref) return false;
2164
+ const normalizedLink = normalizeHref(linkHref);
2165
+ if (!normalizedLink) return false;
2166
+ const root = sectionNavigation.root;
2167
+ if (typeof root.href === "string" && normalizeHref(root.href) === normalizedLink) {
2168
+ return true;
2169
+ }
2170
+ if (root.slug) {
2171
+ const slugPath = normalizeHref(`/${root.slug}`);
2172
+ if (slugPath && normalizedLink === slugPath) {
2173
+ return true;
2174
+ }
2175
+ }
2176
+ return false;
2177
+ }
2178
+ function rootSegmentFromHref(href) {
2179
+ const normalized = normalizeHref(href);
2180
+ if (!normalized || normalized === "/") return "";
2181
+ const trimmed = normalized.replace(/^\/+/, "");
2182
+ return trimmed.split("/")[0] || "";
2183
+ }
2184
+ function getLinkNavigationData(link, navigationRoots, sectionNavigation) {
2185
+ if (!link || typeof link.href !== "string") return null;
2186
+ const segment = rootSegmentFromHref(link.href);
2187
+ if (navigationRoots && segment && navigationRoots[segment]) {
2188
+ return navigationRoots[segment];
2189
+ }
2190
+ if (sectionNavigation && doesLinkMatchSection(link.href, sectionNavigation)) {
2191
+ return sectionNavigation;
2192
+ }
2193
+ return null;
1973
2194
  }
1974
2195
  function CanopyHeader(props = {}) {
1975
2196
  const {
@@ -1983,20 +2204,34 @@ function CanopyHeader(props = {}) {
1983
2204
  } = props;
1984
2205
  const navLinks = ensureArray(navLinksProp);
1985
2206
  const PageContext = getSafePageContext();
1986
- const context = React19.useContext(PageContext);
1987
- const sectionNavigation = context && context.navigation && context.navigation.root ? context.navigation : null;
2207
+ const context = React20.useContext(PageContext);
2208
+ const contextNavigation = context && context.navigation ? context.navigation : null;
2209
+ const sectionNavigation = contextNavigation && contextNavigation.root ? contextNavigation : null;
2210
+ const navigationRoots = contextNavigation && contextNavigation.allRoots ? contextNavigation.allRoots : null;
1988
2211
  const sectionHeading = sectionNavigation && sectionNavigation.title || (sectionNavigation && sectionNavigation.root ? sectionNavigation.root.title : "");
1989
2212
  const hasSectionNav = !!(sectionNavigation && sectionNavigation.root && Array.isArray(sectionNavigation.root.children) && sectionNavigation.root.children.length);
1990
2213
  const sectionLabel = sectionHeading ? `More in ${sectionHeading}` : "More in this section";
1991
2214
  const sectionAriaLabel = sectionHeading ? `${sectionHeading} section navigation` : "Section navigation";
1992
- return /* @__PURE__ */ React19.createElement(React19.Fragment, null, /* @__PURE__ */ React19.createElement(
2215
+ const defaultSectionLabel = sectionLabel;
2216
+ const defaultSectionAriaLabel = sectionAriaLabel;
2217
+ const shouldAttachSectionNav = (link) => {
2218
+ const navData = getLinkNavigationData(
2219
+ link,
2220
+ navigationRoots,
2221
+ sectionNavigation
2222
+ );
2223
+ const rootNode = navData && navData.root;
2224
+ return !!(rootNode && Array.isArray(rootNode.children) && rootNode.children.length);
2225
+ };
2226
+ const hasIntegratedSectionNav = navLinks.some(shouldAttachSectionNav);
2227
+ return /* @__PURE__ */ React20.createElement(React20.Fragment, null, /* @__PURE__ */ React20.createElement(
1993
2228
  "header",
1994
2229
  {
1995
2230
  className: "canopy-header",
1996
2231
  "data-mobile-nav": "closed",
1997
2232
  "data-mobile-search": "closed"
1998
2233
  },
1999
- /* @__PURE__ */ React19.createElement("div", { className: "canopy-header__brand" }, /* @__PURE__ */ React19.createElement(
2234
+ /* @__PURE__ */ React20.createElement("div", { className: "canopy-header__brand" }, /* @__PURE__ */ React20.createElement(
2000
2235
  CanopyBrand,
2001
2236
  {
2002
2237
  label: title,
@@ -2005,7 +2240,7 @@ function CanopyHeader(props = {}) {
2005
2240
  Logo: SiteLogo
2006
2241
  }
2007
2242
  )),
2008
- /* @__PURE__ */ React19.createElement("div", { className: "canopy-header__desktop-search" }, /* @__PURE__ */ React19.createElement(
2243
+ /* @__PURE__ */ React20.createElement("div", { className: "canopy-header__desktop-search" }, /* @__PURE__ */ React20.createElement(
2009
2244
  SearchPanel,
2010
2245
  {
2011
2246
  label: searchLabel,
@@ -2013,13 +2248,13 @@ function CanopyHeader(props = {}) {
2013
2248
  placeholder: searchPlaceholder
2014
2249
  }
2015
2250
  )),
2016
- /* @__PURE__ */ React19.createElement(
2251
+ /* @__PURE__ */ React20.createElement(
2017
2252
  "nav",
2018
2253
  {
2019
2254
  className: "canopy-nav-links canopy-header__desktop-nav",
2020
2255
  "aria-label": "Primary navigation"
2021
2256
  },
2022
- navLinks.map((link) => /* @__PURE__ */ React19.createElement(
2257
+ navLinks.map((link) => /* @__PURE__ */ React20.createElement(
2023
2258
  "a",
2024
2259
  {
2025
2260
  key: link.href,
@@ -2029,7 +2264,7 @@ function CanopyHeader(props = {}) {
2029
2264
  link.label || link.href
2030
2265
  ))
2031
2266
  ),
2032
- /* @__PURE__ */ React19.createElement("div", { className: "canopy-header__actions" }, /* @__PURE__ */ React19.createElement(
2267
+ /* @__PURE__ */ React20.createElement("div", { className: "canopy-header__actions" }, /* @__PURE__ */ React20.createElement(
2033
2268
  "button",
2034
2269
  {
2035
2270
  type: "button",
@@ -2039,7 +2274,7 @@ function CanopyHeader(props = {}) {
2039
2274
  "aria-expanded": "false",
2040
2275
  "data-canopy-header-toggle": "search"
2041
2276
  },
2042
- /* @__PURE__ */ React19.createElement(
2277
+ /* @__PURE__ */ React20.createElement(
2043
2278
  "svg",
2044
2279
  {
2045
2280
  xmlns: "http://www.w3.org/2000/svg",
@@ -2049,7 +2284,7 @@ function CanopyHeader(props = {}) {
2049
2284
  strokeWidth: "1.5",
2050
2285
  className: "canopy-header__search-icon"
2051
2286
  },
2052
- /* @__PURE__ */ React19.createElement(
2287
+ /* @__PURE__ */ React20.createElement(
2053
2288
  "path",
2054
2289
  {
2055
2290
  strokeLinecap: "round",
@@ -2058,7 +2293,7 @@ function CanopyHeader(props = {}) {
2058
2293
  }
2059
2294
  )
2060
2295
  )
2061
- ), /* @__PURE__ */ React19.createElement(
2296
+ ), /* @__PURE__ */ React20.createElement(
2062
2297
  "button",
2063
2298
  {
2064
2299
  type: "button",
@@ -2068,7 +2303,7 @@ function CanopyHeader(props = {}) {
2068
2303
  "aria-expanded": "false",
2069
2304
  "data-canopy-header-toggle": "nav"
2070
2305
  },
2071
- /* @__PURE__ */ React19.createElement(
2306
+ /* @__PURE__ */ React20.createElement(
2072
2307
  "svg",
2073
2308
  {
2074
2309
  xmlns: "http://www.w3.org/2000/svg",
@@ -2078,7 +2313,7 @@ function CanopyHeader(props = {}) {
2078
2313
  stroke: "currentColor",
2079
2314
  className: "canopy-header__menu-icon"
2080
2315
  },
2081
- /* @__PURE__ */ React19.createElement(
2316
+ /* @__PURE__ */ React20.createElement(
2082
2317
  "path",
2083
2318
  {
2084
2319
  strokeLinecap: "round",
@@ -2088,7 +2323,7 @@ function CanopyHeader(props = {}) {
2088
2323
  )
2089
2324
  )
2090
2325
  ))
2091
- ), /* @__PURE__ */ React19.createElement(
2326
+ ), /* @__PURE__ */ React20.createElement(
2092
2327
  CanopyModal,
2093
2328
  {
2094
2329
  id: "canopy-modal-nav",
@@ -2100,32 +2335,90 @@ function CanopyHeader(props = {}) {
2100
2335
  closeLabel: "Close navigation",
2101
2336
  closeDataAttr: "nav"
2102
2337
  },
2103
- /* @__PURE__ */ React19.createElement(
2338
+ /* @__PURE__ */ React20.createElement(
2104
2339
  "nav",
2105
2340
  {
2106
2341
  className: "canopy-nav-links canopy-modal__nav",
2107
2342
  "aria-label": "Primary navigation"
2108
2343
  },
2109
- navLinks.map((link) => /* @__PURE__ */ React19.createElement(
2110
- "a",
2111
- {
2112
- key: link.href,
2113
- href: link.href,
2114
- "aria-current": link.isActive ? "page" : void 0
2115
- },
2116
- link.label || link.href
2117
- ))
2344
+ /* @__PURE__ */ React20.createElement("ul", { className: "canopy-modal__nav-list", role: "list" }, navLinks.map((link, index) => {
2345
+ const navData = getLinkNavigationData(
2346
+ link,
2347
+ navigationRoots,
2348
+ sectionNavigation
2349
+ );
2350
+ const navRoot = navData && navData.root ? navData.root : null;
2351
+ const hasChildren = !!(navRoot && Array.isArray(navRoot.children) && navRoot.children.length);
2352
+ const nestedId = hasChildren ? `canopy-modal-section-${index}` : null;
2353
+ const toggleLabel = link.label ? `Toggle ${link.label} menu` : "Toggle section menu";
2354
+ const defaultExpanded = hasChildren && !!navRoot.isExpanded;
2355
+ return /* @__PURE__ */ React20.createElement(
2356
+ "li",
2357
+ {
2358
+ className: "canopy-modal__nav-item",
2359
+ key: link.href,
2360
+ "data-canopy-nav-item": hasChildren ? "true" : void 0,
2361
+ "data-expanded": defaultExpanded ? "true" : "false",
2362
+ "data-default-expanded": defaultExpanded ? "true" : void 0
2363
+ },
2364
+ /* @__PURE__ */ React20.createElement("div", { className: "canopy-modal__nav-row" }, /* @__PURE__ */ React20.createElement("a", { href: link.href }, link.label || link.href), hasChildren ? /* @__PURE__ */ React20.createElement(
2365
+ "button",
2366
+ {
2367
+ type: "button",
2368
+ className: "canopy-modal__nav-toggle",
2369
+ "aria-expanded": defaultExpanded ? "true" : "false",
2370
+ "aria-controls": nestedId || void 0,
2371
+ "aria-label": toggleLabel,
2372
+ "data-canopy-nav-item-toggle": nestedId || void 0
2373
+ },
2374
+ /* @__PURE__ */ React20.createElement(
2375
+ "svg",
2376
+ {
2377
+ xmlns: "http://www.w3.org/2000/svg",
2378
+ viewBox: "0 0 24 24",
2379
+ fill: "none",
2380
+ stroke: "currentColor",
2381
+ strokeWidth: "1.5",
2382
+ className: "canopy-modal__nav-toggle-icon"
2383
+ },
2384
+ /* @__PURE__ */ React20.createElement(
2385
+ "path",
2386
+ {
2387
+ strokeLinecap: "round",
2388
+ strokeLinejoin: "round",
2389
+ d: "M5 9l7 7 7-7"
2390
+ }
2391
+ )
2392
+ ),
2393
+ /* @__PURE__ */ React20.createElement("span", { className: "sr-only" }, toggleLabel)
2394
+ ) : null),
2395
+ hasChildren ? /* @__PURE__ */ React20.createElement(
2396
+ NavigationTree,
2397
+ {
2398
+ root: navRoot,
2399
+ parentKey: navData && navData.rootSegment ? navData.rootSegment : `root-${index}`,
2400
+ component: "div",
2401
+ className: "canopy-modal__section-nav canopy-modal__section-nav--nested",
2402
+ "aria-label": navData && navData.title ? `${navData.title} section navigation` : defaultSectionAriaLabel,
2403
+ "aria-hidden": defaultExpanded ? "false" : "true",
2404
+ hidden: !defaultExpanded,
2405
+ id: nestedId || void 0
2406
+ }
2407
+ ) : null
2408
+ );
2409
+ }))
2118
2410
  ),
2119
- hasSectionNav ? /* @__PURE__ */ React19.createElement(
2120
- "nav",
2411
+ hasSectionNav && !hasIntegratedSectionNav ? /* @__PURE__ */ React20.createElement(
2412
+ NavigationTree,
2121
2413
  {
2414
+ root: sectionNavigation.root,
2415
+ component: "nav",
2122
2416
  className: "canopy-modal__section-nav",
2123
- "aria-label": sectionAriaLabel
2124
- },
2125
- /* @__PURE__ */ React19.createElement("div", { className: "canopy-modal__section-label" }, sectionLabel),
2126
- /* @__PURE__ */ React19.createElement(SectionNavList, { root: sectionNavigation.root })
2417
+ "aria-label": defaultSectionAriaLabel,
2418
+ parentKey: "fallback-nav"
2419
+ }
2127
2420
  ) : null
2128
- ), /* @__PURE__ */ React19.createElement(
2421
+ ), /* @__PURE__ */ React20.createElement(
2129
2422
  CanopyModal,
2130
2423
  {
2131
2424
  id: "canopy-modal-search",
@@ -2138,7 +2431,7 @@ function CanopyHeader(props = {}) {
2138
2431
  closeDataAttr: "search",
2139
2432
  bodyClassName: "canopy-modal__body--search"
2140
2433
  },
2141
- /* @__PURE__ */ React19.createElement(
2434
+ /* @__PURE__ */ React20.createElement(
2142
2435
  SearchPanel,
2143
2436
  {
2144
2437
  label: searchLabel,
@@ -2146,18 +2439,18 @@ function CanopyHeader(props = {}) {
2146
2439
  placeholder: searchPlaceholder
2147
2440
  }
2148
2441
  )
2149
- ), /* @__PURE__ */ React19.createElement(HeaderScript, null));
2442
+ ), /* @__PURE__ */ React20.createElement(HeaderScript, null));
2150
2443
  }
2151
2444
 
2152
2445
  // ui/src/layout/CanopyFooter.jsx
2153
- import React20 from "react";
2446
+ import React21 from "react";
2154
2447
  function CanopyFooter({ className = "", children }) {
2155
2448
  const footerClassName = ["canopy-footer", className].filter(Boolean).join(" ");
2156
- return /* @__PURE__ */ React20.createElement("footer", { className: footerClassName }, /* @__PURE__ */ React20.createElement("div", { className: "canopy-footer__inner" }, children));
2449
+ return /* @__PURE__ */ React21.createElement("footer", { className: footerClassName }, /* @__PURE__ */ React21.createElement("div", { className: "canopy-footer__inner" }, children));
2157
2450
  }
2158
2451
 
2159
2452
  // ui/src/layout/TeaserCard.jsx
2160
- import React21 from "react";
2453
+ import React22 from "react";
2161
2454
  function TeaserCard({
2162
2455
  href = "",
2163
2456
  title = "",
@@ -2178,7 +2471,7 @@ function TeaserCard({
2178
2471
  ].filter(Boolean).join(" ");
2179
2472
  const showThumb = type === "work" && thumbnail;
2180
2473
  const metaLine = (Array.isArray(metadata) && metadata.length ? metadata.filter(Boolean) : summary ? [summary] : []).filter(Boolean).slice(0, 2).join(" \u2022 ");
2181
- return /* @__PURE__ */ React21.createElement(
2474
+ return /* @__PURE__ */ React22.createElement(
2182
2475
  Tag,
2183
2476
  {
2184
2477
  className: classes,
@@ -2186,7 +2479,7 @@ function TeaserCard({
2186
2479
  "data-canopy-item": href ? "" : void 0,
2187
2480
  ...rest
2188
2481
  },
2189
- showThumb ? /* @__PURE__ */ React21.createElement("div", { className: "canopy-search-teaser__thumb" }, /* @__PURE__ */ React21.createElement(
2482
+ showThumb ? /* @__PURE__ */ React22.createElement("div", { className: "canopy-search-teaser__thumb" }, /* @__PURE__ */ React22.createElement(
2190
2483
  "img",
2191
2484
  {
2192
2485
  src: thumbnail,
@@ -2196,12 +2489,12 @@ function TeaserCard({
2196
2489
  className: "canopy-search-teaser__thumb-img"
2197
2490
  }
2198
2491
  )) : null,
2199
- /* @__PURE__ */ React21.createElement("div", { className: "canopy-search-teaser__text" }, /* @__PURE__ */ React21.createElement("span", { className: "canopy-search-teaser__title" }, title || href || "Untitled"), metaLine ? /* @__PURE__ */ React21.createElement("span", { className: "canopy-search-teaser__meta" }, metaLine) : null)
2492
+ /* @__PURE__ */ React22.createElement("div", { className: "canopy-search-teaser__text" }, /* @__PURE__ */ React22.createElement("span", { className: "canopy-search-teaser__title" }, title || href || "Untitled"), metaLine ? /* @__PURE__ */ React22.createElement("span", { className: "canopy-search-teaser__meta" }, metaLine) : null)
2200
2493
  );
2201
2494
  }
2202
2495
 
2203
2496
  // ui/src/layout/GoogleAnalytics.jsx
2204
- import React22 from "react";
2497
+ import React23 from "react";
2205
2498
  var GA_HOST = "https://www.googletagmanager.com/gtag/js";
2206
2499
  function GoogleAnalytics({ id }) {
2207
2500
  if (!id) return null;
@@ -2211,11 +2504,11 @@ function GoogleAnalytics({ id }) {
2211
2504
  gtag('js', new Date());
2212
2505
  gtag('config', '${id}');
2213
2506
  `;
2214
- return /* @__PURE__ */ React22.createElement(React22.Fragment, null, /* @__PURE__ */ React22.createElement("script", { async: true, src: `${GA_HOST}?id=${encodeURIComponent(id)}` }), /* @__PURE__ */ React22.createElement("script", { dangerouslySetInnerHTML: { __html: inlineConfig } }));
2507
+ return /* @__PURE__ */ React23.createElement(React23.Fragment, null, /* @__PURE__ */ React23.createElement("script", { async: true, src: `${GA_HOST}?id=${encodeURIComponent(id)}` }), /* @__PURE__ */ React23.createElement("script", { dangerouslySetInnerHTML: { __html: inlineConfig } }));
2215
2508
  }
2216
2509
 
2217
2510
  // ui/src/layout/Container.jsx
2218
- import React23 from "react";
2511
+ import React24 from "react";
2219
2512
  function Container({
2220
2513
  className = "",
2221
2514
  variant = "content",
@@ -2224,7 +2517,7 @@ function Container({
2224
2517
  }) {
2225
2518
  const variantClass = variant === "wide" ? "max-w-wide" : "max-w-content";
2226
2519
  const classes = ["mx-auto", variantClass, "w-full", className].filter(Boolean).join(" ");
2227
- return /* @__PURE__ */ React23.createElement(
2520
+ return /* @__PURE__ */ React24.createElement(
2228
2521
  "div",
2229
2522
  {
2230
2523
  className: classes,
@@ -2236,7 +2529,7 @@ function Container({
2236
2529
  }
2237
2530
 
2238
2531
  // ui/src/layout/Card.jsx
2239
- import React24, { useEffect as useEffect5, useRef, useState as useState5 } from "react";
2532
+ import React25, { useEffect as useEffect5, useRef, useState as useState5 } from "react";
2240
2533
  var DEFAULT_CARD_ASPECT_RATIO = 4 / 3;
2241
2534
  function Card({
2242
2535
  href,
@@ -2300,8 +2593,8 @@ function Card({
2300
2593
  const hasDimensions = Number.isFinite(w) && w > 0 && Number.isFinite(h) && h > 0;
2301
2594
  const ratio = hasAspectRatio ? Number(aspectRatio) : hasDimensions ? w / h : src ? DEFAULT_CARD_ASPECT_RATIO : void 0;
2302
2595
  const paddingPercent = ratio ? 100 / ratio : 100;
2303
- const caption = /* @__PURE__ */ React24.createElement("figcaption", null, title && /* @__PURE__ */ React24.createElement("span", null, title), subtitle && /* @__PURE__ */ React24.createElement("span", null, subtitle), children);
2304
- return /* @__PURE__ */ React24.createElement(
2596
+ const caption = /* @__PURE__ */ React25.createElement("figcaption", null, title && /* @__PURE__ */ React25.createElement("span", null, title), subtitle && /* @__PURE__ */ React25.createElement("span", null, subtitle), children);
2597
+ return /* @__PURE__ */ React25.createElement(
2305
2598
  "a",
2306
2599
  {
2307
2600
  href,
@@ -2313,13 +2606,13 @@ function Card({
2313
2606
  "data-image-loaded": imageLoaded ? "true" : "false",
2314
2607
  ...rest
2315
2608
  },
2316
- /* @__PURE__ */ React24.createElement("figure", null, src ? ratio ? /* @__PURE__ */ React24.createElement(
2609
+ /* @__PURE__ */ React25.createElement("figure", null, src ? ratio ? /* @__PURE__ */ React25.createElement(
2317
2610
  "div",
2318
2611
  {
2319
2612
  className: "canopy-card-media",
2320
2613
  style: { "--canopy-card-padding": `${paddingPercent}%` }
2321
2614
  },
2322
- inView ? /* @__PURE__ */ React24.createElement(
2615
+ inView ? /* @__PURE__ */ React25.createElement(
2323
2616
  "img",
2324
2617
  {
2325
2618
  src,
@@ -2330,7 +2623,7 @@ function Card({
2330
2623
  onError: () => setImageLoaded(true)
2331
2624
  }
2332
2625
  ) : null
2333
- ) : /* @__PURE__ */ React24.createElement(
2626
+ ) : /* @__PURE__ */ React25.createElement(
2334
2627
  "img",
2335
2628
  {
2336
2629
  src,
@@ -2346,13 +2639,13 @@ function Card({
2346
2639
  }
2347
2640
 
2348
2641
  // ui/src/content/ReferencedItems.jsx
2349
- import React25 from "react";
2642
+ import React26 from "react";
2350
2643
  import navigationHelpers4 from "@canopy-iiif/app/lib/components/navigation.js";
2351
2644
  function useReferencedItems(itemsProp) {
2352
2645
  if (Array.isArray(itemsProp)) return itemsProp;
2353
2646
  const PageContext = navigationHelpers4 && typeof navigationHelpers4.getPageContext === "function" ? navigationHelpers4.getPageContext() : null;
2354
2647
  if (!PageContext) return [];
2355
- const context = React25.useContext(PageContext);
2648
+ const context = React26.useContext(PageContext);
2356
2649
  const items = context && context.page ? context.page.referencedItems : null;
2357
2650
  return Array.isArray(items) ? items : [];
2358
2651
  }
@@ -2372,13 +2665,13 @@ function ReferencedItems({
2372
2665
  "referenced-items--empty",
2373
2666
  className
2374
2667
  ].filter(Boolean).join(" ");
2375
- return /* @__PURE__ */ React25.createElement("div", { className: emptyClass, ...rest }, typeof emptyLabel === "function" ? emptyLabel() : emptyLabel);
2668
+ return /* @__PURE__ */ React26.createElement("div", { className: emptyClass, ...rest }, typeof emptyLabel === "function" ? emptyLabel() : emptyLabel);
2376
2669
  }
2377
2670
  const containerClassName = ["referenced-items", className].filter(Boolean).join(" ");
2378
- return /* @__PURE__ */ React25.createElement("section", { className: containerClassName, ...rest }, children, /* @__PURE__ */ React25.createElement("div", { className: "referenced-items__grid", role: "list" }, items.map((item) => {
2671
+ return /* @__PURE__ */ React26.createElement("section", { className: containerClassName, ...rest }, children, /* @__PURE__ */ React26.createElement("div", { className: "referenced-items__grid", role: "list" }, items.map((item) => {
2379
2672
  if (!item) return null;
2380
2673
  const key = item.href || item.slug || item.id;
2381
- return /* @__PURE__ */ React25.createElement("div", { className: "referenced-items__item", role: "listitem", key }, /* @__PURE__ */ React25.createElement(
2674
+ return /* @__PURE__ */ React26.createElement("div", { className: "referenced-items__item", role: "listitem", key }, /* @__PURE__ */ React26.createElement(
2382
2675
  Card,
2383
2676
  {
2384
2677
  href: item.href,
@@ -2395,7 +2688,7 @@ function ReferencedItems({
2395
2688
  }
2396
2689
 
2397
2690
  // ui/src/content/References.jsx
2398
- import React26 from "react";
2691
+ import React27 from "react";
2399
2692
  import navigationHelpers5 from "@canopy-iiif/app/lib/components/navigation.js";
2400
2693
  import referenced from "@canopy-iiif/app/lib/components/referenced.js";
2401
2694
  function getPageContext() {
@@ -2426,7 +2719,7 @@ function References({
2426
2719
  ...rest
2427
2720
  }) {
2428
2721
  const PageContext = getPageContext();
2429
- const context = PageContext ? React26.useContext(PageContext) : null;
2722
+ const context = PageContext ? React27.useContext(PageContext) : null;
2430
2723
  const contextPage = context && context.page ? context.page : null;
2431
2724
  const manifestId = id || contextPage && contextPage.manifestId || "";
2432
2725
  const contextReferences = !id && contextPage && Array.isArray(contextPage.referencedBy) ? contextPage.referencedBy : null;
@@ -2435,11 +2728,11 @@ function References({
2435
2728
  const entries = references && references.length ? references : null;
2436
2729
  if (!entries || !entries.length) return null;
2437
2730
  const containerClass = ["references", className].filter(Boolean).join(" ");
2438
- return /* @__PURE__ */ React26.createElement("dl", { className: containerClass, ...rest }, /* @__PURE__ */ React26.createElement("div", { className: "references__group" }, /* @__PURE__ */ React26.createElement("dt", null, title), entries.map((entry) => /* @__PURE__ */ React26.createElement("dd", { key: entry.href, className: "references__item" }, /* @__PURE__ */ React26.createElement("a", { href: entry.href }, entry.title || entry.href)))));
2731
+ return /* @__PURE__ */ React27.createElement("dl", { className: containerClass, ...rest }, /* @__PURE__ */ React27.createElement("div", { className: "references__group" }, /* @__PURE__ */ React27.createElement("dt", null, title), entries.map((entry) => /* @__PURE__ */ React27.createElement("dd", { key: entry.href, className: "references__item" }, /* @__PURE__ */ React27.createElement("a", { href: entry.href }, entry.title || entry.href)))));
2439
2732
  }
2440
2733
 
2441
2734
  // ui/src/content/Bibliography.jsx
2442
- import React27 from "react";
2735
+ import React28 from "react";
2443
2736
  import bibliography from "@canopy-iiif/app/lib/components/bibliography.js";
2444
2737
  function resolveHeadingTag(tag, fallback) {
2445
2738
  if (typeof tag === "string" && tag.trim()) return tag;
@@ -2449,7 +2742,7 @@ function resolveHeadingTag(tag, fallback) {
2449
2742
  function NoteBody({ note }) {
2450
2743
  if (!note) return null;
2451
2744
  if (note.html) {
2452
- return /* @__PURE__ */ React27.createElement(
2745
+ return /* @__PURE__ */ React28.createElement(
2453
2746
  "div",
2454
2747
  {
2455
2748
  className: "bibliography__note-body",
@@ -2458,7 +2751,7 @@ function NoteBody({ note }) {
2458
2751
  );
2459
2752
  }
2460
2753
  if (note.text) {
2461
- return /* @__PURE__ */ React27.createElement("div", { className: "bibliography__note-body" }, note.text);
2754
+ return /* @__PURE__ */ React28.createElement("div", { className: "bibliography__note-body" }, note.text);
2462
2755
  }
2463
2756
  return null;
2464
2757
  }
@@ -2476,22 +2769,22 @@ function Bibliography({
2476
2769
  if (!entries.length) return null;
2477
2770
  const PageHeadingTag = resolveHeadingTag(pageHeadingTag, "h3");
2478
2771
  const rootClass = ["bibliography", className].filter(Boolean).join(" ");
2479
- return /* @__PURE__ */ React27.createElement("section", { className: rootClass }, /* @__PURE__ */ React27.createElement("div", { className: "bibliography__pages" }, entries.map((entry) => {
2772
+ return /* @__PURE__ */ React28.createElement("section", { className: rootClass }, /* @__PURE__ */ React28.createElement("div", { className: "bibliography__pages" }, entries.map((entry) => {
2480
2773
  const key = entry.href || entry.relativePath || entry.title;
2481
2774
  const pageTitle = entry.title || entry.href;
2482
- return /* @__PURE__ */ React27.createElement("article", { key, className: "bibliography__page" }, /* @__PURE__ */ React27.createElement("header", { className: "bibliography__page-header" }, pageTitle ? /* @__PURE__ */ React27.createElement(PageHeadingTag, { className: "bibliography__page-title" }, pageTitle) : null, entry.href ? /* @__PURE__ */ React27.createElement("a", { className: "bibliography__page-link", href: entry.href }, entry.href) : null), /* @__PURE__ */ React27.createElement("ol", { className: "bibliography__notes" }, (entry.footnotes || []).map((note, idx) => {
2775
+ return /* @__PURE__ */ React28.createElement("article", { key, className: "bibliography__page" }, /* @__PURE__ */ React28.createElement("header", { className: "bibliography__page-header" }, pageTitle ? /* @__PURE__ */ React28.createElement(PageHeadingTag, { className: "bibliography__page-title" }, pageTitle) : null, entry.href ? /* @__PURE__ */ React28.createElement("a", { className: "bibliography__page-link", href: entry.href }, entry.href) : null), /* @__PURE__ */ React28.createElement("ol", { className: "bibliography__notes" }, (entry.footnotes || []).map((note, idx) => {
2483
2776
  const noteKey = `${key || "entry"}-${note.identifier || idx}`;
2484
- return /* @__PURE__ */ React27.createElement("li", { key: noteKey, className: "bibliography__note" }, note.identifier ? /* @__PURE__ */ React27.createElement("span", { className: "bibliography__note-label" }, note.identifier) : null, /* @__PURE__ */ React27.createElement(NoteBody, { note }));
2777
+ return /* @__PURE__ */ React28.createElement("li", { key: noteKey, className: "bibliography__note" }, note.identifier ? /* @__PURE__ */ React28.createElement("span", { className: "bibliography__note-label" }, note.identifier) : null, /* @__PURE__ */ React28.createElement(NoteBody, { note }));
2485
2778
  })));
2486
2779
  })));
2487
2780
  }
2488
2781
 
2489
2782
  // ui/src/content/timeline/MdxTimeline.jsx
2490
- import React31 from "react";
2783
+ import React32 from "react";
2491
2784
  import ReactDOMServer from "react-dom/server";
2492
2785
 
2493
2786
  // ui/src/content/timeline/Timeline.jsx
2494
- import React29 from "react";
2787
+ import React30 from "react";
2495
2788
 
2496
2789
  // ui/src/content/timeline/date-utils.js
2497
2790
  var FALLBACK_LOCALE = (() => {
@@ -2652,7 +2945,7 @@ function clampProgress(value) {
2652
2945
  }
2653
2946
 
2654
2947
  // ui/src/layout/ReferencedManifestCard.jsx
2655
- import React28 from "react";
2948
+ import React29 from "react";
2656
2949
  function normalizeMetadata(metadata, summary) {
2657
2950
  if (Array.isArray(metadata) && metadata.length) {
2658
2951
  return metadata.filter(Boolean);
@@ -2686,7 +2979,7 @@ function ReferencedManifestCard({
2686
2979
  "canopy-referenced-manifest-card",
2687
2980
  className
2688
2981
  ].filter(Boolean).join(" ");
2689
- return /* @__PURE__ */ React28.createElement(
2982
+ return /* @__PURE__ */ React29.createElement(
2690
2983
  TeaserCard,
2691
2984
  {
2692
2985
  href: resolvedHref || void 0,
@@ -2868,14 +3161,14 @@ function TimelineConnector({ side, isActive, highlight }) {
2868
3161
  "canopy-timeline__connector-dot",
2869
3162
  highlight || isActive ? "is-active" : ""
2870
3163
  ].filter(Boolean).join(" ");
2871
- return /* @__PURE__ */ React29.createElement("span", { className: connectorClasses, "aria-hidden": "true" }, side === "left" ? /* @__PURE__ */ React29.createElement(React29.Fragment, null, /* @__PURE__ */ React29.createElement("span", { className: "canopy-timeline__connector-line" }), /* @__PURE__ */ React29.createElement("span", { className: dotClasses })) : /* @__PURE__ */ React29.createElement(React29.Fragment, null, /* @__PURE__ */ React29.createElement("span", { className: dotClasses }), /* @__PURE__ */ React29.createElement("span", { className: "canopy-timeline__connector-line" })));
3164
+ return /* @__PURE__ */ React30.createElement("span", { className: connectorClasses, "aria-hidden": "true" }, side === "left" ? /* @__PURE__ */ React30.createElement(React30.Fragment, null, /* @__PURE__ */ React30.createElement("span", { className: "canopy-timeline__connector-line" }), /* @__PURE__ */ React30.createElement("span", { className: dotClasses })) : /* @__PURE__ */ React30.createElement(React30.Fragment, null, /* @__PURE__ */ React30.createElement("span", { className: dotClasses }), /* @__PURE__ */ React30.createElement("span", { className: "canopy-timeline__connector-line" })));
2872
3165
  }
2873
3166
  function renderResourceSection(point) {
2874
3167
  if (!point) return null;
2875
3168
  const manifestCards = Array.isArray(point.manifests) ? point.manifests.filter(Boolean) : [];
2876
3169
  const legacyResources = Array.isArray(point.resources) ? point.resources.filter(Boolean) : [];
2877
3170
  if (!manifestCards.length && !legacyResources.length) return null;
2878
- return /* @__PURE__ */ React29.createElement("div", { className: "canopy-timeline__resources" }, /* @__PURE__ */ React29.createElement("div", { className: "canopy-timeline__resources-list" }, manifestCards.map((manifest) => /* @__PURE__ */ React29.createElement("div", { key: manifest.id || manifest.href }, /* @__PURE__ */ React29.createElement(ReferencedManifestCard, { manifest }))), legacyResources.map((resource, idx) => /* @__PURE__ */ React29.createElement("div", { key: resource.id || resource.href || `legacy-${idx}` }, /* @__PURE__ */ React29.createElement(
3171
+ return /* @__PURE__ */ React30.createElement("div", { className: "canopy-timeline__resources" }, /* @__PURE__ */ React30.createElement("div", { className: "canopy-timeline__resources-list" }, manifestCards.map((manifest) => /* @__PURE__ */ React30.createElement("div", { key: manifest.id || manifest.href }, /* @__PURE__ */ React30.createElement(ReferencedManifestCard, { manifest }))), legacyResources.map((resource, idx) => /* @__PURE__ */ React30.createElement("div", { key: resource.id || resource.href || `legacy-${idx}` }, /* @__PURE__ */ React30.createElement(
2879
3172
  TeaserCard,
2880
3173
  {
2881
3174
  href: resource.href,
@@ -2900,26 +3193,26 @@ function Timeline({
2900
3193
  ...rest
2901
3194
  }) {
2902
3195
  const payloadPoints = payload && Array.isArray(payload.points) ? payload.points : null;
2903
- const rawPoints = React29.useMemo(() => {
3196
+ const rawPoints = React30.useMemo(() => {
2904
3197
  if (Array.isArray(pointsProp) && pointsProp.length) return pointsProp;
2905
3198
  if (payloadPoints && payloadPoints.length) return payloadPoints;
2906
3199
  return [];
2907
3200
  }, [pointsProp, payloadPoints]);
2908
- const sanitizedPoints = React29.useMemo(
3201
+ const sanitizedPoints = React30.useMemo(
2909
3202
  () => sanitizePoints(rawPoints),
2910
3203
  [rawPoints]
2911
3204
  );
2912
3205
  const localeValue = payload && payload.locale ? payload.locale : localeProp;
2913
- const baseLocale = React29.useMemo(
3206
+ const baseLocale = React30.useMemo(
2914
3207
  () => createLocale(localeValue),
2915
3208
  [localeValue]
2916
3209
  );
2917
3210
  const rangeInput = payload && payload.range ? payload.range : rangeProp || {};
2918
- const rangeOverrides = React29.useMemo(
3211
+ const rangeOverrides = React30.useMemo(
2919
3212
  () => deriveRangeOverrides(sanitizedPoints, rangeInput),
2920
3213
  [sanitizedPoints, rangeInput]
2921
3214
  );
2922
- const effectiveRange = React29.useMemo(
3215
+ const effectiveRange = React30.useMemo(
2923
3216
  () => normalizeRange({
2924
3217
  ...rangeOverrides,
2925
3218
  locale: baseLocale
@@ -2928,7 +3221,7 @@ function Timeline({
2928
3221
  );
2929
3222
  const spanStart = effectiveRange.startDate.getTime();
2930
3223
  const span = effectiveRange.span;
2931
- const pointsWithPosition = React29.useMemo(() => {
3224
+ const pointsWithPosition = React30.useMemo(() => {
2932
3225
  if (!sanitizedPoints.length) return [];
2933
3226
  return sanitizedPoints.map((point, index) => {
2934
3227
  const timestamp = point.meta.timestamp;
@@ -2942,29 +3235,29 @@ function Timeline({
2942
3235
  };
2943
3236
  });
2944
3237
  }, [sanitizedPoints, spanStart, span]);
2945
- const [activeId, setActiveId] = React29.useState(
3238
+ const [activeId, setActiveId] = React30.useState(
2946
3239
  () => getActivePointId(pointsWithPosition)
2947
3240
  );
2948
- React29.useEffect(() => {
3241
+ React30.useEffect(() => {
2949
3242
  setActiveId(getActivePointId(pointsWithPosition));
2950
3243
  }, [pointsWithPosition]);
2951
3244
  const thresholdValue = typeof thresholdProp === "number" ? thresholdProp : payload && payload.threshold != null ? payload.threshold : null;
2952
3245
  const stepsValue = typeof steps === "number" ? Number(steps) : payload && typeof payload.steps === "number" ? Number(payload.steps) : null;
2953
- const thresholdMs = React29.useMemo(
3246
+ const thresholdMs = React30.useMemo(
2954
3247
  () => getThresholdMs(thresholdValue, effectiveRange.granularity),
2955
3248
  [thresholdValue, effectiveRange.granularity]
2956
3249
  );
2957
- const groupedEntries = React29.useMemo(
3250
+ const groupedEntries = React30.useMemo(
2958
3251
  () => buildGroupedEntries(pointsWithPosition, thresholdMs, {
2959
3252
  granularity: effectiveRange.granularity,
2960
3253
  locale: baseLocale
2961
3254
  }),
2962
3255
  [pointsWithPosition, thresholdMs, effectiveRange.granularity, baseLocale]
2963
3256
  );
2964
- const [expandedGroupIds, setExpandedGroupIds] = React29.useState(
3257
+ const [expandedGroupIds, setExpandedGroupIds] = React30.useState(
2965
3258
  () => /* @__PURE__ */ new Set()
2966
3259
  );
2967
- React29.useEffect(() => {
3260
+ React30.useEffect(() => {
2968
3261
  setExpandedGroupIds((prev) => {
2969
3262
  if (!prev || prev.size === 0) return prev;
2970
3263
  const validIds = new Set(
@@ -2979,7 +3272,7 @@ function Timeline({
2979
3272
  return changed ? next : prev;
2980
3273
  });
2981
3274
  }, [groupedEntries]);
2982
- const toggleGroup = React29.useCallback((groupId) => {
3275
+ const toggleGroup = React30.useCallback((groupId) => {
2983
3276
  setExpandedGroupIds((prev) => {
2984
3277
  const next = new Set(prev || []);
2985
3278
  if (next.has(groupId)) next.delete(groupId);
@@ -3002,7 +3295,7 @@ function Timeline({
3002
3295
  point.id === activeId ? "is-active" : "",
3003
3296
  point.highlight ? "is-highlighted" : ""
3004
3297
  ].filter(Boolean).join(" ");
3005
- const connector = /* @__PURE__ */ React29.createElement(
3298
+ const connector = /* @__PURE__ */ React30.createElement(
3006
3299
  TimelineConnector,
3007
3300
  {
3008
3301
  side: point.side,
@@ -3010,9 +3303,9 @@ function Timeline({
3010
3303
  highlight: point.highlight
3011
3304
  }
3012
3305
  );
3013
- const body = /* @__PURE__ */ React29.createElement("div", { className: "canopy-timeline__point-body" }, /* @__PURE__ */ React29.createElement("span", { className: "canopy-timeline__point-date" }, point.meta.label), /* @__PURE__ */ React29.createElement("span", { className: "canopy-timeline__point-title" }, point.title), point.summary ? /* @__PURE__ */ React29.createElement("span", { className: "canopy-timeline__point-summary" }, point.summary) : null);
3306
+ const body = /* @__PURE__ */ React30.createElement("div", { className: "canopy-timeline__point-body" }, /* @__PURE__ */ React30.createElement("span", { className: "canopy-timeline__point-date" }, point.meta.label), /* @__PURE__ */ React30.createElement("span", { className: "canopy-timeline__point-title" }, point.title), point.summary ? /* @__PURE__ */ React30.createElement("span", { className: "canopy-timeline__point-summary" }, point.summary) : null);
3014
3307
  const resourceSection = renderResourceSection(point);
3015
- return /* @__PURE__ */ React29.createElement(
3308
+ return /* @__PURE__ */ React30.createElement(
3016
3309
  "div",
3017
3310
  {
3018
3311
  key: point.id,
@@ -3020,7 +3313,7 @@ function Timeline({
3020
3313
  style: wrapperStyle,
3021
3314
  role: "listitem"
3022
3315
  },
3023
- point.side === "left" ? /* @__PURE__ */ React29.createElement(React29.Fragment, null, /* @__PURE__ */ React29.createElement("div", { className: cardClasses }, body, resourceSection), connector) : /* @__PURE__ */ React29.createElement(React29.Fragment, null, connector, /* @__PURE__ */ React29.createElement("div", { className: cardClasses }, body, resourceSection))
3316
+ point.side === "left" ? /* @__PURE__ */ React30.createElement(React30.Fragment, null, /* @__PURE__ */ React30.createElement("div", { className: cardClasses }, body, resourceSection), connector) : /* @__PURE__ */ React30.createElement(React30.Fragment, null, connector, /* @__PURE__ */ React30.createElement("div", { className: cardClasses }, body, resourceSection))
3024
3317
  );
3025
3318
  }
3026
3319
  function renderGroupEntry(entry) {
@@ -3031,7 +3324,7 @@ function Timeline({
3031
3324
  const wrapperStyle = { top: `${entry.progress * 100}%` };
3032
3325
  const isExpanded = expandedGroupIds.has(entry.id);
3033
3326
  const hasActivePoint = entry.points.some((point) => point.id === activeId);
3034
- const connector = /* @__PURE__ */ React29.createElement(
3327
+ const connector = /* @__PURE__ */ React30.createElement(
3035
3328
  TimelineConnector,
3036
3329
  {
3037
3330
  side: entry.side,
@@ -3045,7 +3338,7 @@ function Timeline({
3045
3338
  hasActivePoint ? "is-active" : ""
3046
3339
  ].filter(Boolean).join(" ");
3047
3340
  const countLabel = `${entry.count} event${entry.count > 1 ? "s" : ""}`;
3048
- const header = /* @__PURE__ */ React29.createElement("div", { className: "canopy-timeline__group-header" }, /* @__PURE__ */ React29.createElement("div", { className: "canopy-timeline__group-summary" }, /* @__PURE__ */ React29.createElement("span", { className: "canopy-timeline__point-date" }, entry.label), /* @__PURE__ */ React29.createElement("span", { className: "canopy-timeline__group-count" }, countLabel)), /* @__PURE__ */ React29.createElement(
3341
+ const header = /* @__PURE__ */ React30.createElement("div", { className: "canopy-timeline__group-header" }, /* @__PURE__ */ React30.createElement("div", { className: "canopy-timeline__group-summary" }, /* @__PURE__ */ React30.createElement("span", { className: "canopy-timeline__point-date" }, entry.label), /* @__PURE__ */ React30.createElement("span", { className: "canopy-timeline__group-count" }, countLabel)), /* @__PURE__ */ React30.createElement(
3049
3342
  "button",
3050
3343
  {
3051
3344
  type: "button",
@@ -3055,7 +3348,7 @@ function Timeline({
3055
3348
  },
3056
3349
  isExpanded ? "Hide details" : "Show details"
3057
3350
  ));
3058
- const groupPoints = isExpanded ? /* @__PURE__ */ React29.createElement("div", { className: "canopy-timeline__group-points" }, entry.points.map((point) => /* @__PURE__ */ React29.createElement(
3351
+ const groupPoints = isExpanded ? /* @__PURE__ */ React30.createElement("div", { className: "canopy-timeline__group-points" }, entry.points.map((point) => /* @__PURE__ */ React30.createElement(
3059
3352
  "button",
3060
3353
  {
3061
3354
  key: point.id,
@@ -3066,11 +3359,11 @@ function Timeline({
3066
3359
  ].filter(Boolean).join(" "),
3067
3360
  onClick: () => setActiveId(point.id)
3068
3361
  },
3069
- /* @__PURE__ */ React29.createElement("span", { className: "canopy-timeline__point-date" }, point.meta.label),
3070
- /* @__PURE__ */ React29.createElement("span", { className: "canopy-timeline__group-point-title" }, point.title)
3362
+ /* @__PURE__ */ React30.createElement("span", { className: "canopy-timeline__point-date" }, point.meta.label),
3363
+ /* @__PURE__ */ React30.createElement("span", { className: "canopy-timeline__group-point-title" }, point.title)
3071
3364
  ))) : null;
3072
- const groupCard = /* @__PURE__ */ React29.createElement("div", { className: groupClasses }, header, groupPoints);
3073
- return /* @__PURE__ */ React29.createElement(
3365
+ const groupCard = /* @__PURE__ */ React30.createElement("div", { className: groupClasses }, header, groupPoints);
3366
+ return /* @__PURE__ */ React30.createElement(
3074
3367
  "div",
3075
3368
  {
3076
3369
  key: entry.id,
@@ -3078,17 +3371,17 @@ function Timeline({
3078
3371
  style: wrapperStyle,
3079
3372
  role: "listitem"
3080
3373
  },
3081
- entry.side === "left" ? /* @__PURE__ */ React29.createElement(React29.Fragment, null, groupCard, connector) : /* @__PURE__ */ React29.createElement(React29.Fragment, null, connector, groupCard)
3374
+ entry.side === "left" ? /* @__PURE__ */ React30.createElement(React30.Fragment, null, groupCard, connector) : /* @__PURE__ */ React30.createElement(React30.Fragment, null, connector, groupCard)
3082
3375
  );
3083
3376
  }
3084
- return /* @__PURE__ */ React29.createElement("section", { className: containerClasses, ...rest }, title ? /* @__PURE__ */ React29.createElement("h2", { className: "canopy-timeline__title" }, title) : null, description ? /* @__PURE__ */ React29.createElement("p", { className: "canopy-timeline__description" }, description) : null, rangeLabel ? /* @__PURE__ */ React29.createElement("p", { className: "canopy-timeline__range", "aria-live": "polite" }, rangeLabel) : null, /* @__PURE__ */ React29.createElement("div", { className: "canopy-timeline__body" }, /* @__PURE__ */ React29.createElement(
3377
+ return /* @__PURE__ */ React30.createElement("section", { className: containerClasses, ...rest }, title ? /* @__PURE__ */ React30.createElement("h2", { className: "canopy-timeline__title" }, title) : null, description ? /* @__PURE__ */ React30.createElement("p", { className: "canopy-timeline__description" }, description) : null, rangeLabel ? /* @__PURE__ */ React30.createElement("p", { className: "canopy-timeline__range", "aria-live": "polite" }, rangeLabel) : null, /* @__PURE__ */ React30.createElement("div", { className: "canopy-timeline__body" }, /* @__PURE__ */ React30.createElement(
3085
3378
  "div",
3086
3379
  {
3087
3380
  className: "canopy-timeline__list",
3088
3381
  role: "list",
3089
3382
  style: { minHeight: trackHeight }
3090
3383
  },
3091
- /* @__PURE__ */ React29.createElement("div", { className: "canopy-timeline__spine", "aria-hidden": "true" }),
3384
+ /* @__PURE__ */ React30.createElement("div", { className: "canopy-timeline__spine", "aria-hidden": "true" }),
3092
3385
  renderSteps(stepsValue, effectiveRange),
3093
3386
  groupedEntries.map((entry) => {
3094
3387
  if (entry.type === "group") return renderGroupEntry(entry);
@@ -3103,7 +3396,7 @@ function renderSteps(stepSize, range) {
3103
3396
  const markers = [];
3104
3397
  if (startYear < endYear) {
3105
3398
  markers.push(
3106
- /* @__PURE__ */ React29.createElement(
3399
+ /* @__PURE__ */ React30.createElement(
3107
3400
  "span",
3108
3401
  {
3109
3402
  key: "timeline-step-start",
@@ -3111,12 +3404,12 @@ function renderSteps(stepSize, range) {
3111
3404
  style: { top: "0%" },
3112
3405
  "aria-hidden": "true"
3113
3406
  },
3114
- /* @__PURE__ */ React29.createElement("span", { className: "canopy-timeline__step-line" }),
3115
- /* @__PURE__ */ React29.createElement("span", { className: "canopy-timeline__step-label" }, startYear)
3407
+ /* @__PURE__ */ React30.createElement("span", { className: "canopy-timeline__step-line" }),
3408
+ /* @__PURE__ */ React30.createElement("span", { className: "canopy-timeline__step-label" }, startYear)
3116
3409
  )
3117
3410
  );
3118
3411
  markers.push(
3119
- /* @__PURE__ */ React29.createElement(
3412
+ /* @__PURE__ */ React30.createElement(
3120
3413
  "span",
3121
3414
  {
3122
3415
  key: "timeline-step-end",
@@ -3124,8 +3417,8 @@ function renderSteps(stepSize, range) {
3124
3417
  style: { top: "100%" },
3125
3418
  "aria-hidden": "true"
3126
3419
  },
3127
- /* @__PURE__ */ React29.createElement("span", { className: "canopy-timeline__step-line" }),
3128
- /* @__PURE__ */ React29.createElement("span", { className: "canopy-timeline__step-label" }, endYear)
3420
+ /* @__PURE__ */ React30.createElement("span", { className: "canopy-timeline__step-line" }),
3421
+ /* @__PURE__ */ React30.createElement("span", { className: "canopy-timeline__step-label" }, endYear)
3129
3422
  )
3130
3423
  );
3131
3424
  }
@@ -3135,7 +3428,7 @@ function renderSteps(stepSize, range) {
3135
3428
  const progress = (timestamp - range.startDate.getTime()) / range.span;
3136
3429
  if (progress <= 0 || progress >= 1) continue;
3137
3430
  markers.push(
3138
- /* @__PURE__ */ React29.createElement(
3431
+ /* @__PURE__ */ React30.createElement(
3139
3432
  "span",
3140
3433
  {
3141
3434
  key: `timeline-step-${year}`,
@@ -3143,8 +3436,8 @@ function renderSteps(stepSize, range) {
3143
3436
  style: { top: `calc(${progress * 100}% - 0.5px)` },
3144
3437
  "aria-hidden": "true"
3145
3438
  },
3146
- /* @__PURE__ */ React29.createElement("span", { className: "canopy-timeline__step-line" }),
3147
- /* @__PURE__ */ React29.createElement("span", { className: "canopy-timeline__step-label" }, year)
3439
+ /* @__PURE__ */ React30.createElement("span", { className: "canopy-timeline__step-line" }),
3440
+ /* @__PURE__ */ React30.createElement("span", { className: "canopy-timeline__step-label" }, year)
3148
3441
  )
3149
3442
  );
3150
3443
  }
@@ -3158,7 +3451,7 @@ function TimelinePoint() {
3158
3451
  TimelinePoint.displayName = "TimelinePoint";
3159
3452
 
3160
3453
  // ui/src/utils/manifestReferences.js
3161
- import React30 from "react";
3454
+ import React31 from "react";
3162
3455
  import navigationHelpers6 from "@canopy-iiif/app/lib/components/navigation.js";
3163
3456
  function normalizeManifestId(raw) {
3164
3457
  if (!raw) return "";
@@ -3175,13 +3468,13 @@ function normalizeManifestId(raw) {
3175
3468
  return String(raw || "").trim();
3176
3469
  }
3177
3470
  }
3178
- var PageContextFallback = React30.createContext(null);
3471
+ var PageContextFallback = React31.createContext(null);
3179
3472
  function useReferencedManifestMap() {
3180
3473
  var _a, _b;
3181
3474
  const PageContext = ((_b = (_a = navigationHelpers6) == null ? void 0 : _a.getPageContext) == null ? void 0 : _b.call(_a)) || PageContextFallback;
3182
- const pageContext = React30.useContext(PageContext);
3475
+ const pageContext = React31.useContext(PageContext);
3183
3476
  const referencedItems = pageContext && pageContext.page && Array.isArray(pageContext.page.referencedItems) ? pageContext.page.referencedItems : [];
3184
- return React30.useMemo(() => {
3477
+ return React31.useMemo(() => {
3185
3478
  const map = /* @__PURE__ */ new Map();
3186
3479
  referencedItems.forEach((item) => {
3187
3480
  if (!item) return;
@@ -3236,7 +3529,7 @@ function normalizeResource(resource, index) {
3236
3529
  }
3237
3530
  function normalizePoint(child, index, options) {
3238
3531
  var _a, _b, _c, _d, _e, _f;
3239
- if (!React31.isValidElement(child)) return null;
3532
+ if (!React32.isValidElement(child)) return null;
3240
3533
  if (child.type !== TimelinePoint && ((_a = child.type) == null ? void 0 : _a.displayName) !== "TimelinePoint")
3241
3534
  return null;
3242
3535
  const props = child.props || {};
@@ -3252,7 +3545,7 @@ function normalizePoint(child, index, options) {
3252
3545
  try {
3253
3546
  if (props.children) {
3254
3547
  detailsHtml = ReactDOMServer.renderToStaticMarkup(
3255
- React31.createElement(React31.Fragment, null, props.children)
3548
+ React32.createElement(React32.Fragment, null, props.children)
3256
3549
  );
3257
3550
  }
3258
3551
  } catch (_) {
@@ -3301,7 +3594,7 @@ function MdxTimeline({ children, ...rest }) {
3301
3594
  const localeObj = createLocale(localeValue);
3302
3595
  const localeBase = typeof localeObj === "string" ? localeObj : localeObj.baseName || "en-US";
3303
3596
  const manifestMap = useReferencedManifestMap();
3304
- const childArray = React31.Children.toArray(children);
3597
+ const childArray = React32.Children.toArray(children);
3305
3598
  const points = childArray.map(
3306
3599
  (child, index) => normalizePoint(child, index, {
3307
3600
  range: rest.range || {},
@@ -3317,7 +3610,7 @@ function MdxTimeline({ children, ...rest }) {
3317
3610
  steps: rest.steps != null ? rest.steps : null
3318
3611
  };
3319
3612
  const json = serializeForScript(serializeProps(rest, payload, localeBase));
3320
- return /* @__PURE__ */ React31.createElement("div", { "data-canopy-timeline": "1" }, /* @__PURE__ */ React31.createElement(Timeline, { ...rest, __canopyTimeline: payload }), /* @__PURE__ */ React31.createElement(
3613
+ return /* @__PURE__ */ React32.createElement("div", { "data-canopy-timeline": "1" }, /* @__PURE__ */ React32.createElement(Timeline, { ...rest, __canopyTimeline: payload }), /* @__PURE__ */ React32.createElement(
3321
3614
  "script",
3322
3615
  {
3323
3616
  type: "application/json",
@@ -3327,7 +3620,7 @@ function MdxTimeline({ children, ...rest }) {
3327
3620
  }
3328
3621
 
3329
3622
  // ui/src/content/map/MdxMap.jsx
3330
- import React32 from "react";
3623
+ import React33 from "react";
3331
3624
  import ReactDOMServer2 from "react-dom/server";
3332
3625
 
3333
3626
  // ui/src/content/map/MapPoint.jsx
@@ -3354,7 +3647,7 @@ function renderDetailsHtml(children) {
3354
3647
  if (!children) return "";
3355
3648
  try {
3356
3649
  return ReactDOMServer2.renderToStaticMarkup(
3357
- React32.createElement(React32.Fragment, null, children)
3650
+ React33.createElement(React33.Fragment, null, children)
3358
3651
  );
3359
3652
  } catch (_) {
3360
3653
  return "";
@@ -3362,7 +3655,7 @@ function renderDetailsHtml(children) {
3362
3655
  }
3363
3656
  function normalizeCustomPoint(child, index, manifestMap) {
3364
3657
  var _a;
3365
- if (!React32.isValidElement(child)) return null;
3658
+ if (!React33.isValidElement(child)) return null;
3366
3659
  if (child.type !== MapPoint && ((_a = child.type) == null ? void 0 : _a.displayName) !== "MapPoint") return null;
3367
3660
  const coords = normalizeCoordinates(child.props || {});
3368
3661
  if (!coords) return null;
@@ -3407,7 +3700,7 @@ function normalizeCustomPoint(child, index, manifestMap) {
3407
3700
  };
3408
3701
  }
3409
3702
  function normalizeCustomPoints(children, manifestMap) {
3410
- return React32.Children.toArray(children).map((child, index) => normalizeCustomPoint(child, index, manifestMap)).filter(Boolean);
3703
+ return React33.Children.toArray(children).map((child, index) => normalizeCustomPoint(child, index, manifestMap)).filter(Boolean);
3411
3704
  }
3412
3705
  function normalizeHeight(value) {
3413
3706
  if (value == null) return "600px";
@@ -3545,11 +3838,11 @@ function MdxMap({ children, ...rest }) {
3545
3838
  if (placeholderClass) placeholderProps.className = placeholderClass;
3546
3839
  if (rest.id) placeholderProps.id = rest.id;
3547
3840
  if (rest.style) placeholderProps.style = rest.style;
3548
- return /* @__PURE__ */ React32.createElement("div", { "data-canopy-map": "1" }, /* @__PURE__ */ React32.createElement("div", { ...placeholderProps, "aria-live": "polite" }, /* @__PURE__ */ React32.createElement("div", { className: "canopy-map__status" }, "Loading map\u2026")), /* @__PURE__ */ React32.createElement("script", { type: "application/json", dangerouslySetInnerHTML: { __html: json } }));
3841
+ return /* @__PURE__ */ React33.createElement("div", { "data-canopy-map": "1" }, /* @__PURE__ */ React33.createElement("div", { ...placeholderProps, "aria-live": "polite" }, /* @__PURE__ */ React33.createElement("div", { className: "canopy-map__status" }, "Loading map\u2026")), /* @__PURE__ */ React33.createElement("script", { type: "application/json", dangerouslySetInnerHTML: { __html: json } }));
3549
3842
  }
3550
3843
 
3551
3844
  // ui/src/search/MdxSearchResults.jsx
3552
- import React33 from "react";
3845
+ import React34 from "react";
3553
3846
  function MdxSearchResults(props) {
3554
3847
  let json = "{}";
3555
3848
  try {
@@ -3557,11 +3850,11 @@ function MdxSearchResults(props) {
3557
3850
  } catch (_) {
3558
3851
  json = "{}";
3559
3852
  }
3560
- return /* @__PURE__ */ React33.createElement("div", { "data-canopy-search-results": "1" }, /* @__PURE__ */ React33.createElement("script", { type: "application/json", dangerouslySetInnerHTML: { __html: json } }));
3853
+ return /* @__PURE__ */ React34.createElement("div", { "data-canopy-search-results": "1" }, /* @__PURE__ */ React34.createElement("script", { type: "application/json", dangerouslySetInnerHTML: { __html: json } }));
3561
3854
  }
3562
3855
 
3563
3856
  // ui/src/search/SearchSummary.jsx
3564
- import React34 from "react";
3857
+ import React35 from "react";
3565
3858
  function SearchSummary(props) {
3566
3859
  let json = "{}";
3567
3860
  try {
@@ -3569,11 +3862,11 @@ function SearchSummary(props) {
3569
3862
  } catch (_) {
3570
3863
  json = "{}";
3571
3864
  }
3572
- return /* @__PURE__ */ React34.createElement("div", { "data-canopy-search-summary": "1" }, /* @__PURE__ */ React34.createElement("script", { type: "application/json", dangerouslySetInnerHTML: { __html: json } }));
3865
+ return /* @__PURE__ */ React35.createElement("div", { "data-canopy-search-summary": "1" }, /* @__PURE__ */ React35.createElement("script", { type: "application/json", dangerouslySetInnerHTML: { __html: json } }));
3573
3866
  }
3574
3867
 
3575
3868
  // ui/src/search/MdxSearchTabs.jsx
3576
- import React35 from "react";
3869
+ import React36 from "react";
3577
3870
  function MdxSearchTabs(props) {
3578
3871
  let json = "{}";
3579
3872
  try {
@@ -3581,11 +3874,11 @@ function MdxSearchTabs(props) {
3581
3874
  } catch (_) {
3582
3875
  json = "{}";
3583
3876
  }
3584
- return /* @__PURE__ */ React35.createElement("div", { "data-canopy-search-tabs": "1" }, /* @__PURE__ */ React35.createElement("script", { type: "application/json", dangerouslySetInnerHTML: { __html: json } }));
3877
+ return /* @__PURE__ */ React36.createElement("div", { "data-canopy-search-tabs": "1" }, /* @__PURE__ */ React36.createElement("script", { type: "application/json", dangerouslySetInnerHTML: { __html: json } }));
3585
3878
  }
3586
3879
 
3587
3880
  // ui/src/search/MdxSearch.jsx
3588
- import React36 from "react";
3881
+ import React37 from "react";
3589
3882
  function MdxSearch(props = {}) {
3590
3883
  const {
3591
3884
  layout,
@@ -3603,11 +3896,11 @@ function MdxSearch(props = {}) {
3603
3896
  resultsPayload.layout = layout;
3604
3897
  }
3605
3898
  const classes = ["canopy-search", className].filter(Boolean).join(" ");
3606
- return /* @__PURE__ */ React36.createElement("section", { className: classes, "data-canopy-search": "1" }, showTabs ? /* @__PURE__ */ React36.createElement(MdxSearchTabs, { ...tabsProps }) : null, showSummary ? /* @__PURE__ */ React36.createElement(SearchSummary, { ...summaryProps }) : null, showResults ? /* @__PURE__ */ React36.createElement(MdxSearchResults, { ...resultsPayload }) : null, children || null);
3899
+ return /* @__PURE__ */ React37.createElement("section", { className: classes, "data-canopy-search": "1" }, showTabs ? /* @__PURE__ */ React37.createElement(MdxSearchTabs, { ...tabsProps }) : null, showSummary ? /* @__PURE__ */ React37.createElement(SearchSummary, { ...summaryProps }) : null, showResults ? /* @__PURE__ */ React37.createElement(MdxSearchResults, { ...resultsPayload }) : null, children || null);
3607
3900
  }
3608
3901
 
3609
3902
  // ui/src/search-form/MdxSearchFormModal.jsx
3610
- import React37 from "react";
3903
+ import React38 from "react";
3611
3904
  function MdxSearchFormModal(props = {}) {
3612
3905
  const {
3613
3906
  placeholder = "Search\u2026",
@@ -3623,12 +3916,12 @@ function MdxSearchFormModal(props = {}) {
3623
3916
  const text = typeof label === "string" && label.trim() ? label.trim() : buttonLabel;
3624
3917
  const resolvedSearchPath = resolveSearchPath(searchPath);
3625
3918
  const data = { placeholder, hotkey, maxResults, groupOrder, label: text, searchPath: resolvedSearchPath };
3626
- return /* @__PURE__ */ React37.createElement("div", { "data-canopy-search-form": true, className: "flex-1 min-w-0" }, /* @__PURE__ */ React37.createElement("div", { className: "relative w-full" }, /* @__PURE__ */ React37.createElement(SearchPanelForm, { placeholder, buttonLabel, label, searchPath: resolvedSearchPath }), /* @__PURE__ */ React37.createElement(SearchPanelTeaserResults, null)), /* @__PURE__ */ React37.createElement("script", { type: "application/json", dangerouslySetInnerHTML: { __html: JSON.stringify(data) } }));
3919
+ return /* @__PURE__ */ React38.createElement("div", { "data-canopy-search-form": true, className: "flex-1 min-w-0" }, /* @__PURE__ */ React38.createElement("div", { className: "relative w-full" }, /* @__PURE__ */ React38.createElement(SearchPanelForm, { placeholder, buttonLabel, label, searchPath: resolvedSearchPath }), /* @__PURE__ */ React38.createElement(SearchPanelTeaserResults, null)), /* @__PURE__ */ React38.createElement("script", { type: "application/json", dangerouslySetInnerHTML: { __html: JSON.stringify(data) } }));
3627
3920
  }
3628
3921
 
3629
3922
  // ui/src/iiif/ManifestPrimitives.jsx
3630
3923
  var import_slugify = __toESM(require_slugify());
3631
- import React38 from "react";
3924
+ import React39 from "react";
3632
3925
  import {
3633
3926
  Label as CloverLabel,
3634
3927
  Metadata as CloverMetadata,
@@ -3769,7 +4062,7 @@ function MetadataFacetLink(props) {
3769
4062
  const valueSlug = facetSlug ? toValueSlug(text) : "";
3770
4063
  const href = facetSlug && valueSlug ? buildFacetSearchHref(facetSlug, valueSlug) : "";
3771
4064
  if (!href) return text;
3772
- return /* @__PURE__ */ React38.createElement(
4065
+ return /* @__PURE__ */ React39.createElement(
3773
4066
  "a",
3774
4067
  {
3775
4068
  href,
@@ -3795,7 +4088,7 @@ function buildFacetCustomValueContent(items, manifest) {
3795
4088
  seen.add(normalized);
3796
4089
  custom.push({
3797
4090
  matchingLabel: item.label,
3798
- Content: /* @__PURE__ */ React38.createElement(MetadataFacetLink, { facetSlug: facet.slug })
4091
+ Content: /* @__PURE__ */ React39.createElement(MetadataFacetLink, { facetSlug: facet.slug })
3799
4092
  });
3800
4093
  }
3801
4094
  return custom;
@@ -3827,12 +4120,12 @@ function mergeCustomValueContent(userContent, autoContent) {
3827
4120
  function Label({ manifest, label, ...rest }) {
3828
4121
  const intl = label || manifest && manifest.label;
3829
4122
  if (!hasInternationalValue(intl)) return null;
3830
- return /* @__PURE__ */ React38.createElement(CloverLabel, { label: intl, ...rest });
4123
+ return /* @__PURE__ */ React39.createElement(CloverLabel, { label: intl, ...rest });
3831
4124
  }
3832
4125
  function Summary({ manifest, summary, ...rest }) {
3833
4126
  const intl = summary || manifest && manifest.summary;
3834
4127
  if (!hasInternationalValue(intl)) return null;
3835
- return /* @__PURE__ */ React38.createElement(CloverSummary, { summary: intl, ...rest });
4128
+ return /* @__PURE__ */ React39.createElement(CloverSummary, { summary: intl, ...rest });
3836
4129
  }
3837
4130
  function Metadata({ manifest, metadata, customValueContent, ...rest }) {
3838
4131
  const items = ensureMetadata(metadata || manifest && manifest.metadata);
@@ -3842,7 +4135,7 @@ function Metadata({ manifest, metadata, customValueContent, ...rest }) {
3842
4135
  customValueContent,
3843
4136
  autoCustomContent
3844
4137
  );
3845
- return /* @__PURE__ */ React38.createElement(
4138
+ return /* @__PURE__ */ React39.createElement(
3846
4139
  CloverMetadata,
3847
4140
  {
3848
4141
  metadata: items,
@@ -3856,17 +4149,17 @@ function RequiredStatement({ manifest, requiredStatement, ...rest }) {
3856
4149
  if (!stmt || !hasInternationalValue(stmt.label) || !hasInternationalValue(stmt.value)) {
3857
4150
  return null;
3858
4151
  }
3859
- return /* @__PURE__ */ React38.createElement(CloverRequiredStatement, { requiredStatement: stmt, ...rest });
4152
+ return /* @__PURE__ */ React39.createElement(CloverRequiredStatement, { requiredStatement: stmt, ...rest });
3860
4153
  }
3861
4154
 
3862
4155
  // ui/src/iiif/Properties/Id.jsx
3863
- import React39 from "react";
4156
+ import React40 from "react";
3864
4157
  function Id({ title = "IIIF Manifest", id, ...props }) {
3865
- return /* @__PURE__ */ React39.createElement("dl", null, /* @__PURE__ */ React39.createElement("dt", null, title), /* @__PURE__ */ React39.createElement("dd", null, /* @__PURE__ */ React39.createElement("a", { href: id }, id)));
4158
+ return /* @__PURE__ */ React40.createElement("dl", null, /* @__PURE__ */ React40.createElement("dt", null, title), /* @__PURE__ */ React40.createElement("dd", null, /* @__PURE__ */ React40.createElement("a", { href: id }, id)));
3866
4159
  }
3867
4160
 
3868
4161
  // ui/src/docs/CodeBlock.jsx
3869
- import React40 from "react";
4162
+ import React41 from "react";
3870
4163
  function parseHighlightAttr(attr) {
3871
4164
  if (!attr) return /* @__PURE__ */ new Set();
3872
4165
  const cleaned = String(attr || "").trim();
@@ -3912,10 +4205,10 @@ var highlightBaseStyle = {
3912
4205
  };
3913
4206
  function DocsCodeBlock(props = {}) {
3914
4207
  const { children, ...rest } = props;
3915
- const childArray = React40.Children.toArray(children);
3916
- const codeElement = childArray.find((el) => React40.isValidElement(el));
4208
+ const childArray = React41.Children.toArray(children);
4209
+ const codeElement = childArray.find((el) => React41.isValidElement(el));
3917
4210
  if (!codeElement || !codeElement.props) {
3918
- return React40.createElement("pre", props);
4211
+ return React41.createElement("pre", props);
3919
4212
  }
3920
4213
  const {
3921
4214
  className = "",
@@ -3930,8 +4223,8 @@ function DocsCodeBlock(props = {}) {
3930
4223
  const highlightSet = parseHighlightAttr(highlightAttr);
3931
4224
  const copyAttr = codeProps["data-copy"];
3932
4225
  const enableCopy = copyAttr !== void 0 ? copyAttr === true || copyAttr === "true" || copyAttr === "" : false;
3933
- const [copied, setCopied] = React40.useState(false);
3934
- const handleCopy = React40.useCallback(async () => {
4226
+ const [copied, setCopied] = React41.useState(false);
4227
+ const handleCopy = React41.useCallback(async () => {
3935
4228
  const text = rawCode;
3936
4229
  try {
3937
4230
  if (typeof navigator !== "undefined" && navigator.clipboard && navigator.clipboard.writeText) {
@@ -3996,20 +4289,20 @@ function DocsCodeBlock(props = {}) {
3996
4289
  const highlight = highlightSet.has(lineNumber);
3997
4290
  const style = highlight ? { ...baseLineStyle, ...highlightBaseStyle } : baseLineStyle;
3998
4291
  const displayLine = line === "" ? " " : line;
3999
- return React40.createElement(
4292
+ return React41.createElement(
4000
4293
  "span",
4001
4294
  { key: lineNumber, style },
4002
- React40.createElement("span", { style: lineContentStyle }, displayLine)
4295
+ React41.createElement("span", { style: lineContentStyle }, displayLine)
4003
4296
  );
4004
4297
  });
4005
- return React40.createElement(
4298
+ return React41.createElement(
4006
4299
  "div",
4007
4300
  { style: containerStyle },
4008
- showHeader ? React40.createElement(
4301
+ showHeader ? React41.createElement(
4009
4302
  "div",
4010
4303
  { style: headerStyle },
4011
- React40.createElement("span", null, showFilename ? filename : null),
4012
- enableCopy ? React40.createElement(
4304
+ React41.createElement("span", null, showFilename ? filename : null),
4305
+ enableCopy ? React41.createElement(
4013
4306
  "button",
4014
4307
  {
4015
4308
  type: "button",
@@ -4029,29 +4322,29 @@ function DocsCodeBlock(props = {}) {
4029
4322
  copied ? "Copied" : "Copy"
4030
4323
  ) : null
4031
4324
  ) : null,
4032
- React40.createElement(
4325
+ React41.createElement(
4033
4326
  "pre",
4034
4327
  { ...preRest, className: preClassName, style: mergedPreStyle },
4035
- React40.createElement("code", { style: codeStyle }, lineElements)
4328
+ React41.createElement("code", { style: codeStyle }, lineElements)
4036
4329
  )
4037
4330
  );
4038
4331
  }
4039
4332
 
4040
4333
  // ui/src/docs/MarkdownTable.jsx
4041
- import React41 from "react";
4334
+ import React42 from "react";
4042
4335
  function MarkdownTable({ className = "", ...rest }) {
4043
4336
  const merged = ["markdown-table", className].filter(Boolean).join(" ");
4044
- return /* @__PURE__ */ React41.createElement("div", { className: "markdown-table__frame" }, /* @__PURE__ */ React41.createElement("table", { className: merged, ...rest }));
4337
+ return /* @__PURE__ */ React42.createElement("div", { className: "markdown-table__frame" }, /* @__PURE__ */ React42.createElement("table", { className: merged, ...rest }));
4045
4338
  }
4046
4339
 
4047
4340
  // ui/src/docs/Diagram.jsx
4048
- import React42 from "react";
4341
+ import React43 from "react";
4049
4342
  function CanopyDiagram() {
4050
- return /* @__PURE__ */ React42.createElement("div", { className: "canopy-diagram" }, /* @__PURE__ */ React42.createElement("section", { className: "canopy-diagram__section canopy-diagram__section--collections" }, /* @__PURE__ */ React42.createElement("h3", null, "IIIF Collection(s)"), /* @__PURE__ */ React42.createElement("span", { className: "canopy-diagram__section-summary" }, "Source collections contribute 105 total manifests that Canopy retrieves as-is via IIIF endpoints."), /* @__PURE__ */ React42.createElement("div", { className: "canopy-diagram__grid" }, /* @__PURE__ */ React42.createElement("article", null, /* @__PURE__ */ React42.createElement("h4", null, "Collection A"), /* @__PURE__ */ React42.createElement("ul", null, /* @__PURE__ */ React42.createElement("li", null, "70 Manifests"), /* @__PURE__ */ React42.createElement("li", null, "IIIF Images + A/V"), /* @__PURE__ */ React42.createElement("li", null, "Textual Annotations"))), /* @__PURE__ */ React42.createElement("article", null, /* @__PURE__ */ React42.createElement("h4", null, "Collection B"), /* @__PURE__ */ React42.createElement("ul", null, /* @__PURE__ */ React42.createElement("li", null, "35 Manifests"), /* @__PURE__ */ React42.createElement("li", null, "IIIF Images + A/V"), /* @__PURE__ */ React42.createElement("li", null, "Textual Annotations"))))), /* @__PURE__ */ React42.createElement("div", { className: "canopy-diagram__arrow", "aria-hidden": "true" }, /* @__PURE__ */ React42.createElement("span", { className: "canopy-diagram__arrow-line" }), /* @__PURE__ */ React42.createElement("span", { className: "canopy-diagram__arrow-head" })), /* @__PURE__ */ React42.createElement("section", { className: "canopy-diagram__section canopy-diagram__section--build" }, /* @__PURE__ */ React42.createElement("h3", null, "Canopy Build Process"), /* @__PURE__ */ React42.createElement("span", { className: "canopy-diagram__section-summary" }, "Canopy syncs manifests, page content, and annotations before bundling the site."), /* @__PURE__ */ React42.createElement("div", { className: "canopy-diagram__grid" }, /* @__PURE__ */ React42.createElement("article", null, /* @__PURE__ */ React42.createElement("h4", null, "Automated content"), /* @__PURE__ */ React42.createElement("ul", null, /* @__PURE__ */ React42.createElement("li", null, "105 manifests \u2192 105 work pages"), /* @__PURE__ */ React42.createElement("li", null, "One page per manifest"), /* @__PURE__ */ React42.createElement("li", null, "Customize page layout"))), /* @__PURE__ */ React42.createElement("article", null, /* @__PURE__ */ React42.createElement("h4", null, "Contextual content"), /* @__PURE__ */ React42.createElement("ul", null, /* @__PURE__ */ React42.createElement("li", null, "Markdown & MDX pages"), /* @__PURE__ */ React42.createElement("li", null, "Author narratives"), /* @__PURE__ */ React42.createElement("li", null, "Reference manifests inline"))), /* @__PURE__ */ React42.createElement("article", null, /* @__PURE__ */ React42.createElement("h4", null, "Search index"), /* @__PURE__ */ React42.createElement("ul", null, /* @__PURE__ */ React42.createElement("li", null, "Combines works + pages"), /* @__PURE__ */ React42.createElement("li", null, "Customize result layout"), /* @__PURE__ */ React42.createElement("li", null, "Optional annotations"))))), /* @__PURE__ */ React42.createElement("div", { className: "canopy-diagram__arrow", "aria-hidden": "true" }, /* @__PURE__ */ React42.createElement("span", { className: "canopy-diagram__arrow-line" }), /* @__PURE__ */ React42.createElement("span", { className: "canopy-diagram__arrow-head" })), /* @__PURE__ */ React42.createElement("section", { className: "canopy-diagram__section canopy-diagram__section--output" }, /* @__PURE__ */ React42.createElement("h3", null, "Static Digital Project"), /* @__PURE__ */ React42.createElement("span", { className: "canopy-diagram__section-summary" }, "The output is a lightweight bundle of HTML, CSS, JS, and JSON assets that can deploy anywhere."), /* @__PURE__ */ React42.createElement("div", { className: "canopy-diagram__grid" }, /* @__PURE__ */ React42.createElement("article", null, /* @__PURE__ */ React42.createElement("h4", null, "Work pages"), /* @__PURE__ */ React42.createElement("ul", null, /* @__PURE__ */ React42.createElement("li", null, "105 generated HTML pages"), /* @__PURE__ */ React42.createElement("li", null, "Each links back to source manifests"), /* @__PURE__ */ React42.createElement("li", null, "Styled with Canopy components"))), /* @__PURE__ */ React42.createElement("article", null, /* @__PURE__ */ React42.createElement("h4", null, "Custom pages"), /* @__PURE__ */ React42.createElement("ul", null, /* @__PURE__ */ React42.createElement("li", null, "Markdown & MDX-authored content"), /* @__PURE__ */ React42.createElement("li", null, "Reusable layouts for narratives"), /* @__PURE__ */ React42.createElement("li", null, "Embed IIIF media & interstitials"))), /* @__PURE__ */ React42.createElement("article", null, /* @__PURE__ */ React42.createElement("h4", null, "Search bundle"), /* @__PURE__ */ React42.createElement("ul", null, /* @__PURE__ */ React42.createElement("li", null, "Static FlexSearch index"), /* @__PURE__ */ React42.createElement("li", null, "Works + pages share records"), /* @__PURE__ */ React42.createElement("li", null, "Optional annotation dataset"))))));
4343
+ return /* @__PURE__ */ React43.createElement("div", { className: "canopy-diagram" }, /* @__PURE__ */ React43.createElement("section", { className: "canopy-diagram__section canopy-diagram__section--collections" }, /* @__PURE__ */ React43.createElement("h3", null, "IIIF Collection(s)"), /* @__PURE__ */ React43.createElement("span", { className: "canopy-diagram__section-summary" }, "Source collections contribute 105 total manifests that Canopy retrieves as-is via IIIF endpoints."), /* @__PURE__ */ React43.createElement("div", { className: "canopy-diagram__grid" }, /* @__PURE__ */ React43.createElement("article", null, /* @__PURE__ */ React43.createElement("h4", null, "Collection A"), /* @__PURE__ */ React43.createElement("ul", null, /* @__PURE__ */ React43.createElement("li", null, "70 Manifests"), /* @__PURE__ */ React43.createElement("li", null, "IIIF Images + A/V"), /* @__PURE__ */ React43.createElement("li", null, "Textual Annotations"))), /* @__PURE__ */ React43.createElement("article", null, /* @__PURE__ */ React43.createElement("h4", null, "Collection B"), /* @__PURE__ */ React43.createElement("ul", null, /* @__PURE__ */ React43.createElement("li", null, "35 Manifests"), /* @__PURE__ */ React43.createElement("li", null, "IIIF Images + A/V"), /* @__PURE__ */ React43.createElement("li", null, "Textual Annotations"))))), /* @__PURE__ */ React43.createElement("div", { className: "canopy-diagram__arrow", "aria-hidden": "true" }, /* @__PURE__ */ React43.createElement("span", { className: "canopy-diagram__arrow-line" }), /* @__PURE__ */ React43.createElement("span", { className: "canopy-diagram__arrow-head" })), /* @__PURE__ */ React43.createElement("section", { className: "canopy-diagram__section canopy-diagram__section--build" }, /* @__PURE__ */ React43.createElement("h3", null, "Canopy Build Process"), /* @__PURE__ */ React43.createElement("span", { className: "canopy-diagram__section-summary" }, "Canopy syncs manifests, page content, and annotations before bundling the site."), /* @__PURE__ */ React43.createElement("div", { className: "canopy-diagram__grid" }, /* @__PURE__ */ React43.createElement("article", null, /* @__PURE__ */ React43.createElement("h4", null, "Automated content"), /* @__PURE__ */ React43.createElement("ul", null, /* @__PURE__ */ React43.createElement("li", null, "105 manifests \u2192 105 work pages"), /* @__PURE__ */ React43.createElement("li", null, "One page per manifest"), /* @__PURE__ */ React43.createElement("li", null, "Customize page layout"))), /* @__PURE__ */ React43.createElement("article", null, /* @__PURE__ */ React43.createElement("h4", null, "Contextual content"), /* @__PURE__ */ React43.createElement("ul", null, /* @__PURE__ */ React43.createElement("li", null, "Markdown & MDX pages"), /* @__PURE__ */ React43.createElement("li", null, "Author narratives"), /* @__PURE__ */ React43.createElement("li", null, "Reference manifests inline"))), /* @__PURE__ */ React43.createElement("article", null, /* @__PURE__ */ React43.createElement("h4", null, "Search index"), /* @__PURE__ */ React43.createElement("ul", null, /* @__PURE__ */ React43.createElement("li", null, "Combines works + pages"), /* @__PURE__ */ React43.createElement("li", null, "Customize result layout"), /* @__PURE__ */ React43.createElement("li", null, "Optional annotations"))))), /* @__PURE__ */ React43.createElement("div", { className: "canopy-diagram__arrow", "aria-hidden": "true" }, /* @__PURE__ */ React43.createElement("span", { className: "canopy-diagram__arrow-line" }), /* @__PURE__ */ React43.createElement("span", { className: "canopy-diagram__arrow-head" })), /* @__PURE__ */ React43.createElement("section", { className: "canopy-diagram__section canopy-diagram__section--output" }, /* @__PURE__ */ React43.createElement("h3", null, "Static Digital Project"), /* @__PURE__ */ React43.createElement("span", { className: "canopy-diagram__section-summary" }, "The output is a lightweight bundle of HTML, CSS, JS, and JSON assets that can deploy anywhere."), /* @__PURE__ */ React43.createElement("div", { className: "canopy-diagram__grid" }, /* @__PURE__ */ React43.createElement("article", null, /* @__PURE__ */ React43.createElement("h4", null, "Work pages"), /* @__PURE__ */ React43.createElement("ul", null, /* @__PURE__ */ React43.createElement("li", null, "105 generated HTML pages"), /* @__PURE__ */ React43.createElement("li", null, "Each links back to source manifests"), /* @__PURE__ */ React43.createElement("li", null, "Styled with Canopy components"))), /* @__PURE__ */ React43.createElement("article", null, /* @__PURE__ */ React43.createElement("h4", null, "Custom pages"), /* @__PURE__ */ React43.createElement("ul", null, /* @__PURE__ */ React43.createElement("li", null, "Markdown & MDX-authored content"), /* @__PURE__ */ React43.createElement("li", null, "Reusable layouts for narratives"), /* @__PURE__ */ React43.createElement("li", null, "Embed IIIF media & interstitials"))), /* @__PURE__ */ React43.createElement("article", null, /* @__PURE__ */ React43.createElement("h4", null, "Search bundle"), /* @__PURE__ */ React43.createElement("ul", null, /* @__PURE__ */ React43.createElement("li", null, "Static FlexSearch index"), /* @__PURE__ */ React43.createElement("li", null, "Works + pages share records"), /* @__PURE__ */ React43.createElement("li", null, "Optional annotation dataset"))))));
4051
4344
  }
4052
4345
 
4053
4346
  // ui/src/docs/ThemeShowcase.jsx
4054
- import React43 from "react";
4347
+ import React44 from "react";
4055
4348
 
4056
4349
  // ../../node_modules/@radix-ui/colors/index.mjs
4057
4350
  var colors_exports = {};
@@ -7903,21 +8196,21 @@ var STEP_MAP = {
7903
8196
  800: 11,
7904
8197
  900: 12
7905
8198
  };
7906
- var Section = ({ title, description, children }) => /* @__PURE__ */ React43.createElement("div", { className: "canopy-theme-showcase__section" }, /* @__PURE__ */ React43.createElement("h3", { className: "canopy-theme-showcase__section-title" }, title), description ? /* @__PURE__ */ React43.createElement("p", { className: "canopy-theme-showcase__section-description" }, description) : null, children);
7907
- var ColorScaleRow = ({ label, prefix }) => /* @__PURE__ */ React43.createElement("div", { className: "canopy-theme-showcase__scale-row" }, /* @__PURE__ */ React43.createElement("div", { className: "canopy-theme-showcase__scale-label" }, /* @__PURE__ */ React43.createElement("strong", null, label)), /* @__PURE__ */ React43.createElement("div", { className: "canopy-theme-showcase__scale-track" }, COLOR_STOPS.map((stop) => /* @__PURE__ */ React43.createElement(
8199
+ var Section = ({ title, description, children }) => /* @__PURE__ */ React44.createElement("div", { className: "canopy-theme-showcase__section" }, /* @__PURE__ */ React44.createElement("h3", { className: "canopy-theme-showcase__section-title" }, title), description ? /* @__PURE__ */ React44.createElement("p", { className: "canopy-theme-showcase__section-description" }, description) : null, children);
8200
+ var ColorScaleRow = ({ label, prefix }) => /* @__PURE__ */ React44.createElement("div", { className: "canopy-theme-showcase__scale-row" }, /* @__PURE__ */ React44.createElement("div", { className: "canopy-theme-showcase__scale-label" }, /* @__PURE__ */ React44.createElement("strong", null, label)), /* @__PURE__ */ React44.createElement("div", { className: "canopy-theme-showcase__scale-track" }, COLOR_STOPS.map((stop) => /* @__PURE__ */ React44.createElement(
7908
8201
  "div",
7909
8202
  {
7910
8203
  key: `${label}-${stop}`,
7911
8204
  className: "canopy-theme-showcase__scale-stop"
7912
8205
  },
7913
- /* @__PURE__ */ React43.createElement(
8206
+ /* @__PURE__ */ React44.createElement(
7914
8207
  "span",
7915
8208
  {
7916
8209
  className: "canopy-theme-showcase__scale-chip",
7917
8210
  style: { backgroundColor: `var(${prefix}-${stop})` }
7918
8211
  }
7919
8212
  ),
7920
- /* @__PURE__ */ React43.createElement("span", { className: "canopy-theme-showcase__scale-token" }, stop)
8213
+ /* @__PURE__ */ React44.createElement("span", { className: "canopy-theme-showcase__scale-token" }, stop)
7921
8214
  ))));
7922
8215
  var AVAILABLE = new Set(
7923
8216
  Object.keys(colors_exports).filter(
@@ -7982,9 +8275,9 @@ var PREVIEW_DATA = buildPreviewData();
7982
8275
  function encodeJson(value) {
7983
8276
  return JSON.stringify(value).replace(/</g, "\\u003c");
7984
8277
  }
7985
- var ColorsLabeled = ({ colors, type, getRadixSwatch }) => /* @__PURE__ */ React43.createElement("div", { className: "canopy-theme-showcase__swatch-grid" }, colors.map((name) => {
8278
+ var ColorsLabeled = ({ colors, type, getRadixSwatch }) => /* @__PURE__ */ React44.createElement("div", { className: "canopy-theme-showcase__swatch-grid" }, colors.map((name) => {
7986
8279
  const colorValue = getRadixSwatch(name);
7987
- return /* @__PURE__ */ React43.createElement(
8280
+ return /* @__PURE__ */ React44.createElement(
7988
8281
  "button",
7989
8282
  {
7990
8283
  key: `${type}-${name}`,
@@ -7995,14 +8288,14 @@ var ColorsLabeled = ({ colors, type, getRadixSwatch }) => /* @__PURE__ */ React4
7995
8288
  "data-theme-swatch-value": name,
7996
8289
  "aria-pressed": "false"
7997
8290
  },
7998
- /* @__PURE__ */ React43.createElement(
8291
+ /* @__PURE__ */ React44.createElement(
7999
8292
  "span",
8000
8293
  {
8001
8294
  className: "canopy-theme-showcase__swatch-chip",
8002
8295
  style: { background: colorValue || "var(--color-gray-200)" }
8003
8296
  }
8004
8297
  ),
8005
- /* @__PURE__ */ React43.createElement("span", { className: "canopy-theme-showcase__swatch-label" }, name)
8298
+ /* @__PURE__ */ React44.createElement("span", { className: "canopy-theme-showcase__swatch-label" }, name)
8006
8299
  );
8007
8300
  }));
8008
8301
  function ThemeShowcase() {
@@ -8162,7 +8455,7 @@ function ThemeShowcase() {
8162
8455
  .canopy-theme-showcase__swatch-controls { display: none; }
8163
8456
  .canopy-theme-showcase__clear-button { display: none; }
8164
8457
  `;
8165
- return /* @__PURE__ */ React43.createElement("div", { className: "canopy-theme-showcase", "data-theme-showcase": true }, /* @__PURE__ */ React43.createElement("style", { dangerouslySetInnerHTML: { __html: styles } }), /* @__PURE__ */ React43.createElement(
8458
+ return /* @__PURE__ */ React44.createElement("div", { className: "canopy-theme-showcase", "data-theme-showcase": true }, /* @__PURE__ */ React44.createElement("style", { dangerouslySetInnerHTML: { __html: styles } }), /* @__PURE__ */ React44.createElement(
8166
8459
  "div",
8167
8460
  {
8168
8461
  style: {
@@ -8173,18 +8466,18 @@ function ThemeShowcase() {
8173
8466
  marginBottom: "1rem"
8174
8467
  }
8175
8468
  },
8176
- /* @__PURE__ */ React43.createElement(
8469
+ /* @__PURE__ */ React44.createElement(
8177
8470
  Section,
8178
8471
  {
8179
8472
  title: "Appearance",
8180
8473
  description: "Pick the base light or dark mode for the theme preview."
8181
8474
  },
8182
- /* @__PURE__ */ React43.createElement("div", { className: "canopy-theme-showcase__appearance-buttons" }, ["light", "dark"].map((mode) => {
8475
+ /* @__PURE__ */ React44.createElement("div", { className: "canopy-theme-showcase__appearance-buttons" }, ["light", "dark"].map((mode) => {
8183
8476
  const label = `${mode.charAt(0).toUpperCase()}${mode.slice(1)}`;
8184
8477
  const baseClass = "canopy-theme-showcase__appearance-button";
8185
8478
  const isDefault = mode === DEFAULTS.appearance;
8186
8479
  const className = isDefault ? `${baseClass} is-active` : baseClass;
8187
- return /* @__PURE__ */ React43.createElement(
8480
+ return /* @__PURE__ */ React44.createElement(
8188
8481
  "button",
8189
8482
  {
8190
8483
  key: mode,
@@ -8196,7 +8489,7 @@ function ThemeShowcase() {
8196
8489
  );
8197
8490
  }))
8198
8491
  ),
8199
- /* @__PURE__ */ React43.createElement(
8492
+ /* @__PURE__ */ React44.createElement(
8200
8493
  "button",
8201
8494
  {
8202
8495
  type: "button",
@@ -8205,13 +8498,13 @@ function ThemeShowcase() {
8205
8498
  },
8206
8499
  "Reset"
8207
8500
  )
8208
- ), /* @__PURE__ */ React43.createElement(
8501
+ ), /* @__PURE__ */ React44.createElement(
8209
8502
  Section,
8210
8503
  {
8211
8504
  title: "Color scales",
8212
8505
  description: "Accent and gray ramps from the active theme."
8213
8506
  },
8214
- /* @__PURE__ */ React43.createElement("div", { style: { display: "flex", flexDirection: "column", gap: "1.5rem" } }, COLOR_SCALES.map((scale) => /* @__PURE__ */ React43.createElement(
8507
+ /* @__PURE__ */ React44.createElement("div", { style: { display: "flex", flexDirection: "column", gap: "1.5rem" } }, COLOR_SCALES.map((scale) => /* @__PURE__ */ React44.createElement(
8215
8508
  ColorScaleRow,
8216
8509
  {
8217
8510
  key: scale.label,
@@ -8219,13 +8512,13 @@ function ThemeShowcase() {
8219
8512
  prefix: scale.prefix
8220
8513
  }
8221
8514
  )))
8222
- ), /* @__PURE__ */ React43.createElement(
8515
+ ), /* @__PURE__ */ React44.createElement(
8223
8516
  Section,
8224
8517
  {
8225
8518
  title: "Accent color palette options",
8226
8519
  description: "Click a swatch to temporarily override the accent palette."
8227
8520
  },
8228
- /* @__PURE__ */ React43.createElement(
8521
+ /* @__PURE__ */ React44.createElement(
8229
8522
  ColorsLabeled,
8230
8523
  {
8231
8524
  colors: accentColors,
@@ -8233,13 +8526,13 @@ function ThemeShowcase() {
8233
8526
  getRadixSwatch
8234
8527
  }
8235
8528
  )
8236
- ), /* @__PURE__ */ React43.createElement(
8529
+ ), /* @__PURE__ */ React44.createElement(
8237
8530
  Section,
8238
8531
  {
8239
8532
  title: "Gray color palette options",
8240
8533
  description: "Click a swatch to preview the neutral ramp for surfaces and text."
8241
8534
  },
8242
- /* @__PURE__ */ React43.createElement(
8535
+ /* @__PURE__ */ React44.createElement(
8243
8536
  ColorsLabeled,
8244
8537
  {
8245
8538
  colors: grayColors,
@@ -8247,7 +8540,7 @@ function ThemeShowcase() {
8247
8540
  getRadixSwatch
8248
8541
  }
8249
8542
  )
8250
- ), /* @__PURE__ */ React43.createElement(
8543
+ ), /* @__PURE__ */ React44.createElement(
8251
8544
  "script",
8252
8545
  {
8253
8546
  type: "application/json",