@canopy-iiif/app 1.5.9 → 1.5.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/ui/dist/index.mjs CHANGED
@@ -451,7 +451,7 @@ function ButtonWrapper({
451
451
  }
452
452
 
453
453
  // ui/src/layout/CanopyHeader.jsx
454
- import React14 from "react";
454
+ import React15 from "react";
455
455
 
456
456
  // ui/src/search/SearchPanel.jsx
457
457
  import React11 from "react";
@@ -764,6 +764,130 @@ function CanopyModal(props = {}) {
764
764
  )) : null, children)));
765
765
  }
766
766
 
767
+ // ui/src/layout/NavigationTree.jsx
768
+ import React14 from "react";
769
+ function normalizeDepth(depth) {
770
+ if (typeof depth !== "number") return 0;
771
+ return Math.max(0, Math.min(5, depth));
772
+ }
773
+ function NavigationTreeList({ nodes, depth, parentKey }) {
774
+ if (!Array.isArray(nodes) || !nodes.length) return null;
775
+ const listClasses = ["canopy-nav-tree__list"];
776
+ if (depth > 0) listClasses.push("canopy-nav-tree__list--nested");
777
+ return /* @__PURE__ */ React14.createElement("ul", { className: listClasses.join(" "), role: "list" }, nodes.map((node, index) => /* @__PURE__ */ React14.createElement(
778
+ NavigationTreeItem,
779
+ {
780
+ key: node.slug || node.href || node.title || `${parentKey}-${index}`,
781
+ node,
782
+ depth,
783
+ nodeKey: `${parentKey}-${index}`
784
+ }
785
+ )));
786
+ }
787
+ function NavigationTreeItem({ node, depth, nodeKey }) {
788
+ if (!node) return null;
789
+ const hasChildren = Array.isArray(node.children) && node.children.length > 0;
790
+ const isRoadmap = !!node.isRoadmap;
791
+ const isInteractive = !!(node.href && !isRoadmap);
792
+ const Tag = isInteractive ? "a" : "span";
793
+ const depthClass = `depth-${normalizeDepth(depth + 1)}`;
794
+ const classes = ["canopy-nav-tree__link", depthClass];
795
+ if (!isInteractive && !isRoadmap) classes.push("is-label");
796
+ if (isRoadmap) classes.push("is-disabled");
797
+ if (node.isActive) classes.push("is-active");
798
+ const isRootLevel = depth < 0;
799
+ const panelId = hasChildren ? `canopy-section-${nodeKey}` : null;
800
+ const allowToggle = hasChildren && !isRootLevel;
801
+ const defaultExpanded = allowToggle ? !!node.isExpanded : true;
802
+ const toggleLabel = node.title ? `Toggle ${node.title} menu` : "Toggle section menu";
803
+ return /* @__PURE__ */ React14.createElement(
804
+ "li",
805
+ {
806
+ className: "canopy-nav-tree__item",
807
+ "data-depth": depth,
808
+ "data-canopy-nav-item": allowToggle ? "true" : void 0,
809
+ "data-expanded": allowToggle ? defaultExpanded ? "true" : "false" : void 0,
810
+ "data-default-expanded": allowToggle && defaultExpanded ? "true" : void 0
811
+ },
812
+ /* @__PURE__ */ React14.createElement("div", { className: "canopy-nav-tree__row" }, /* @__PURE__ */ React14.createElement(
813
+ Tag,
814
+ {
815
+ className: classes.join(" "),
816
+ href: isInteractive ? node.href : void 0,
817
+ "aria-current": node.isActive ? "page" : void 0,
818
+ tabIndex: isInteractive ? void 0 : -1
819
+ },
820
+ node.title || node.slug,
821
+ isRoadmap ? /* @__PURE__ */ React14.createElement("span", { className: "canopy-nav-tree__badge" }, "Roadmap") : null
822
+ ), allowToggle ? /* @__PURE__ */ React14.createElement(
823
+ "button",
824
+ {
825
+ type: "button",
826
+ className: "canopy-nav-tree__toggle",
827
+ "aria-expanded": defaultExpanded ? "true" : "false",
828
+ "aria-controls": panelId || void 0,
829
+ "aria-label": toggleLabel,
830
+ "data-canopy-nav-item-toggle": panelId || void 0
831
+ },
832
+ /* @__PURE__ */ React14.createElement(
833
+ "svg",
834
+ {
835
+ xmlns: "http://www.w3.org/2000/svg",
836
+ viewBox: "0 0 24 24",
837
+ fill: "none",
838
+ stroke: "currentColor",
839
+ strokeWidth: "1.5",
840
+ className: "canopy-nav-tree__toggle-icon"
841
+ },
842
+ /* @__PURE__ */ React14.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M5 9l7 7 7-7" })
843
+ ),
844
+ /* @__PURE__ */ React14.createElement("span", { className: "sr-only" }, toggleLabel)
845
+ ) : null),
846
+ hasChildren ? /* @__PURE__ */ React14.createElement(
847
+ "div",
848
+ {
849
+ id: panelId || void 0,
850
+ className: "canopy-nav-tree__children",
851
+ "aria-hidden": allowToggle ? defaultExpanded ? "false" : "true" : "false",
852
+ hidden: allowToggle ? !defaultExpanded : void 0
853
+ },
854
+ /* @__PURE__ */ React14.createElement(
855
+ NavigationTreeList,
856
+ {
857
+ nodes: node.children,
858
+ depth: depth + 1,
859
+ parentKey: nodeKey
860
+ }
861
+ )
862
+ ) : null
863
+ );
864
+ }
865
+ function NavigationTree({
866
+ root,
867
+ className = "",
868
+ parentKey = "nav",
869
+ includeRoot = false,
870
+ heading,
871
+ headingClassName = "canopy-nav-tree__heading",
872
+ component: Component = "div",
873
+ ...rest
874
+ }) {
875
+ if (!root) return null;
876
+ const nodes = includeRoot ? [root] : root.children;
877
+ if (!Array.isArray(nodes) || !nodes.length) return null;
878
+ const combinedClassName = ["canopy-nav-tree", className].filter(Boolean).join(" ");
879
+ return /* @__PURE__ */ React14.createElement(
880
+ Component,
881
+ {
882
+ className: combinedClassName,
883
+ "data-canopy-nav-tree": "true",
884
+ ...rest
885
+ },
886
+ heading ? /* @__PURE__ */ React14.createElement("div", { className: headingClassName }, heading) : null,
887
+ /* @__PURE__ */ React14.createElement(NavigationTreeList, { nodes, depth: includeRoot ? -1 : 0, parentKey })
888
+ );
889
+ }
890
+
767
891
  // ui/src/layout/CanopyHeader.jsx
768
892
  function HeaderScript() {
769
893
  const code = `
@@ -774,11 +898,24 @@ function HeaderScript() {
774
898
  var body = doc.body;
775
899
  var root = doc.documentElement;
776
900
 
901
+ function desktopBreakpointQuery() {
902
+ if (typeof window === 'undefined') return '(min-width: 70rem)';
903
+ try {
904
+ var styles = window.getComputedStyle ? window.getComputedStyle(root) : null;
905
+ var value = styles ? styles.getPropertyValue('--canopy-desktop-breakpoint') : '';
906
+ if (typeof value === 'string') value = value.trim();
907
+ if (!value) value = '70rem';
908
+ return '(min-width: ' + value + ')';
909
+ } catch (error) {
910
+ return '(min-width: 70rem)';
911
+ }
912
+ }
913
+
777
914
  function ready(fn) {
778
915
  if (doc.readyState === 'loading') {
779
916
  doc.addEventListener('DOMContentLoaded', fn, { once: true });
780
917
  } else {
781
- fn();
918
+ fn();
782
919
  }
783
920
  }
784
921
 
@@ -788,6 +925,8 @@ function HeaderScript() {
788
925
 
789
926
  var NAV_ATTR = 'data-mobile-nav';
790
927
  var SEARCH_ATTR = 'data-mobile-search';
928
+ var NAV_ITEM_ATTR = 'data-canopy-nav-item';
929
+ var NAV_ITEM_TOGGLE_ATTR = 'data-canopy-nav-item-toggle';
791
930
 
792
931
  function modalFor(type) {
793
932
  return doc.querySelector('[data-canopy-modal="' + type + '"]');
@@ -869,6 +1008,51 @@ function HeaderScript() {
869
1008
  });
870
1009
  }
871
1010
 
1011
+ function forEachNavTree(scope, fn) {
1012
+ if (typeof fn !== 'function') return;
1013
+ var rootNode = scope || doc;
1014
+ var trees = rootNode.querySelectorAll('[data-canopy-nav-tree]');
1015
+ each(trees, function (tree) {
1016
+ fn(tree);
1017
+ });
1018
+ }
1019
+
1020
+ function resetNavItemToggles(scope) {
1021
+ forEachNavTree(scope, function (tree) {
1022
+ var toggles = tree.querySelectorAll('[' + NAV_ITEM_TOGGLE_ATTR + ']');
1023
+ each(toggles, function (btn) {
1024
+ btn.setAttribute('aria-expanded', 'false');
1025
+ var targetId = btn.getAttribute(NAV_ITEM_TOGGLE_ATTR);
1026
+ var panel = targetId ? doc.getElementById(targetId) : null;
1027
+ var parent = btn.closest('[' + NAV_ITEM_ATTR + ']');
1028
+ if (panel) {
1029
+ panel.hidden = true;
1030
+ panel.setAttribute('aria-hidden', 'true');
1031
+ panel.setAttribute('hidden', '');
1032
+ }
1033
+ if (parent) parent.setAttribute('data-expanded', 'false');
1034
+ });
1035
+ });
1036
+ }
1037
+
1038
+ function applyDefaultNavItemState(scope) {
1039
+ forEachNavTree(scope, function (tree) {
1040
+ var defaults = tree.querySelectorAll('[data-default-expanded="true"]');
1041
+ each(defaults, function (item) {
1042
+ var toggle = item.querySelector('[' + NAV_ITEM_TOGGLE_ATTR + ']');
1043
+ var targetId = toggle ? toggle.getAttribute(NAV_ITEM_TOGGLE_ATTR) : null;
1044
+ var panel = targetId ? doc.getElementById(targetId) : null;
1045
+ item.setAttribute('data-expanded', 'true');
1046
+ if (toggle) toggle.setAttribute('aria-expanded', 'true');
1047
+ if (panel) {
1048
+ panel.hidden = false;
1049
+ panel.removeAttribute('hidden');
1050
+ panel.setAttribute('aria-hidden', 'false');
1051
+ }
1052
+ });
1053
+ });
1054
+ }
1055
+
872
1056
  function setState(type, next) {
873
1057
  if (type === 'nav') header.setAttribute(NAV_ATTR, next);
874
1058
  if (type === 'search') header.setAttribute(SEARCH_ATTR, next);
@@ -876,6 +1060,13 @@ function HeaderScript() {
876
1060
  var navOpen = header.getAttribute(NAV_ATTR) === 'open';
877
1061
  var searchOpen = header.getAttribute(SEARCH_ATTR) === 'open';
878
1062
  lockScroll(navOpen || searchOpen);
1063
+ if (type === 'nav') {
1064
+ if (next !== 'open') {
1065
+ resetNavItemToggles(modalFor('nav'));
1066
+ } else {
1067
+ applyDefaultNavItemState(modalFor('nav'));
1068
+ }
1069
+ }
879
1070
  }
880
1071
 
881
1072
  function toggle(type, force) {
@@ -888,6 +1079,35 @@ function HeaderScript() {
888
1079
  if (type === 'nav' && shouldOpen) focusNavMenu();
889
1080
  }
890
1081
 
1082
+ function setupNavItemToggles() {
1083
+ var toggles = doc.querySelectorAll('[' + NAV_ITEM_TOGGLE_ATTR + ']');
1084
+ each(toggles, function (btn) {
1085
+ if (btn.__canopyNavReady) return;
1086
+ btn.__canopyNavReady = true;
1087
+ btn.addEventListener('click', function (event) {
1088
+ event.preventDefault();
1089
+ event.stopPropagation();
1090
+ var targetId = btn.getAttribute(NAV_ITEM_TOGGLE_ATTR);
1091
+ if (!targetId) return;
1092
+ var panel = doc.getElementById(targetId);
1093
+ var parent = btn.closest('[' + NAV_ITEM_ATTR + ']');
1094
+ var expanded = btn.getAttribute('aria-expanded') === 'true';
1095
+ var next = !expanded;
1096
+ btn.setAttribute('aria-expanded', next ? 'true' : 'false');
1097
+ if (panel) {
1098
+ panel.hidden = !next;
1099
+ panel.setAttribute('aria-hidden', next ? 'false' : 'true');
1100
+ if (next) {
1101
+ panel.removeAttribute('hidden');
1102
+ } else {
1103
+ panel.setAttribute('hidden', '');
1104
+ }
1105
+ }
1106
+ if (parent) parent.setAttribute('data-expanded', next ? 'true' : 'false');
1107
+ });
1108
+ });
1109
+ }
1110
+
891
1111
  each(header.querySelectorAll('[data-canopy-header-toggle]'), function (btn) {
892
1112
  btn.addEventListener('click', function (event) {
893
1113
  event.preventDefault();
@@ -935,7 +1155,7 @@ function HeaderScript() {
935
1155
  toggle('search', false);
936
1156
  });
937
1157
 
938
- var mq = window.matchMedia('(min-width: 48rem)');
1158
+ var mq = window.matchMedia(desktopBreakpointQuery());
939
1159
  function syncDesktopState() {
940
1160
  if (mq.matches) {
941
1161
  setState('nav', 'closed');
@@ -952,11 +1172,13 @@ function HeaderScript() {
952
1172
  mq.addListener(syncDesktopState);
953
1173
  }
954
1174
 
1175
+ setupNavItemToggles();
1176
+ applyDefaultNavItemState(null);
955
1177
  syncDesktopState();
956
1178
  });
957
1179
  })();
958
1180
  `;
959
- return /* @__PURE__ */ React14.createElement(
1181
+ return /* @__PURE__ */ React15.createElement(
960
1182
  "script",
961
1183
  {
962
1184
  dangerouslySetInnerHTML: {
@@ -975,7 +1197,7 @@ function getSharedRoot() {
975
1197
  function getSafePageContext() {
976
1198
  const root = getSharedRoot();
977
1199
  if (root && root[CONTEXT_KEY]) return root[CONTEXT_KEY];
978
- const ctx = React14.createContext({ navigation: null, page: null });
1200
+ const ctx = React15.createContext({ navigation: null, page: null });
979
1201
  if (root) root[CONTEXT_KEY] = ctx;
980
1202
  return ctx;
981
1203
  }
@@ -985,29 +1207,55 @@ function ensureArray(navLinks) {
985
1207
  (link) => link && typeof link === "object" && typeof link.href === "string"
986
1208
  );
987
1209
  }
988
- function SectionNavList({ root }) {
989
- if (!root || !Array.isArray(root.children) || !root.children.length) return null;
990
- return /* @__PURE__ */ React14.createElement("ul", { className: "canopy-modal__section-list", role: "list" }, root.children.map((node) => /* @__PURE__ */ React14.createElement(SectionNavItem, { key: node.slug || node.href || node.title, node, depth: 0 })));
1210
+ function normalizeHref(href) {
1211
+ if (typeof href !== "string") return "";
1212
+ let next = href.trim();
1213
+ if (!next) return "";
1214
+ try {
1215
+ const parsed = new URL(next, "https://canopy.local");
1216
+ next = parsed.pathname || "/";
1217
+ } catch (_) {
1218
+ next = next.replace(/[?#].*$/, "");
1219
+ }
1220
+ next = next.replace(/[?#].*$/, "");
1221
+ if (next.length > 1) {
1222
+ next = next.replace(/\/+$/, "");
1223
+ }
1224
+ if (!next) return "/";
1225
+ return next;
991
1226
  }
992
- function SectionNavItem({ node, depth }) {
993
- if (!node) return null;
994
- const hasChildren = Array.isArray(node.children) && node.children.length > 0;
995
- const Tag = node.href ? "a" : "span";
996
- const classes = [
997
- "canopy-modal__section-link",
998
- `depth-${Math.min(5, Math.max(0, depth + 1))}`
999
- ];
1000
- if (!node.href) classes.push("is-label");
1001
- if (node.isActive) classes.push("is-active");
1002
- return /* @__PURE__ */ React14.createElement("li", { className: "canopy-modal__section-item", "data-depth": depth }, /* @__PURE__ */ React14.createElement(
1003
- Tag,
1004
- {
1005
- className: classes.join(" "),
1006
- href: node.href || void 0,
1007
- "aria-current": node.isActive ? "page" : void 0
1008
- },
1009
- node.title || node.slug
1010
- ), hasChildren ? /* @__PURE__ */ React14.createElement("ul", { className: "canopy-modal__section-list canopy-modal__section-list--nested", role: "list" }, node.children.map((child) => /* @__PURE__ */ React14.createElement(SectionNavItem, { key: child.slug || child.href || child.title, node: child, depth: depth + 1 }))) : null);
1227
+ function doesLinkMatchSection(linkHref, sectionNavigation) {
1228
+ if (!sectionNavigation || !sectionNavigation.root || !linkHref) return false;
1229
+ const normalizedLink = normalizeHref(linkHref);
1230
+ if (!normalizedLink) return false;
1231
+ const root = sectionNavigation.root;
1232
+ if (typeof root.href === "string" && normalizeHref(root.href) === normalizedLink) {
1233
+ return true;
1234
+ }
1235
+ if (root.slug) {
1236
+ const slugPath = normalizeHref(`/${root.slug}`);
1237
+ if (slugPath && normalizedLink === slugPath) {
1238
+ return true;
1239
+ }
1240
+ }
1241
+ return false;
1242
+ }
1243
+ function rootSegmentFromHref(href) {
1244
+ const normalized = normalizeHref(href);
1245
+ if (!normalized || normalized === "/") return "";
1246
+ const trimmed = normalized.replace(/^\/+/, "");
1247
+ return trimmed.split("/")[0] || "";
1248
+ }
1249
+ function getLinkNavigationData(link, navigationRoots, sectionNavigation) {
1250
+ if (!link || typeof link.href !== "string") return null;
1251
+ const segment = rootSegmentFromHref(link.href);
1252
+ if (navigationRoots && segment && navigationRoots[segment]) {
1253
+ return navigationRoots[segment];
1254
+ }
1255
+ if (sectionNavigation && doesLinkMatchSection(link.href, sectionNavigation)) {
1256
+ return sectionNavigation;
1257
+ }
1258
+ return null;
1011
1259
  }
1012
1260
  function CanopyHeader(props = {}) {
1013
1261
  const {
@@ -1021,20 +1269,34 @@ function CanopyHeader(props = {}) {
1021
1269
  } = props;
1022
1270
  const navLinks = ensureArray(navLinksProp);
1023
1271
  const PageContext = getSafePageContext();
1024
- const context = React14.useContext(PageContext);
1025
- const sectionNavigation = context && context.navigation && context.navigation.root ? context.navigation : null;
1272
+ const context = React15.useContext(PageContext);
1273
+ const contextNavigation = context && context.navigation ? context.navigation : null;
1274
+ const sectionNavigation = contextNavigation && contextNavigation.root ? contextNavigation : null;
1275
+ const navigationRoots = contextNavigation && contextNavigation.allRoots ? contextNavigation.allRoots : null;
1026
1276
  const sectionHeading = sectionNavigation && sectionNavigation.title || (sectionNavigation && sectionNavigation.root ? sectionNavigation.root.title : "");
1027
1277
  const hasSectionNav = !!(sectionNavigation && sectionNavigation.root && Array.isArray(sectionNavigation.root.children) && sectionNavigation.root.children.length);
1028
1278
  const sectionLabel = sectionHeading ? `More in ${sectionHeading}` : "More in this section";
1029
1279
  const sectionAriaLabel = sectionHeading ? `${sectionHeading} section navigation` : "Section navigation";
1030
- return /* @__PURE__ */ React14.createElement(React14.Fragment, null, /* @__PURE__ */ React14.createElement(
1280
+ const defaultSectionLabel = sectionLabel;
1281
+ const defaultSectionAriaLabel = sectionAriaLabel;
1282
+ const shouldAttachSectionNav = (link) => {
1283
+ const navData = getLinkNavigationData(
1284
+ link,
1285
+ navigationRoots,
1286
+ sectionNavigation
1287
+ );
1288
+ const rootNode = navData && navData.root;
1289
+ return !!(rootNode && Array.isArray(rootNode.children) && rootNode.children.length);
1290
+ };
1291
+ const hasIntegratedSectionNav = navLinks.some(shouldAttachSectionNav);
1292
+ return /* @__PURE__ */ React15.createElement(React15.Fragment, null, /* @__PURE__ */ React15.createElement(
1031
1293
  "header",
1032
1294
  {
1033
1295
  className: "canopy-header",
1034
1296
  "data-mobile-nav": "closed",
1035
1297
  "data-mobile-search": "closed"
1036
1298
  },
1037
- /* @__PURE__ */ React14.createElement("div", { className: "canopy-header__brand" }, /* @__PURE__ */ React14.createElement(
1299
+ /* @__PURE__ */ React15.createElement("div", { className: "canopy-header__brand" }, /* @__PURE__ */ React15.createElement(
1038
1300
  CanopyBrand,
1039
1301
  {
1040
1302
  label: title,
@@ -1043,7 +1305,7 @@ function CanopyHeader(props = {}) {
1043
1305
  Logo: SiteLogo
1044
1306
  }
1045
1307
  )),
1046
- /* @__PURE__ */ React14.createElement("div", { className: "canopy-header__desktop-search" }, /* @__PURE__ */ React14.createElement(
1308
+ /* @__PURE__ */ React15.createElement("div", { className: "canopy-header__desktop-search" }, /* @__PURE__ */ React15.createElement(
1047
1309
  SearchPanel,
1048
1310
  {
1049
1311
  label: searchLabel,
@@ -1051,13 +1313,13 @@ function CanopyHeader(props = {}) {
1051
1313
  placeholder: searchPlaceholder
1052
1314
  }
1053
1315
  )),
1054
- /* @__PURE__ */ React14.createElement(
1316
+ /* @__PURE__ */ React15.createElement(
1055
1317
  "nav",
1056
1318
  {
1057
1319
  className: "canopy-nav-links canopy-header__desktop-nav",
1058
1320
  "aria-label": "Primary navigation"
1059
1321
  },
1060
- navLinks.map((link) => /* @__PURE__ */ React14.createElement(
1322
+ navLinks.map((link) => /* @__PURE__ */ React15.createElement(
1061
1323
  "a",
1062
1324
  {
1063
1325
  key: link.href,
@@ -1067,7 +1329,7 @@ function CanopyHeader(props = {}) {
1067
1329
  link.label || link.href
1068
1330
  ))
1069
1331
  ),
1070
- /* @__PURE__ */ React14.createElement("div", { className: "canopy-header__actions" }, /* @__PURE__ */ React14.createElement(
1332
+ /* @__PURE__ */ React15.createElement("div", { className: "canopy-header__actions" }, /* @__PURE__ */ React15.createElement(
1071
1333
  "button",
1072
1334
  {
1073
1335
  type: "button",
@@ -1077,7 +1339,7 @@ function CanopyHeader(props = {}) {
1077
1339
  "aria-expanded": "false",
1078
1340
  "data-canopy-header-toggle": "search"
1079
1341
  },
1080
- /* @__PURE__ */ React14.createElement(
1342
+ /* @__PURE__ */ React15.createElement(
1081
1343
  "svg",
1082
1344
  {
1083
1345
  xmlns: "http://www.w3.org/2000/svg",
@@ -1087,7 +1349,7 @@ function CanopyHeader(props = {}) {
1087
1349
  strokeWidth: "1.5",
1088
1350
  className: "canopy-header__search-icon"
1089
1351
  },
1090
- /* @__PURE__ */ React14.createElement(
1352
+ /* @__PURE__ */ React15.createElement(
1091
1353
  "path",
1092
1354
  {
1093
1355
  strokeLinecap: "round",
@@ -1096,7 +1358,7 @@ function CanopyHeader(props = {}) {
1096
1358
  }
1097
1359
  )
1098
1360
  )
1099
- ), /* @__PURE__ */ React14.createElement(
1361
+ ), /* @__PURE__ */ React15.createElement(
1100
1362
  "button",
1101
1363
  {
1102
1364
  type: "button",
@@ -1106,7 +1368,7 @@ function CanopyHeader(props = {}) {
1106
1368
  "aria-expanded": "false",
1107
1369
  "data-canopy-header-toggle": "nav"
1108
1370
  },
1109
- /* @__PURE__ */ React14.createElement(
1371
+ /* @__PURE__ */ React15.createElement(
1110
1372
  "svg",
1111
1373
  {
1112
1374
  xmlns: "http://www.w3.org/2000/svg",
@@ -1116,7 +1378,7 @@ function CanopyHeader(props = {}) {
1116
1378
  stroke: "currentColor",
1117
1379
  className: "canopy-header__menu-icon"
1118
1380
  },
1119
- /* @__PURE__ */ React14.createElement(
1381
+ /* @__PURE__ */ React15.createElement(
1120
1382
  "path",
1121
1383
  {
1122
1384
  strokeLinecap: "round",
@@ -1126,7 +1388,7 @@ function CanopyHeader(props = {}) {
1126
1388
  )
1127
1389
  )
1128
1390
  ))
1129
- ), /* @__PURE__ */ React14.createElement(
1391
+ ), /* @__PURE__ */ React15.createElement(
1130
1392
  CanopyModal,
1131
1393
  {
1132
1394
  id: "canopy-modal-nav",
@@ -1138,32 +1400,90 @@ function CanopyHeader(props = {}) {
1138
1400
  closeLabel: "Close navigation",
1139
1401
  closeDataAttr: "nav"
1140
1402
  },
1141
- /* @__PURE__ */ React14.createElement(
1403
+ /* @__PURE__ */ React15.createElement(
1142
1404
  "nav",
1143
1405
  {
1144
1406
  className: "canopy-nav-links canopy-modal__nav",
1145
1407
  "aria-label": "Primary navigation"
1146
1408
  },
1147
- navLinks.map((link) => /* @__PURE__ */ React14.createElement(
1148
- "a",
1149
- {
1150
- key: link.href,
1151
- href: link.href,
1152
- "aria-current": link.isActive ? "page" : void 0
1153
- },
1154
- link.label || link.href
1155
- ))
1409
+ /* @__PURE__ */ React15.createElement("ul", { className: "canopy-modal__nav-list", role: "list" }, navLinks.map((link, index) => {
1410
+ const navData = getLinkNavigationData(
1411
+ link,
1412
+ navigationRoots,
1413
+ sectionNavigation
1414
+ );
1415
+ const navRoot = navData && navData.root ? navData.root : null;
1416
+ const hasChildren = !!(navRoot && Array.isArray(navRoot.children) && navRoot.children.length);
1417
+ const nestedId = hasChildren ? `canopy-modal-section-${index}` : null;
1418
+ const toggleLabel = link.label ? `Toggle ${link.label} menu` : "Toggle section menu";
1419
+ const defaultExpanded = hasChildren && !!navRoot.isExpanded;
1420
+ return /* @__PURE__ */ React15.createElement(
1421
+ "li",
1422
+ {
1423
+ className: "canopy-modal__nav-item",
1424
+ key: link.href,
1425
+ "data-canopy-nav-item": hasChildren ? "true" : void 0,
1426
+ "data-expanded": defaultExpanded ? "true" : "false",
1427
+ "data-default-expanded": defaultExpanded ? "true" : void 0
1428
+ },
1429
+ /* @__PURE__ */ React15.createElement("div", { className: "canopy-modal__nav-row" }, /* @__PURE__ */ React15.createElement("a", { href: link.href }, link.label || link.href), hasChildren ? /* @__PURE__ */ React15.createElement(
1430
+ "button",
1431
+ {
1432
+ type: "button",
1433
+ className: "canopy-modal__nav-toggle",
1434
+ "aria-expanded": defaultExpanded ? "true" : "false",
1435
+ "aria-controls": nestedId || void 0,
1436
+ "aria-label": toggleLabel,
1437
+ "data-canopy-nav-item-toggle": nestedId || void 0
1438
+ },
1439
+ /* @__PURE__ */ React15.createElement(
1440
+ "svg",
1441
+ {
1442
+ xmlns: "http://www.w3.org/2000/svg",
1443
+ viewBox: "0 0 24 24",
1444
+ fill: "none",
1445
+ stroke: "currentColor",
1446
+ strokeWidth: "1.5",
1447
+ className: "canopy-modal__nav-toggle-icon"
1448
+ },
1449
+ /* @__PURE__ */ React15.createElement(
1450
+ "path",
1451
+ {
1452
+ strokeLinecap: "round",
1453
+ strokeLinejoin: "round",
1454
+ d: "M5 9l7 7 7-7"
1455
+ }
1456
+ )
1457
+ ),
1458
+ /* @__PURE__ */ React15.createElement("span", { className: "sr-only" }, toggleLabel)
1459
+ ) : null),
1460
+ hasChildren ? /* @__PURE__ */ React15.createElement(
1461
+ NavigationTree,
1462
+ {
1463
+ root: navRoot,
1464
+ parentKey: navData && navData.rootSegment ? navData.rootSegment : `root-${index}`,
1465
+ component: "div",
1466
+ className: "canopy-modal__section-nav canopy-modal__section-nav--nested",
1467
+ "aria-label": navData && navData.title ? `${navData.title} section navigation` : defaultSectionAriaLabel,
1468
+ "aria-hidden": defaultExpanded ? "false" : "true",
1469
+ hidden: !defaultExpanded,
1470
+ id: nestedId || void 0
1471
+ }
1472
+ ) : null
1473
+ );
1474
+ }))
1156
1475
  ),
1157
- hasSectionNav ? /* @__PURE__ */ React14.createElement(
1158
- "nav",
1476
+ hasSectionNav && !hasIntegratedSectionNav ? /* @__PURE__ */ React15.createElement(
1477
+ NavigationTree,
1159
1478
  {
1479
+ root: sectionNavigation.root,
1480
+ component: "nav",
1160
1481
  className: "canopy-modal__section-nav",
1161
- "aria-label": sectionAriaLabel
1162
- },
1163
- /* @__PURE__ */ React14.createElement("div", { className: "canopy-modal__section-label" }, sectionLabel),
1164
- /* @__PURE__ */ React14.createElement(SectionNavList, { root: sectionNavigation.root })
1482
+ "aria-label": defaultSectionAriaLabel,
1483
+ parentKey: "fallback-nav"
1484
+ }
1165
1485
  ) : null
1166
- ), /* @__PURE__ */ React14.createElement(
1486
+ ), /* @__PURE__ */ React15.createElement(
1167
1487
  CanopyModal,
1168
1488
  {
1169
1489
  id: "canopy-modal-search",
@@ -1176,7 +1496,7 @@ function CanopyHeader(props = {}) {
1176
1496
  closeDataAttr: "search",
1177
1497
  bodyClassName: "canopy-modal__body--search"
1178
1498
  },
1179
- /* @__PURE__ */ React14.createElement(
1499
+ /* @__PURE__ */ React15.createElement(
1180
1500
  SearchPanel,
1181
1501
  {
1182
1502
  label: searchLabel,
@@ -1184,18 +1504,18 @@ function CanopyHeader(props = {}) {
1184
1504
  placeholder: searchPlaceholder
1185
1505
  }
1186
1506
  )
1187
- ), /* @__PURE__ */ React14.createElement(HeaderScript, null));
1507
+ ), /* @__PURE__ */ React15.createElement(HeaderScript, null));
1188
1508
  }
1189
1509
 
1190
1510
  // ui/src/layout/CanopyFooter.jsx
1191
- import React15 from "react";
1511
+ import React16 from "react";
1192
1512
  function CanopyFooter({ className = "", children }) {
1193
1513
  const footerClassName = ["canopy-footer", className].filter(Boolean).join(" ");
1194
- return /* @__PURE__ */ React15.createElement("footer", { className: footerClassName }, /* @__PURE__ */ React15.createElement("div", { className: "canopy-footer__inner" }, children));
1514
+ return /* @__PURE__ */ React16.createElement("footer", { className: footerClassName }, /* @__PURE__ */ React16.createElement("div", { className: "canopy-footer__inner" }, children));
1195
1515
  }
1196
1516
 
1197
1517
  // ui/src/layout/TeaserCard.jsx
1198
- import React16 from "react";
1518
+ import React17 from "react";
1199
1519
  function TeaserCard({
1200
1520
  href = "",
1201
1521
  title = "",
@@ -1216,7 +1536,7 @@ function TeaserCard({
1216
1536
  ].filter(Boolean).join(" ");
1217
1537
  const showThumb = type === "work" && thumbnail;
1218
1538
  const metaLine = (Array.isArray(metadata) && metadata.length ? metadata.filter(Boolean) : summary ? [summary] : []).filter(Boolean).slice(0, 2).join(" \u2022 ");
1219
- return /* @__PURE__ */ React16.createElement(
1539
+ return /* @__PURE__ */ React17.createElement(
1220
1540
  Tag,
1221
1541
  {
1222
1542
  className: classes,
@@ -1224,7 +1544,7 @@ function TeaserCard({
1224
1544
  "data-canopy-item": href ? "" : void 0,
1225
1545
  ...rest
1226
1546
  },
1227
- showThumb ? /* @__PURE__ */ React16.createElement("div", { className: "canopy-search-teaser__thumb" }, /* @__PURE__ */ React16.createElement(
1547
+ showThumb ? /* @__PURE__ */ React17.createElement("div", { className: "canopy-search-teaser__thumb" }, /* @__PURE__ */ React17.createElement(
1228
1548
  "img",
1229
1549
  {
1230
1550
  src: thumbnail,
@@ -1234,12 +1554,12 @@ function TeaserCard({
1234
1554
  className: "canopy-search-teaser__thumb-img"
1235
1555
  }
1236
1556
  )) : null,
1237
- /* @__PURE__ */ React16.createElement("div", { className: "canopy-search-teaser__text" }, /* @__PURE__ */ React16.createElement("span", { className: "canopy-search-teaser__title" }, title || href || "Untitled"), metaLine ? /* @__PURE__ */ React16.createElement("span", { className: "canopy-search-teaser__meta" }, metaLine) : null)
1557
+ /* @__PURE__ */ React17.createElement("div", { className: "canopy-search-teaser__text" }, /* @__PURE__ */ React17.createElement("span", { className: "canopy-search-teaser__title" }, title || href || "Untitled"), metaLine ? /* @__PURE__ */ React17.createElement("span", { className: "canopy-search-teaser__meta" }, metaLine) : null)
1238
1558
  );
1239
1559
  }
1240
1560
 
1241
1561
  // ui/src/layout/GoogleAnalytics.jsx
1242
- import React17 from "react";
1562
+ import React18 from "react";
1243
1563
  var GA_HOST = "https://www.googletagmanager.com/gtag/js";
1244
1564
  function GoogleAnalytics({ id }) {
1245
1565
  if (!id) return null;
@@ -1249,11 +1569,11 @@ function GoogleAnalytics({ id }) {
1249
1569
  gtag('js', new Date());
1250
1570
  gtag('config', '${id}');
1251
1571
  `;
1252
- return /* @__PURE__ */ React17.createElement(React17.Fragment, null, /* @__PURE__ */ React17.createElement("script", { async: true, src: `${GA_HOST}?id=${encodeURIComponent(id)}` }), /* @__PURE__ */ React17.createElement("script", { dangerouslySetInnerHTML: { __html: inlineConfig } }));
1572
+ return /* @__PURE__ */ React18.createElement(React18.Fragment, null, /* @__PURE__ */ React18.createElement("script", { async: true, src: `${GA_HOST}?id=${encodeURIComponent(id)}` }), /* @__PURE__ */ React18.createElement("script", { dangerouslySetInnerHTML: { __html: inlineConfig } }));
1253
1573
  }
1254
1574
 
1255
1575
  // ui/src/iiif/Viewer.jsx
1256
- import React18, { useEffect as useEffect2, useState as useState2 } from "react";
1576
+ import React19, { useEffect as useEffect2, useState as useState2 } from "react";
1257
1577
  var DEFAULT_VIEWER_OPTIONS = {
1258
1578
  showDownload: false,
1259
1579
  showIIIFBadge: false,
@@ -1309,7 +1629,7 @@ var Viewer = (props) => {
1309
1629
  } catch (_) {
1310
1630
  json = "{}";
1311
1631
  }
1312
- return /* @__PURE__ */ React18.createElement("div", { "data-canopy-viewer": "1", className: "not-prose" }, /* @__PURE__ */ React18.createElement(
1632
+ return /* @__PURE__ */ React19.createElement("div", { "data-canopy-viewer": "1", className: "not-prose" }, /* @__PURE__ */ React19.createElement(
1313
1633
  "script",
1314
1634
  {
1315
1635
  type: "application/json",
@@ -1317,11 +1637,11 @@ var Viewer = (props) => {
1317
1637
  }
1318
1638
  ));
1319
1639
  }
1320
- return /* @__PURE__ */ React18.createElement(CloverViewer, { ...props, options: mergedOptions });
1640
+ return /* @__PURE__ */ React19.createElement(CloverViewer, { ...props, options: mergedOptions });
1321
1641
  };
1322
1642
 
1323
1643
  // ui/src/iiif/Slider.jsx
1324
- import React19, { useEffect as useEffect3, useState as useState3 } from "react";
1644
+ import React20, { useEffect as useEffect3, useState as useState3 } from "react";
1325
1645
 
1326
1646
  // ui/src/iiif/sliderOptions.js
1327
1647
  var UNIT_TOKEN = "__canopySliderUnit";
@@ -1468,7 +1788,7 @@ var Slider = (props = {}) => {
1468
1788
  } catch (_) {
1469
1789
  json = "{}";
1470
1790
  }
1471
- return /* @__PURE__ */ React19.createElement("div", { className: sliderClassName, "data-canopy-slider": "1" }, /* @__PURE__ */ React19.createElement(
1791
+ return /* @__PURE__ */ React20.createElement("div", { className: sliderClassName, "data-canopy-slider": "1" }, /* @__PURE__ */ React20.createElement(
1472
1792
  "script",
1473
1793
  {
1474
1794
  type: "application/json",
@@ -1476,11 +1796,11 @@ var Slider = (props = {}) => {
1476
1796
  }
1477
1797
  ));
1478
1798
  }
1479
- return /* @__PURE__ */ React19.createElement(CloverSlider, { ...resolvedProps });
1799
+ return /* @__PURE__ */ React20.createElement(CloverSlider, { ...resolvedProps });
1480
1800
  };
1481
1801
 
1482
1802
  // ui/src/iiif/Scroll.jsx
1483
- import React20, { useEffect as useEffect4, useState as useState4 } from "react";
1803
+ import React21, { useEffect as useEffect4, useState as useState4 } from "react";
1484
1804
  var Scroll = (props) => {
1485
1805
  const [CloverScroll, setCloverScroll] = useState4(null);
1486
1806
  useEffect4(() => {
@@ -1505,7 +1825,7 @@ var Scroll = (props) => {
1505
1825
  } catch (_) {
1506
1826
  json = "{}";
1507
1827
  }
1508
- return /* @__PURE__ */ React20.createElement("div", { "data-canopy-scroll": "1", className: "not-prose" }, /* @__PURE__ */ React20.createElement(
1828
+ return /* @__PURE__ */ React21.createElement("div", { "data-canopy-scroll": "1", className: "not-prose" }, /* @__PURE__ */ React21.createElement(
1509
1829
  "script",
1510
1830
  {
1511
1831
  type: "application/json",
@@ -1513,11 +1833,11 @@ var Scroll = (props) => {
1513
1833
  }
1514
1834
  ));
1515
1835
  }
1516
- return /* @__PURE__ */ React20.createElement(CloverScroll, { ...props });
1836
+ return /* @__PURE__ */ React21.createElement(CloverScroll, { ...props });
1517
1837
  };
1518
1838
 
1519
1839
  // ui/src/iiif/Image.jsx
1520
- import React21, { useEffect as useEffect5, useState as useState5 } from "react";
1840
+ import React22, { useEffect as useEffect5, useState as useState5 } from "react";
1521
1841
  var Image = (props = {}) => {
1522
1842
  const [CloverImage, setCloverImage] = useState5(null);
1523
1843
  const baseClass = "canopy-iiif-image";
@@ -1550,7 +1870,7 @@ var Image = (props = {}) => {
1550
1870
  } catch (_) {
1551
1871
  json = "{}";
1552
1872
  }
1553
- return /* @__PURE__ */ React21.createElement("figure", { className: rootClassName }, /* @__PURE__ */ React21.createElement(
1873
+ return /* @__PURE__ */ React22.createElement("figure", { className: rootClassName }, /* @__PURE__ */ React22.createElement(
1554
1874
  "div",
1555
1875
  {
1556
1876
  className: `${baseClass}__placeholder`,
@@ -1560,26 +1880,26 @@ var Image = (props = {}) => {
1560
1880
  "--canopy-iiif-image-bg": backgroundColor
1561
1881
  }
1562
1882
  },
1563
- /* @__PURE__ */ React21.createElement(
1883
+ /* @__PURE__ */ React22.createElement(
1564
1884
  "script",
1565
1885
  {
1566
1886
  type: "application/json",
1567
1887
  dangerouslySetInnerHTML: { __html: json }
1568
1888
  }
1569
1889
  )
1570
- ), caption && /* @__PURE__ */ React21.createElement("figcaption", { className: `${baseClass}__caption` }, caption));
1890
+ ), caption && /* @__PURE__ */ React22.createElement("figcaption", { className: `${baseClass}__caption` }, caption));
1571
1891
  }
1572
- return /* @__PURE__ */ React21.createElement(CloverImage, { ...props, className: rootClassName });
1892
+ return /* @__PURE__ */ React22.createElement(CloverImage, { ...props, className: rootClassName });
1573
1893
  };
1574
1894
 
1575
1895
  // ui/src/iiif/Properties/Id.jsx
1576
- import React22 from "react";
1896
+ import React23 from "react";
1577
1897
  function Id({ title = "IIIF Manifest", id, ...props }) {
1578
- return /* @__PURE__ */ React22.createElement("dl", null, /* @__PURE__ */ React22.createElement("dt", null, title), /* @__PURE__ */ React22.createElement("dd", null, /* @__PURE__ */ React22.createElement("a", { href: id }, id)));
1898
+ return /* @__PURE__ */ React23.createElement("dl", null, /* @__PURE__ */ React23.createElement("dt", null, title), /* @__PURE__ */ React23.createElement("dd", null, /* @__PURE__ */ React23.createElement("a", { href: id }, id)));
1579
1899
  }
1580
1900
 
1581
1901
  // ui/src/iiif/MdxRelatedItems.jsx
1582
- import React23 from "react";
1902
+ import React24 from "react";
1583
1903
  function MdxRelatedItems(props) {
1584
1904
  let json = "{}";
1585
1905
  try {
@@ -1587,7 +1907,7 @@ function MdxRelatedItems(props) {
1587
1907
  } catch (_) {
1588
1908
  json = "{}";
1589
1909
  }
1590
- return /* @__PURE__ */ React23.createElement("div", { "data-canopy-related-items": "1" }, /* @__PURE__ */ React23.createElement(
1910
+ return /* @__PURE__ */ React24.createElement("div", { "data-canopy-related-items": "1" }, /* @__PURE__ */ React24.createElement(
1591
1911
  "script",
1592
1912
  {
1593
1913
  type: "application/json",
@@ -1597,7 +1917,7 @@ function MdxRelatedItems(props) {
1597
1917
  }
1598
1918
 
1599
1919
  // ui/src/search/MdxSearchResults.jsx
1600
- import React24 from "react";
1920
+ import React25 from "react";
1601
1921
  function MdxSearchResults(props) {
1602
1922
  let json = "{}";
1603
1923
  try {
@@ -1605,11 +1925,11 @@ function MdxSearchResults(props) {
1605
1925
  } catch (_) {
1606
1926
  json = "{}";
1607
1927
  }
1608
- return /* @__PURE__ */ React24.createElement("div", { "data-canopy-search-results": "1" }, /* @__PURE__ */ React24.createElement("script", { type: "application/json", dangerouslySetInnerHTML: { __html: json } }));
1928
+ return /* @__PURE__ */ React25.createElement("div", { "data-canopy-search-results": "1" }, /* @__PURE__ */ React25.createElement("script", { type: "application/json", dangerouslySetInnerHTML: { __html: json } }));
1609
1929
  }
1610
1930
 
1611
1931
  // ui/src/search/SearchSummary.jsx
1612
- import React25 from "react";
1932
+ import React26 from "react";
1613
1933
  function SearchSummary(props) {
1614
1934
  let json = "{}";
1615
1935
  try {
@@ -1617,11 +1937,11 @@ function SearchSummary(props) {
1617
1937
  } catch (_) {
1618
1938
  json = "{}";
1619
1939
  }
1620
- return /* @__PURE__ */ React25.createElement("div", { "data-canopy-search-summary": "1" }, /* @__PURE__ */ React25.createElement("script", { type: "application/json", dangerouslySetInnerHTML: { __html: json } }));
1940
+ return /* @__PURE__ */ React26.createElement("div", { "data-canopy-search-summary": "1" }, /* @__PURE__ */ React26.createElement("script", { type: "application/json", dangerouslySetInnerHTML: { __html: json } }));
1621
1941
  }
1622
1942
 
1623
1943
  // ui/src/search/MdxSearchTabs.jsx
1624
- import React26 from "react";
1944
+ import React27 from "react";
1625
1945
  function MdxSearchTabs(props) {
1626
1946
  let json = "{}";
1627
1947
  try {
@@ -1629,11 +1949,11 @@ function MdxSearchTabs(props) {
1629
1949
  } catch (_) {
1630
1950
  json = "{}";
1631
1951
  }
1632
- return /* @__PURE__ */ React26.createElement("div", { "data-canopy-search-tabs": "1" }, /* @__PURE__ */ React26.createElement("script", { type: "application/json", dangerouslySetInnerHTML: { __html: json } }));
1952
+ return /* @__PURE__ */ React27.createElement("div", { "data-canopy-search-tabs": "1" }, /* @__PURE__ */ React27.createElement("script", { type: "application/json", dangerouslySetInnerHTML: { __html: json } }));
1633
1953
  }
1634
1954
 
1635
1955
  // ui/src/search/MdxSearch.jsx
1636
- import React27 from "react";
1956
+ import React28 from "react";
1637
1957
  function MdxSearch(props = {}) {
1638
1958
  const {
1639
1959
  layout,
@@ -1651,15 +1971,15 @@ function MdxSearch(props = {}) {
1651
1971
  resultsPayload.layout = layout;
1652
1972
  }
1653
1973
  const classes = ["canopy-search", className].filter(Boolean).join(" ");
1654
- return /* @__PURE__ */ React27.createElement("section", { className: classes, "data-canopy-search": "1" }, showTabs ? /* @__PURE__ */ React27.createElement(MdxSearchTabs, { ...tabsProps }) : null, showSummary ? /* @__PURE__ */ React27.createElement(SearchSummary, { ...summaryProps }) : null, showResults ? /* @__PURE__ */ React27.createElement(MdxSearchResults, { ...resultsPayload }) : null, children || null);
1974
+ return /* @__PURE__ */ React28.createElement("section", { className: classes, "data-canopy-search": "1" }, showTabs ? /* @__PURE__ */ React28.createElement(MdxSearchTabs, { ...tabsProps }) : null, showSummary ? /* @__PURE__ */ React28.createElement(SearchSummary, { ...summaryProps }) : null, showResults ? /* @__PURE__ */ React28.createElement(MdxSearchResults, { ...resultsPayload }) : null, children || null);
1655
1975
  }
1656
1976
 
1657
1977
  // ui/src/search/SearchResults.jsx
1658
- import React28 from "react";
1978
+ import React29 from "react";
1659
1979
  function DefaultArticleTemplate({ record, query }) {
1660
1980
  if (!record) return null;
1661
1981
  const metadata = Array.isArray(record.metadata) ? record.metadata : [];
1662
- return /* @__PURE__ */ React28.createElement(
1982
+ return /* @__PURE__ */ React29.createElement(
1663
1983
  ArticleCard,
1664
1984
  {
1665
1985
  href: record.href,
@@ -1677,7 +1997,7 @@ function DefaultFigureTemplate({ record, thumbnailAspectRatio }) {
1677
1997
  if (!record) return null;
1678
1998
  const hasDims = Number.isFinite(Number(record.thumbnailWidth)) && Number(record.thumbnailWidth) > 0 && Number.isFinite(Number(record.thumbnailHeight)) && Number(record.thumbnailHeight) > 0;
1679
1999
  const aspect = Number.isFinite(Number(thumbnailAspectRatio)) && Number(thumbnailAspectRatio) > 0 ? Number(thumbnailAspectRatio) : hasDims ? Number(record.thumbnailWidth) / Number(record.thumbnailHeight) : void 0;
1680
- return /* @__PURE__ */ React28.createElement(
2000
+ return /* @__PURE__ */ React29.createElement(
1681
2001
  Card,
1682
2002
  {
1683
2003
  href: record.href,
@@ -1698,7 +2018,7 @@ function SearchResults({
1698
2018
  variant = "auto"
1699
2019
  }) {
1700
2020
  if (!results.length) {
1701
- return /* @__PURE__ */ React28.createElement("div", { className: "text-slate-600" }, /* @__PURE__ */ React28.createElement("em", null, "No results"));
2021
+ return /* @__PURE__ */ React29.createElement("div", { className: "text-slate-600" }, /* @__PURE__ */ React29.createElement("em", null, "No results"));
1702
2022
  }
1703
2023
  const normalizedType = String(type || "all").toLowerCase();
1704
2024
  const normalizedVariant = variant === "figure" || variant === "article" ? variant : "auto";
@@ -1706,9 +2026,9 @@ function SearchResults({
1706
2026
  const FigureTemplate = templates && templates.figure ? templates.figure : DefaultFigureTemplate;
1707
2027
  const ArticleTemplate = templates && templates.article ? templates.article : DefaultArticleTemplate;
1708
2028
  if (isAnnotationView) {
1709
- return /* @__PURE__ */ React28.createElement("div", { id: "search-results", className: "space-y-4", role: "region", "aria-label": "Search results" }, results.map((r, i) => {
2029
+ return /* @__PURE__ */ React29.createElement("div", { id: "search-results", className: "space-y-4", role: "region", "aria-label": "Search results" }, results.map((r, i) => {
1710
2030
  if (!r) return null;
1711
- return /* @__PURE__ */ React28.createElement(
2031
+ return /* @__PURE__ */ React29.createElement(
1712
2032
  ArticleTemplate,
1713
2033
  {
1714
2034
  key: r.id || i,
@@ -1726,20 +2046,20 @@ function SearchResults({
1726
2046
  return !isWorkRecord(record) || normalizedType !== "work";
1727
2047
  };
1728
2048
  if (layout === "list") {
1729
- return /* @__PURE__ */ React28.createElement("div", { id: "search-results", className: "space-y-6", role: "region", "aria-label": "Search results" }, results.map((r, i) => {
2049
+ return /* @__PURE__ */ React29.createElement("div", { id: "search-results", className: "space-y-6", role: "region", "aria-label": "Search results" }, results.map((r, i) => {
1730
2050
  if (shouldRenderAsArticle(r)) {
1731
- return /* @__PURE__ */ React28.createElement("div", { key: i, className: `search-result ${r && r.type}` }, /* @__PURE__ */ React28.createElement(ArticleTemplate, { record: r, query, layout }));
2051
+ return /* @__PURE__ */ React29.createElement("div", { key: i, className: `search-result ${r && r.type}` }, /* @__PURE__ */ React29.createElement(ArticleTemplate, { record: r, query, layout }));
1732
2052
  }
1733
2053
  const hasDims = Number.isFinite(Number(r.thumbnailWidth)) && Number(r.thumbnailWidth) > 0 && Number.isFinite(Number(r.thumbnailHeight)) && Number(r.thumbnailHeight) > 0;
1734
2054
  const aspect = hasDims ? Number(r.thumbnailWidth) / Number(r.thumbnailHeight) : void 0;
1735
- return /* @__PURE__ */ React28.createElement(
2055
+ return /* @__PURE__ */ React29.createElement(
1736
2056
  "div",
1737
2057
  {
1738
2058
  key: i,
1739
2059
  className: `search-result ${r.type}`,
1740
2060
  "data-thumbnail-aspect-ratio": aspect
1741
2061
  },
1742
- /* @__PURE__ */ React28.createElement(
2062
+ /* @__PURE__ */ React29.createElement(
1743
2063
  FigureTemplate,
1744
2064
  {
1745
2065
  record: r,
@@ -1751,20 +2071,20 @@ function SearchResults({
1751
2071
  );
1752
2072
  }));
1753
2073
  }
1754
- return /* @__PURE__ */ React28.createElement("div", { id: "search-results", role: "region", "aria-label": "Search results" }, /* @__PURE__ */ React28.createElement(Grid, null, results.map((r, i) => {
2074
+ return /* @__PURE__ */ React29.createElement("div", { id: "search-results", role: "region", "aria-label": "Search results" }, /* @__PURE__ */ React29.createElement(Grid, null, results.map((r, i) => {
1755
2075
  if (shouldRenderAsArticle(r)) {
1756
- return /* @__PURE__ */ React28.createElement(GridItem, { key: i, className: `search-result ${r && r.type}` }, /* @__PURE__ */ React28.createElement(ArticleTemplate, { record: r, query, layout }));
2076
+ return /* @__PURE__ */ React29.createElement(GridItem, { key: i, className: `search-result ${r && r.type}` }, /* @__PURE__ */ React29.createElement(ArticleTemplate, { record: r, query, layout }));
1757
2077
  }
1758
2078
  const hasDims = Number.isFinite(Number(r.thumbnailWidth)) && Number(r.thumbnailWidth) > 0 && Number.isFinite(Number(r.thumbnailHeight)) && Number(r.thumbnailHeight) > 0;
1759
2079
  const aspect = hasDims ? Number(r.thumbnailWidth) / Number(r.thumbnailHeight) : void 0;
1760
- return /* @__PURE__ */ React28.createElement(
2080
+ return /* @__PURE__ */ React29.createElement(
1761
2081
  GridItem,
1762
2082
  {
1763
2083
  key: i,
1764
2084
  className: `search-result ${r.type}`,
1765
2085
  "data-thumbnail-aspect-ratio": aspect
1766
2086
  },
1767
- /* @__PURE__ */ React28.createElement(
2087
+ /* @__PURE__ */ React29.createElement(
1768
2088
  FigureTemplate,
1769
2089
  {
1770
2090
  record: r,
@@ -1778,7 +2098,7 @@ function SearchResults({
1778
2098
  }
1779
2099
 
1780
2100
  // ui/src/search/SearchTabs.jsx
1781
- import React29, { useRef as useRef2, useState as useState6, useEffect as useEffect6 } from "react";
2101
+ import React30, { useRef as useRef2, useState as useState6, useEffect as useEffect6 } from "react";
1782
2102
  function SearchTabs({
1783
2103
  type = "all",
1784
2104
  onTypeChange,
@@ -1832,7 +2152,7 @@ function SearchTabs({
1832
2152
  width: `${itemBoundingBox.width}px`
1833
2153
  };
1834
2154
  }
1835
- return /* @__PURE__ */ React29.createElement("div", { className: "canopy-search-tabs-wrapper" }, /* @__PURE__ */ React29.createElement(
2155
+ return /* @__PURE__ */ React30.createElement("div", { className: "canopy-search-tabs-wrapper" }, /* @__PURE__ */ React30.createElement(
1836
2156
  "div",
1837
2157
  {
1838
2158
  role: "tablist",
@@ -1841,7 +2161,7 @@ function SearchTabs({
1841
2161
  ref: wrapperRef,
1842
2162
  onMouseLeave: resetHighlight
1843
2163
  },
1844
- /* @__PURE__ */ React29.createElement(
2164
+ /* @__PURE__ */ React30.createElement(
1845
2165
  "div",
1846
2166
  {
1847
2167
  ref: highlightRef,
@@ -1853,7 +2173,7 @@ function SearchTabs({
1853
2173
  const active = String(type).toLowerCase() === String(t).toLowerCase();
1854
2174
  const cRaw = counts && Object.prototype.hasOwnProperty.call(counts, t) ? counts[t] : void 0;
1855
2175
  const c = Number.isFinite(Number(cRaw)) ? Number(cRaw) : 0;
1856
- return /* @__PURE__ */ React29.createElement(
2176
+ return /* @__PURE__ */ React30.createElement(
1857
2177
  "button",
1858
2178
  {
1859
2179
  key: t,
@@ -1871,7 +2191,7 @@ function SearchTabs({
1871
2191
  ")"
1872
2192
  );
1873
2193
  })
1874
- ), hasFilters ? /* @__PURE__ */ React29.createElement(
2194
+ ), hasFilters ? /* @__PURE__ */ React30.createElement(
1875
2195
  "button",
1876
2196
  {
1877
2197
  type: "button",
@@ -1879,12 +2199,12 @@ function SearchTabs({
1879
2199
  "aria-expanded": filtersOpen ? "true" : "false",
1880
2200
  className: "inline-flex items-center gap-2 rounded-md border border-slate-200 bg-white px-3 py-1.5 text-sm font-medium text-slate-700 shadow-sm transition hover:border-brand-200 hover:bg-brand-50 hover:text-brand-700"
1881
2201
  },
1882
- /* @__PURE__ */ React29.createElement("span", null, filtersLabel, filterBadge)
2202
+ /* @__PURE__ */ React30.createElement("span", null, filtersLabel, filterBadge)
1883
2203
  ) : null);
1884
2204
  }
1885
2205
 
1886
2206
  // ui/src/search/SearchFiltersDialog.jsx
1887
- import React30 from "react";
2207
+ import React31 from "react";
1888
2208
  function toArray(input) {
1889
2209
  if (!input) return [];
1890
2210
  if (Array.isArray(input)) return input;
@@ -1923,20 +2243,20 @@ function FacetSection({ facet, selected, onToggle }) {
1923
2243
  const selectedValues = selected.get(String(slug)) || /* @__PURE__ */ new Set();
1924
2244
  const checkboxId = (valueSlug) => `filter-${slug}-${valueSlug}`;
1925
2245
  const hasSelection = selectedValues.size > 0;
1926
- const [quickQuery, setQuickQuery] = React30.useState("");
2246
+ const [quickQuery, setQuickQuery] = React31.useState("");
1927
2247
  const hasQuery = quickQuery.trim().length > 0;
1928
- const filteredValues = React30.useMemo(
2248
+ const filteredValues = React31.useMemo(
1929
2249
  () => facetMatches(values, quickQuery),
1930
2250
  [values, quickQuery]
1931
2251
  );
1932
- return /* @__PURE__ */ React30.createElement(
2252
+ return /* @__PURE__ */ React31.createElement(
1933
2253
  "details",
1934
2254
  {
1935
2255
  className: "canopy-search-filters__facet",
1936
2256
  open: hasSelection
1937
2257
  },
1938
- /* @__PURE__ */ React30.createElement("summary", { className: "canopy-search-filters__facet-summary" }, /* @__PURE__ */ React30.createElement("span", null, label), /* @__PURE__ */ React30.createElement("span", { className: "canopy-search-filters__facet-count" }, values.length)),
1939
- /* @__PURE__ */ React30.createElement("div", { className: "canopy-search-filters__facet-content" }, /* @__PURE__ */ React30.createElement("div", { className: "canopy-search-filters__quick" }, /* @__PURE__ */ React30.createElement(
2258
+ /* @__PURE__ */ React31.createElement("summary", { className: "canopy-search-filters__facet-summary" }, /* @__PURE__ */ React31.createElement("span", null, label), /* @__PURE__ */ React31.createElement("span", { className: "canopy-search-filters__facet-count" }, values.length)),
2259
+ /* @__PURE__ */ React31.createElement("div", { className: "canopy-search-filters__facet-content" }, /* @__PURE__ */ React31.createElement("div", { className: "canopy-search-filters__quick" }, /* @__PURE__ */ React31.createElement(
1940
2260
  "input",
1941
2261
  {
1942
2262
  type: "search",
@@ -1946,7 +2266,7 @@ function FacetSection({ facet, selected, onToggle }) {
1946
2266
  className: "canopy-search-filters__quick-input",
1947
2267
  "aria-label": `Filter ${label} values`
1948
2268
  }
1949
- ), quickQuery ? /* @__PURE__ */ React30.createElement(
2269
+ ), quickQuery ? /* @__PURE__ */ React31.createElement(
1950
2270
  "button",
1951
2271
  {
1952
2272
  type: "button",
@@ -1955,11 +2275,11 @@ function FacetSection({ facet, selected, onToggle }) {
1955
2275
  "aria-label": `Clear ${label} filter search`
1956
2276
  },
1957
2277
  "Clear"
1958
- ) : null), hasQuery && !filteredValues.length ? /* @__PURE__ */ React30.createElement("p", { className: "canopy-search-filters__facet-notice" }, "No matches found.") : null, /* @__PURE__ */ React30.createElement("ul", { className: "canopy-search-filters__facet-list" }, filteredValues.map((entry) => {
2278
+ ) : null), hasQuery && !filteredValues.length ? /* @__PURE__ */ React31.createElement("p", { className: "canopy-search-filters__facet-notice" }, "No matches found.") : null, /* @__PURE__ */ React31.createElement("ul", { className: "canopy-search-filters__facet-list" }, filteredValues.map((entry) => {
1959
2279
  const valueSlug = String(entry.slug || entry.value || "");
1960
2280
  const isChecked = selectedValues.has(valueSlug);
1961
2281
  const inputId = checkboxId(valueSlug);
1962
- return /* @__PURE__ */ React30.createElement("li", { key: valueSlug, className: "canopy-search-filters__facet-item" }, /* @__PURE__ */ React30.createElement(
2282
+ return /* @__PURE__ */ React31.createElement("li", { key: valueSlug, className: "canopy-search-filters__facet-item" }, /* @__PURE__ */ React31.createElement(
1963
2283
  "input",
1964
2284
  {
1965
2285
  id: inputId,
@@ -1971,15 +2291,15 @@ function FacetSection({ facet, selected, onToggle }) {
1971
2291
  if (onToggle) onToggle(slug, valueSlug, nextChecked);
1972
2292
  }
1973
2293
  }
1974
- ), /* @__PURE__ */ React30.createElement(
2294
+ ), /* @__PURE__ */ React31.createElement(
1975
2295
  "label",
1976
2296
  {
1977
2297
  htmlFor: inputId,
1978
2298
  className: "canopy-search-filters__facet-label"
1979
2299
  },
1980
- /* @__PURE__ */ React30.createElement("span", null, entry.value, " ", Number.isFinite(entry.doc_count) ? /* @__PURE__ */ React30.createElement("span", { className: "canopy-search-filters__facet-count" }, "(", entry.doc_count, ")") : null)
2300
+ /* @__PURE__ */ React31.createElement("span", null, entry.value, " ", Number.isFinite(entry.doc_count) ? /* @__PURE__ */ React31.createElement("span", { className: "canopy-search-filters__facet-count" }, "(", entry.doc_count, ")") : null)
1981
2301
  ));
1982
- }), !filteredValues.length && !hasQuery ? /* @__PURE__ */ React30.createElement("li", { className: "canopy-search-filters__facet-empty" }, "No values available.") : null))
2302
+ }), !filteredValues.length && !hasQuery ? /* @__PURE__ */ React31.createElement("li", { className: "canopy-search-filters__facet-empty" }, "No values available.") : null))
1983
2303
  );
1984
2304
  }
1985
2305
  function SearchFiltersDialog(props = {}) {
@@ -2001,7 +2321,7 @@ function SearchFiltersDialog(props = {}) {
2001
2321
  (total, set) => total + set.size,
2002
2322
  0
2003
2323
  );
2004
- React30.useEffect(() => {
2324
+ React31.useEffect(() => {
2005
2325
  if (!open) return void 0;
2006
2326
  if (typeof document === "undefined") return void 0;
2007
2327
  const body = document.body;
@@ -2018,7 +2338,7 @@ function SearchFiltersDialog(props = {}) {
2018
2338
  if (!open) return null;
2019
2339
  const brandId = "canopy-modal-filters-label";
2020
2340
  const subtitleText = subtitle != null ? subtitle : title;
2021
- return /* @__PURE__ */ React30.createElement(
2341
+ return /* @__PURE__ */ React31.createElement(
2022
2342
  CanopyModal,
2023
2343
  {
2024
2344
  id: "canopy-modal-filters",
@@ -2033,8 +2353,8 @@ function SearchFiltersDialog(props = {}) {
2033
2353
  onBackgroundClick: () => onOpenChange && onOpenChange(false),
2034
2354
  bodyClassName: "canopy-modal__body--filters"
2035
2355
  },
2036
- subtitleText ? /* @__PURE__ */ React30.createElement("p", { className: "canopy-search-filters__subtitle" }, subtitleText) : null,
2037
- /* @__PURE__ */ React30.createElement("div", { className: "canopy-search-filters__body" }, Array.isArray(facets) && facets.length ? /* @__PURE__ */ React30.createElement("div", { className: "canopy-search-filters__facets" }, facets.map((facet) => /* @__PURE__ */ React30.createElement(
2356
+ subtitleText ? /* @__PURE__ */ React31.createElement("p", { className: "canopy-search-filters__subtitle" }, subtitleText) : null,
2357
+ /* @__PURE__ */ React31.createElement("div", { className: "canopy-search-filters__body" }, Array.isArray(facets) && facets.length ? /* @__PURE__ */ React31.createElement("div", { className: "canopy-search-filters__facets" }, facets.map((facet) => /* @__PURE__ */ React31.createElement(
2038
2358
  FacetSection,
2039
2359
  {
2040
2360
  key: facet.slug || facet.label,
@@ -2042,8 +2362,8 @@ function SearchFiltersDialog(props = {}) {
2042
2362
  selected: selectedMap,
2043
2363
  onToggle
2044
2364
  }
2045
- ))) : /* @__PURE__ */ React30.createElement("p", { className: "canopy-search-filters__empty" }, "No filters are available for this collection.")),
2046
- /* @__PURE__ */ React30.createElement("footer", { className: "canopy-search-filters__footer" }, /* @__PURE__ */ React30.createElement("div", null, activeCount ? `${activeCount} filter${activeCount === 1 ? "" : "s"} applied` : "No filters applied"), /* @__PURE__ */ React30.createElement("div", { className: "canopy-search-filters__footer-actions" }, /* @__PURE__ */ React30.createElement(
2365
+ ))) : /* @__PURE__ */ React31.createElement("p", { className: "canopy-search-filters__empty" }, "No filters are available for this collection.")),
2366
+ /* @__PURE__ */ React31.createElement("footer", { className: "canopy-search-filters__footer" }, /* @__PURE__ */ React31.createElement("div", null, activeCount ? `${activeCount} filter${activeCount === 1 ? "" : "s"} applied` : "No filters applied"), /* @__PURE__ */ React31.createElement("div", { className: "canopy-search-filters__footer-actions" }, /* @__PURE__ */ React31.createElement(
2047
2367
  "button",
2048
2368
  {
2049
2369
  type: "button",
@@ -2054,7 +2374,7 @@ function SearchFiltersDialog(props = {}) {
2054
2374
  className: "canopy-search-filters__button canopy-search-filters__button--secondary"
2055
2375
  },
2056
2376
  "Clear all"
2057
- ), /* @__PURE__ */ React30.createElement(
2377
+ ), /* @__PURE__ */ React31.createElement(
2058
2378
  "button",
2059
2379
  {
2060
2380
  type: "button",
@@ -2067,7 +2387,7 @@ function SearchFiltersDialog(props = {}) {
2067
2387
  }
2068
2388
 
2069
2389
  // ui/src/search-form/MdxSearchFormModal.jsx
2070
- import React31 from "react";
2390
+ import React32 from "react";
2071
2391
  function MdxSearchFormModal(props = {}) {
2072
2392
  const {
2073
2393
  placeholder = "Search\u2026",
@@ -2083,24 +2403,24 @@ function MdxSearchFormModal(props = {}) {
2083
2403
  const text = typeof label === "string" && label.trim() ? label.trim() : buttonLabel;
2084
2404
  const resolvedSearchPath = resolveSearchPath(searchPath);
2085
2405
  const data = { placeholder, hotkey, maxResults, groupOrder, label: text, searchPath: resolvedSearchPath };
2086
- return /* @__PURE__ */ React31.createElement("div", { "data-canopy-search-form": true, className: "flex-1 min-w-0" }, /* @__PURE__ */ React31.createElement("div", { className: "relative w-full" }, /* @__PURE__ */ React31.createElement(SearchPanelForm, { placeholder, buttonLabel, label, searchPath: resolvedSearchPath }), /* @__PURE__ */ React31.createElement(SearchPanelTeaserResults, null)), /* @__PURE__ */ React31.createElement("script", { type: "application/json", dangerouslySetInnerHTML: { __html: JSON.stringify(data) } }));
2406
+ return /* @__PURE__ */ React32.createElement("div", { "data-canopy-search-form": true, className: "flex-1 min-w-0" }, /* @__PURE__ */ React32.createElement("div", { className: "relative w-full" }, /* @__PURE__ */ React32.createElement(SearchPanelForm, { placeholder, buttonLabel, label, searchPath: resolvedSearchPath }), /* @__PURE__ */ React32.createElement(SearchPanelTeaserResults, null)), /* @__PURE__ */ React32.createElement("script", { type: "application/json", dangerouslySetInnerHTML: { __html: JSON.stringify(data) } }));
2087
2407
  }
2088
2408
 
2089
2409
  // ui/src/docs/MarkdownTable.jsx
2090
- import React32 from "react";
2410
+ import React33 from "react";
2091
2411
  function MarkdownTable({ className = "", ...rest }) {
2092
2412
  const merged = ["markdown-table", className].filter(Boolean).join(" ");
2093
- return /* @__PURE__ */ React32.createElement("div", { className: "markdown-table__frame" }, /* @__PURE__ */ React32.createElement("table", { className: merged, ...rest }));
2413
+ return /* @__PURE__ */ React33.createElement("div", { className: "markdown-table__frame" }, /* @__PURE__ */ React33.createElement("table", { className: merged, ...rest }));
2094
2414
  }
2095
2415
 
2096
2416
  // ui/src/docs/Diagram.jsx
2097
- import React33 from "react";
2417
+ import React34 from "react";
2098
2418
  function CanopyDiagram() {
2099
- return /* @__PURE__ */ React33.createElement("div", { className: "canopy-diagram" }, /* @__PURE__ */ React33.createElement("section", { className: "canopy-diagram__section canopy-diagram__section--collections" }, /* @__PURE__ */ React33.createElement("h3", null, "IIIF Collection(s)"), /* @__PURE__ */ React33.createElement("span", { className: "canopy-diagram__section-summary" }, "Source collections contribute 105 total manifests that Canopy retrieves as-is via IIIF endpoints."), /* @__PURE__ */ React33.createElement("div", { className: "canopy-diagram__grid" }, /* @__PURE__ */ React33.createElement("article", null, /* @__PURE__ */ React33.createElement("h4", null, "Collection A"), /* @__PURE__ */ React33.createElement("ul", null, /* @__PURE__ */ React33.createElement("li", null, "70 Manifests"), /* @__PURE__ */ React33.createElement("li", null, "IIIF Images + A/V"), /* @__PURE__ */ React33.createElement("li", null, "Textual Annotations"))), /* @__PURE__ */ React33.createElement("article", null, /* @__PURE__ */ React33.createElement("h4", null, "Collection B"), /* @__PURE__ */ React33.createElement("ul", null, /* @__PURE__ */ React33.createElement("li", null, "35 Manifests"), /* @__PURE__ */ React33.createElement("li", null, "IIIF Images + A/V"), /* @__PURE__ */ React33.createElement("li", null, "Textual Annotations"))))), /* @__PURE__ */ React33.createElement("div", { className: "canopy-diagram__arrow", "aria-hidden": "true" }, /* @__PURE__ */ React33.createElement("span", { className: "canopy-diagram__arrow-line" }), /* @__PURE__ */ React33.createElement("span", { className: "canopy-diagram__arrow-head" })), /* @__PURE__ */ React33.createElement("section", { className: "canopy-diagram__section canopy-diagram__section--build" }, /* @__PURE__ */ React33.createElement("h3", null, "Canopy Build Process"), /* @__PURE__ */ React33.createElement("span", { className: "canopy-diagram__section-summary" }, "Canopy syncs manifests, page content, and annotations before bundling the site."), /* @__PURE__ */ React33.createElement("div", { className: "canopy-diagram__grid" }, /* @__PURE__ */ React33.createElement("article", null, /* @__PURE__ */ React33.createElement("h4", null, "Automated content"), /* @__PURE__ */ React33.createElement("ul", null, /* @__PURE__ */ React33.createElement("li", null, "105 manifests \u2192 105 work pages"), /* @__PURE__ */ React33.createElement("li", null, "One page per manifest"), /* @__PURE__ */ React33.createElement("li", null, "Customize page layout"))), /* @__PURE__ */ React33.createElement("article", null, /* @__PURE__ */ React33.createElement("h4", null, "Contextual content"), /* @__PURE__ */ React33.createElement("ul", null, /* @__PURE__ */ React33.createElement("li", null, "Markdown & MDX pages"), /* @__PURE__ */ React33.createElement("li", null, "Author narratives"), /* @__PURE__ */ React33.createElement("li", null, "Reference manifests inline"))), /* @__PURE__ */ React33.createElement("article", null, /* @__PURE__ */ React33.createElement("h4", null, "Search index"), /* @__PURE__ */ React33.createElement("ul", null, /* @__PURE__ */ React33.createElement("li", null, "Combines works + pages"), /* @__PURE__ */ React33.createElement("li", null, "Customize result layout"), /* @__PURE__ */ React33.createElement("li", null, "Optional annotations"))))), /* @__PURE__ */ React33.createElement("div", { className: "canopy-diagram__arrow", "aria-hidden": "true" }, /* @__PURE__ */ React33.createElement("span", { className: "canopy-diagram__arrow-line" }), /* @__PURE__ */ React33.createElement("span", { className: "canopy-diagram__arrow-head" })), /* @__PURE__ */ React33.createElement("section", { className: "canopy-diagram__section canopy-diagram__section--output" }, /* @__PURE__ */ React33.createElement("h3", null, "Static Digital Project"), /* @__PURE__ */ React33.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__ */ React33.createElement("div", { className: "canopy-diagram__grid" }, /* @__PURE__ */ React33.createElement("article", null, /* @__PURE__ */ React33.createElement("h4", null, "Work pages"), /* @__PURE__ */ React33.createElement("ul", null, /* @__PURE__ */ React33.createElement("li", null, "105 generated HTML pages"), /* @__PURE__ */ React33.createElement("li", null, "Each links back to source manifests"), /* @__PURE__ */ React33.createElement("li", null, "Styled with Canopy components"))), /* @__PURE__ */ React33.createElement("article", null, /* @__PURE__ */ React33.createElement("h4", null, "Custom pages"), /* @__PURE__ */ React33.createElement("ul", null, /* @__PURE__ */ React33.createElement("li", null, "Markdown & MDX-authored content"), /* @__PURE__ */ React33.createElement("li", null, "Reusable layouts for narratives"), /* @__PURE__ */ React33.createElement("li", null, "Embed IIIF media & interstitials"))), /* @__PURE__ */ React33.createElement("article", null, /* @__PURE__ */ React33.createElement("h4", null, "Search bundle"), /* @__PURE__ */ React33.createElement("ul", null, /* @__PURE__ */ React33.createElement("li", null, "Static FlexSearch index"), /* @__PURE__ */ React33.createElement("li", null, "Works + pages share records"), /* @__PURE__ */ React33.createElement("li", null, "Optional annotation dataset"))))));
2419
+ return /* @__PURE__ */ React34.createElement("div", { className: "canopy-diagram" }, /* @__PURE__ */ React34.createElement("section", { className: "canopy-diagram__section canopy-diagram__section--collections" }, /* @__PURE__ */ React34.createElement("h3", null, "IIIF Collection(s)"), /* @__PURE__ */ React34.createElement("span", { className: "canopy-diagram__section-summary" }, "Source collections contribute 105 total manifests that Canopy retrieves as-is via IIIF endpoints."), /* @__PURE__ */ React34.createElement("div", { className: "canopy-diagram__grid" }, /* @__PURE__ */ React34.createElement("article", null, /* @__PURE__ */ React34.createElement("h4", null, "Collection A"), /* @__PURE__ */ React34.createElement("ul", null, /* @__PURE__ */ React34.createElement("li", null, "70 Manifests"), /* @__PURE__ */ React34.createElement("li", null, "IIIF Images + A/V"), /* @__PURE__ */ React34.createElement("li", null, "Textual Annotations"))), /* @__PURE__ */ React34.createElement("article", null, /* @__PURE__ */ React34.createElement("h4", null, "Collection B"), /* @__PURE__ */ React34.createElement("ul", null, /* @__PURE__ */ React34.createElement("li", null, "35 Manifests"), /* @__PURE__ */ React34.createElement("li", null, "IIIF Images + A/V"), /* @__PURE__ */ React34.createElement("li", null, "Textual Annotations"))))), /* @__PURE__ */ React34.createElement("div", { className: "canopy-diagram__arrow", "aria-hidden": "true" }, /* @__PURE__ */ React34.createElement("span", { className: "canopy-diagram__arrow-line" }), /* @__PURE__ */ React34.createElement("span", { className: "canopy-diagram__arrow-head" })), /* @__PURE__ */ React34.createElement("section", { className: "canopy-diagram__section canopy-diagram__section--build" }, /* @__PURE__ */ React34.createElement("h3", null, "Canopy Build Process"), /* @__PURE__ */ React34.createElement("span", { className: "canopy-diagram__section-summary" }, "Canopy syncs manifests, page content, and annotations before bundling the site."), /* @__PURE__ */ React34.createElement("div", { className: "canopy-diagram__grid" }, /* @__PURE__ */ React34.createElement("article", null, /* @__PURE__ */ React34.createElement("h4", null, "Automated content"), /* @__PURE__ */ React34.createElement("ul", null, /* @__PURE__ */ React34.createElement("li", null, "105 manifests \u2192 105 work pages"), /* @__PURE__ */ React34.createElement("li", null, "One page per manifest"), /* @__PURE__ */ React34.createElement("li", null, "Customize page layout"))), /* @__PURE__ */ React34.createElement("article", null, /* @__PURE__ */ React34.createElement("h4", null, "Contextual content"), /* @__PURE__ */ React34.createElement("ul", null, /* @__PURE__ */ React34.createElement("li", null, "Markdown & MDX pages"), /* @__PURE__ */ React34.createElement("li", null, "Author narratives"), /* @__PURE__ */ React34.createElement("li", null, "Reference manifests inline"))), /* @__PURE__ */ React34.createElement("article", null, /* @__PURE__ */ React34.createElement("h4", null, "Search index"), /* @__PURE__ */ React34.createElement("ul", null, /* @__PURE__ */ React34.createElement("li", null, "Combines works + pages"), /* @__PURE__ */ React34.createElement("li", null, "Customize result layout"), /* @__PURE__ */ React34.createElement("li", null, "Optional annotations"))))), /* @__PURE__ */ React34.createElement("div", { className: "canopy-diagram__arrow", "aria-hidden": "true" }, /* @__PURE__ */ React34.createElement("span", { className: "canopy-diagram__arrow-line" }), /* @__PURE__ */ React34.createElement("span", { className: "canopy-diagram__arrow-head" })), /* @__PURE__ */ React34.createElement("section", { className: "canopy-diagram__section canopy-diagram__section--output" }, /* @__PURE__ */ React34.createElement("h3", null, "Static Digital Project"), /* @__PURE__ */ React34.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__ */ React34.createElement("div", { className: "canopy-diagram__grid" }, /* @__PURE__ */ React34.createElement("article", null, /* @__PURE__ */ React34.createElement("h4", null, "Work pages"), /* @__PURE__ */ React34.createElement("ul", null, /* @__PURE__ */ React34.createElement("li", null, "105 generated HTML pages"), /* @__PURE__ */ React34.createElement("li", null, "Each links back to source manifests"), /* @__PURE__ */ React34.createElement("li", null, "Styled with Canopy components"))), /* @__PURE__ */ React34.createElement("article", null, /* @__PURE__ */ React34.createElement("h4", null, "Custom pages"), /* @__PURE__ */ React34.createElement("ul", null, /* @__PURE__ */ React34.createElement("li", null, "Markdown & MDX-authored content"), /* @__PURE__ */ React34.createElement("li", null, "Reusable layouts for narratives"), /* @__PURE__ */ React34.createElement("li", null, "Embed IIIF media & interstitials"))), /* @__PURE__ */ React34.createElement("article", null, /* @__PURE__ */ React34.createElement("h4", null, "Search bundle"), /* @__PURE__ */ React34.createElement("ul", null, /* @__PURE__ */ React34.createElement("li", null, "Static FlexSearch index"), /* @__PURE__ */ React34.createElement("li", null, "Works + pages share records"), /* @__PURE__ */ React34.createElement("li", null, "Optional annotation dataset"))))));
2100
2420
  }
2101
2421
 
2102
2422
  // ui/src/content/timeline/Timeline.jsx
2103
- import React35 from "react";
2423
+ import React36 from "react";
2104
2424
 
2105
2425
  // ui/src/content/timeline/date-utils.js
2106
2426
  var FALLBACK_LOCALE = (() => {
@@ -2253,7 +2573,7 @@ function clampProgress(value) {
2253
2573
  }
2254
2574
 
2255
2575
  // ui/src/layout/ReferencedManifestCard.jsx
2256
- import React34 from "react";
2576
+ import React35 from "react";
2257
2577
  function normalizeMetadata(metadata, summary) {
2258
2578
  if (Array.isArray(metadata) && metadata.length) {
2259
2579
  return metadata.filter(Boolean);
@@ -2287,7 +2607,7 @@ function ReferencedManifestCard({
2287
2607
  "canopy-referenced-manifest-card",
2288
2608
  className
2289
2609
  ].filter(Boolean).join(" ");
2290
- return /* @__PURE__ */ React34.createElement(
2610
+ return /* @__PURE__ */ React35.createElement(
2291
2611
  TeaserCard,
2292
2612
  {
2293
2613
  href: resolvedHref || void 0,
@@ -2469,14 +2789,14 @@ function TimelineConnector({ side, isActive, highlight }) {
2469
2789
  "canopy-timeline__connector-dot",
2470
2790
  highlight || isActive ? "is-active" : ""
2471
2791
  ].filter(Boolean).join(" ");
2472
- return /* @__PURE__ */ React35.createElement("span", { className: connectorClasses, "aria-hidden": "true" }, side === "left" ? /* @__PURE__ */ React35.createElement(React35.Fragment, null, /* @__PURE__ */ React35.createElement("span", { className: "canopy-timeline__connector-line" }), /* @__PURE__ */ React35.createElement("span", { className: dotClasses })) : /* @__PURE__ */ React35.createElement(React35.Fragment, null, /* @__PURE__ */ React35.createElement("span", { className: dotClasses }), /* @__PURE__ */ React35.createElement("span", { className: "canopy-timeline__connector-line" })));
2792
+ return /* @__PURE__ */ React36.createElement("span", { className: connectorClasses, "aria-hidden": "true" }, side === "left" ? /* @__PURE__ */ React36.createElement(React36.Fragment, null, /* @__PURE__ */ React36.createElement("span", { className: "canopy-timeline__connector-line" }), /* @__PURE__ */ React36.createElement("span", { className: dotClasses })) : /* @__PURE__ */ React36.createElement(React36.Fragment, null, /* @__PURE__ */ React36.createElement("span", { className: dotClasses }), /* @__PURE__ */ React36.createElement("span", { className: "canopy-timeline__connector-line" })));
2473
2793
  }
2474
2794
  function renderResourceSection(point) {
2475
2795
  if (!point) return null;
2476
2796
  const manifestCards = Array.isArray(point.manifests) ? point.manifests.filter(Boolean) : [];
2477
2797
  const legacyResources = Array.isArray(point.resources) ? point.resources.filter(Boolean) : [];
2478
2798
  if (!manifestCards.length && !legacyResources.length) return null;
2479
- return /* @__PURE__ */ React35.createElement("div", { className: "canopy-timeline__resources" }, /* @__PURE__ */ React35.createElement("div", { className: "canopy-timeline__resources-list" }, manifestCards.map((manifest) => /* @__PURE__ */ React35.createElement("div", { key: manifest.id || manifest.href }, /* @__PURE__ */ React35.createElement(ReferencedManifestCard, { manifest }))), legacyResources.map((resource, idx) => /* @__PURE__ */ React35.createElement("div", { key: resource.id || resource.href || `legacy-${idx}` }, /* @__PURE__ */ React35.createElement(
2799
+ return /* @__PURE__ */ React36.createElement("div", { className: "canopy-timeline__resources" }, /* @__PURE__ */ React36.createElement("div", { className: "canopy-timeline__resources-list" }, manifestCards.map((manifest) => /* @__PURE__ */ React36.createElement("div", { key: manifest.id || manifest.href }, /* @__PURE__ */ React36.createElement(ReferencedManifestCard, { manifest }))), legacyResources.map((resource, idx) => /* @__PURE__ */ React36.createElement("div", { key: resource.id || resource.href || `legacy-${idx}` }, /* @__PURE__ */ React36.createElement(
2480
2800
  TeaserCard,
2481
2801
  {
2482
2802
  href: resource.href,
@@ -2501,26 +2821,26 @@ function Timeline({
2501
2821
  ...rest
2502
2822
  }) {
2503
2823
  const payloadPoints = payload && Array.isArray(payload.points) ? payload.points : null;
2504
- const rawPoints = React35.useMemo(() => {
2824
+ const rawPoints = React36.useMemo(() => {
2505
2825
  if (Array.isArray(pointsProp) && pointsProp.length) return pointsProp;
2506
2826
  if (payloadPoints && payloadPoints.length) return payloadPoints;
2507
2827
  return [];
2508
2828
  }, [pointsProp, payloadPoints]);
2509
- const sanitizedPoints = React35.useMemo(
2829
+ const sanitizedPoints = React36.useMemo(
2510
2830
  () => sanitizePoints(rawPoints),
2511
2831
  [rawPoints]
2512
2832
  );
2513
2833
  const localeValue = payload && payload.locale ? payload.locale : localeProp;
2514
- const baseLocale = React35.useMemo(
2834
+ const baseLocale = React36.useMemo(
2515
2835
  () => createLocale(localeValue),
2516
2836
  [localeValue]
2517
2837
  );
2518
2838
  const rangeInput = payload && payload.range ? payload.range : rangeProp || {};
2519
- const rangeOverrides = React35.useMemo(
2839
+ const rangeOverrides = React36.useMemo(
2520
2840
  () => deriveRangeOverrides(sanitizedPoints, rangeInput),
2521
2841
  [sanitizedPoints, rangeInput]
2522
2842
  );
2523
- const effectiveRange = React35.useMemo(
2843
+ const effectiveRange = React36.useMemo(
2524
2844
  () => normalizeRange({
2525
2845
  ...rangeOverrides,
2526
2846
  locale: baseLocale
@@ -2529,7 +2849,7 @@ function Timeline({
2529
2849
  );
2530
2850
  const spanStart = effectiveRange.startDate.getTime();
2531
2851
  const span = effectiveRange.span;
2532
- const pointsWithPosition = React35.useMemo(() => {
2852
+ const pointsWithPosition = React36.useMemo(() => {
2533
2853
  if (!sanitizedPoints.length) return [];
2534
2854
  return sanitizedPoints.map((point, index) => {
2535
2855
  const timestamp = point.meta.timestamp;
@@ -2543,29 +2863,29 @@ function Timeline({
2543
2863
  };
2544
2864
  });
2545
2865
  }, [sanitizedPoints, spanStart, span]);
2546
- const [activeId, setActiveId] = React35.useState(
2866
+ const [activeId, setActiveId] = React36.useState(
2547
2867
  () => getActivePointId(pointsWithPosition)
2548
2868
  );
2549
- React35.useEffect(() => {
2869
+ React36.useEffect(() => {
2550
2870
  setActiveId(getActivePointId(pointsWithPosition));
2551
2871
  }, [pointsWithPosition]);
2552
2872
  const thresholdValue = typeof thresholdProp === "number" ? thresholdProp : payload && payload.threshold != null ? payload.threshold : null;
2553
2873
  const stepsValue = typeof steps === "number" ? Number(steps) : payload && typeof payload.steps === "number" ? Number(payload.steps) : null;
2554
- const thresholdMs = React35.useMemo(
2874
+ const thresholdMs = React36.useMemo(
2555
2875
  () => getThresholdMs(thresholdValue, effectiveRange.granularity),
2556
2876
  [thresholdValue, effectiveRange.granularity]
2557
2877
  );
2558
- const groupedEntries = React35.useMemo(
2878
+ const groupedEntries = React36.useMemo(
2559
2879
  () => buildGroupedEntries(pointsWithPosition, thresholdMs, {
2560
2880
  granularity: effectiveRange.granularity,
2561
2881
  locale: baseLocale
2562
2882
  }),
2563
2883
  [pointsWithPosition, thresholdMs, effectiveRange.granularity, baseLocale]
2564
2884
  );
2565
- const [expandedGroupIds, setExpandedGroupIds] = React35.useState(
2885
+ const [expandedGroupIds, setExpandedGroupIds] = React36.useState(
2566
2886
  () => /* @__PURE__ */ new Set()
2567
2887
  );
2568
- React35.useEffect(() => {
2888
+ React36.useEffect(() => {
2569
2889
  setExpandedGroupIds((prev) => {
2570
2890
  if (!prev || prev.size === 0) return prev;
2571
2891
  const validIds = new Set(
@@ -2580,7 +2900,7 @@ function Timeline({
2580
2900
  return changed ? next : prev;
2581
2901
  });
2582
2902
  }, [groupedEntries]);
2583
- const toggleGroup = React35.useCallback((groupId) => {
2903
+ const toggleGroup = React36.useCallback((groupId) => {
2584
2904
  setExpandedGroupIds((prev) => {
2585
2905
  const next = new Set(prev || []);
2586
2906
  if (next.has(groupId)) next.delete(groupId);
@@ -2603,7 +2923,7 @@ function Timeline({
2603
2923
  point.id === activeId ? "is-active" : "",
2604
2924
  point.highlight ? "is-highlighted" : ""
2605
2925
  ].filter(Boolean).join(" ");
2606
- const connector = /* @__PURE__ */ React35.createElement(
2926
+ const connector = /* @__PURE__ */ React36.createElement(
2607
2927
  TimelineConnector,
2608
2928
  {
2609
2929
  side: point.side,
@@ -2611,9 +2931,9 @@ function Timeline({
2611
2931
  highlight: point.highlight
2612
2932
  }
2613
2933
  );
2614
- const body = /* @__PURE__ */ React35.createElement("div", { className: "canopy-timeline__point-body" }, /* @__PURE__ */ React35.createElement("span", { className: "canopy-timeline__point-date" }, point.meta.label), /* @__PURE__ */ React35.createElement("span", { className: "canopy-timeline__point-title" }, point.title), point.summary ? /* @__PURE__ */ React35.createElement("span", { className: "canopy-timeline__point-summary" }, point.summary) : null);
2934
+ const body = /* @__PURE__ */ React36.createElement("div", { className: "canopy-timeline__point-body" }, /* @__PURE__ */ React36.createElement("span", { className: "canopy-timeline__point-date" }, point.meta.label), /* @__PURE__ */ React36.createElement("span", { className: "canopy-timeline__point-title" }, point.title), point.summary ? /* @__PURE__ */ React36.createElement("span", { className: "canopy-timeline__point-summary" }, point.summary) : null);
2615
2935
  const resourceSection = renderResourceSection(point);
2616
- return /* @__PURE__ */ React35.createElement(
2936
+ return /* @__PURE__ */ React36.createElement(
2617
2937
  "div",
2618
2938
  {
2619
2939
  key: point.id,
@@ -2621,7 +2941,7 @@ function Timeline({
2621
2941
  style: wrapperStyle,
2622
2942
  role: "listitem"
2623
2943
  },
2624
- point.side === "left" ? /* @__PURE__ */ React35.createElement(React35.Fragment, null, /* @__PURE__ */ React35.createElement("div", { className: cardClasses }, body, resourceSection), connector) : /* @__PURE__ */ React35.createElement(React35.Fragment, null, connector, /* @__PURE__ */ React35.createElement("div", { className: cardClasses }, body, resourceSection))
2944
+ point.side === "left" ? /* @__PURE__ */ React36.createElement(React36.Fragment, null, /* @__PURE__ */ React36.createElement("div", { className: cardClasses }, body, resourceSection), connector) : /* @__PURE__ */ React36.createElement(React36.Fragment, null, connector, /* @__PURE__ */ React36.createElement("div", { className: cardClasses }, body, resourceSection))
2625
2945
  );
2626
2946
  }
2627
2947
  function renderGroupEntry(entry) {
@@ -2632,7 +2952,7 @@ function Timeline({
2632
2952
  const wrapperStyle = { top: `${entry.progress * 100}%` };
2633
2953
  const isExpanded = expandedGroupIds.has(entry.id);
2634
2954
  const hasActivePoint = entry.points.some((point) => point.id === activeId);
2635
- const connector = /* @__PURE__ */ React35.createElement(
2955
+ const connector = /* @__PURE__ */ React36.createElement(
2636
2956
  TimelineConnector,
2637
2957
  {
2638
2958
  side: entry.side,
@@ -2646,7 +2966,7 @@ function Timeline({
2646
2966
  hasActivePoint ? "is-active" : ""
2647
2967
  ].filter(Boolean).join(" ");
2648
2968
  const countLabel = `${entry.count} event${entry.count > 1 ? "s" : ""}`;
2649
- const header = /* @__PURE__ */ React35.createElement("div", { className: "canopy-timeline__group-header" }, /* @__PURE__ */ React35.createElement("div", { className: "canopy-timeline__group-summary" }, /* @__PURE__ */ React35.createElement("span", { className: "canopy-timeline__point-date" }, entry.label), /* @__PURE__ */ React35.createElement("span", { className: "canopy-timeline__group-count" }, countLabel)), /* @__PURE__ */ React35.createElement(
2969
+ const header = /* @__PURE__ */ React36.createElement("div", { className: "canopy-timeline__group-header" }, /* @__PURE__ */ React36.createElement("div", { className: "canopy-timeline__group-summary" }, /* @__PURE__ */ React36.createElement("span", { className: "canopy-timeline__point-date" }, entry.label), /* @__PURE__ */ React36.createElement("span", { className: "canopy-timeline__group-count" }, countLabel)), /* @__PURE__ */ React36.createElement(
2650
2970
  "button",
2651
2971
  {
2652
2972
  type: "button",
@@ -2656,7 +2976,7 @@ function Timeline({
2656
2976
  },
2657
2977
  isExpanded ? "Hide details" : "Show details"
2658
2978
  ));
2659
- const groupPoints = isExpanded ? /* @__PURE__ */ React35.createElement("div", { className: "canopy-timeline__group-points" }, entry.points.map((point) => /* @__PURE__ */ React35.createElement(
2979
+ const groupPoints = isExpanded ? /* @__PURE__ */ React36.createElement("div", { className: "canopy-timeline__group-points" }, entry.points.map((point) => /* @__PURE__ */ React36.createElement(
2660
2980
  "button",
2661
2981
  {
2662
2982
  key: point.id,
@@ -2667,11 +2987,11 @@ function Timeline({
2667
2987
  ].filter(Boolean).join(" "),
2668
2988
  onClick: () => setActiveId(point.id)
2669
2989
  },
2670
- /* @__PURE__ */ React35.createElement("span", { className: "canopy-timeline__point-date" }, point.meta.label),
2671
- /* @__PURE__ */ React35.createElement("span", { className: "canopy-timeline__group-point-title" }, point.title)
2990
+ /* @__PURE__ */ React36.createElement("span", { className: "canopy-timeline__point-date" }, point.meta.label),
2991
+ /* @__PURE__ */ React36.createElement("span", { className: "canopy-timeline__group-point-title" }, point.title)
2672
2992
  ))) : null;
2673
- const groupCard = /* @__PURE__ */ React35.createElement("div", { className: groupClasses }, header, groupPoints);
2674
- return /* @__PURE__ */ React35.createElement(
2993
+ const groupCard = /* @__PURE__ */ React36.createElement("div", { className: groupClasses }, header, groupPoints);
2994
+ return /* @__PURE__ */ React36.createElement(
2675
2995
  "div",
2676
2996
  {
2677
2997
  key: entry.id,
@@ -2679,17 +2999,17 @@ function Timeline({
2679
2999
  style: wrapperStyle,
2680
3000
  role: "listitem"
2681
3001
  },
2682
- entry.side === "left" ? /* @__PURE__ */ React35.createElement(React35.Fragment, null, groupCard, connector) : /* @__PURE__ */ React35.createElement(React35.Fragment, null, connector, groupCard)
3002
+ entry.side === "left" ? /* @__PURE__ */ React36.createElement(React36.Fragment, null, groupCard, connector) : /* @__PURE__ */ React36.createElement(React36.Fragment, null, connector, groupCard)
2683
3003
  );
2684
3004
  }
2685
- return /* @__PURE__ */ React35.createElement("section", { className: containerClasses, ...rest }, title ? /* @__PURE__ */ React35.createElement("h2", { className: "canopy-timeline__title" }, title) : null, description ? /* @__PURE__ */ React35.createElement("p", { className: "canopy-timeline__description" }, description) : null, rangeLabel ? /* @__PURE__ */ React35.createElement("p", { className: "canopy-timeline__range", "aria-live": "polite" }, rangeLabel) : null, /* @__PURE__ */ React35.createElement("div", { className: "canopy-timeline__body" }, /* @__PURE__ */ React35.createElement(
3005
+ return /* @__PURE__ */ React36.createElement("section", { className: containerClasses, ...rest }, title ? /* @__PURE__ */ React36.createElement("h2", { className: "canopy-timeline__title" }, title) : null, description ? /* @__PURE__ */ React36.createElement("p", { className: "canopy-timeline__description" }, description) : null, rangeLabel ? /* @__PURE__ */ React36.createElement("p", { className: "canopy-timeline__range", "aria-live": "polite" }, rangeLabel) : null, /* @__PURE__ */ React36.createElement("div", { className: "canopy-timeline__body" }, /* @__PURE__ */ React36.createElement(
2686
3006
  "div",
2687
3007
  {
2688
3008
  className: "canopy-timeline__list",
2689
3009
  role: "list",
2690
3010
  style: { minHeight: trackHeight }
2691
3011
  },
2692
- /* @__PURE__ */ React35.createElement("div", { className: "canopy-timeline__spine", "aria-hidden": "true" }),
3012
+ /* @__PURE__ */ React36.createElement("div", { className: "canopy-timeline__spine", "aria-hidden": "true" }),
2693
3013
  renderSteps(stepsValue, effectiveRange),
2694
3014
  groupedEntries.map((entry) => {
2695
3015
  if (entry.type === "group") return renderGroupEntry(entry);
@@ -2704,7 +3024,7 @@ function renderSteps(stepSize, range) {
2704
3024
  const markers = [];
2705
3025
  if (startYear < endYear) {
2706
3026
  markers.push(
2707
- /* @__PURE__ */ React35.createElement(
3027
+ /* @__PURE__ */ React36.createElement(
2708
3028
  "span",
2709
3029
  {
2710
3030
  key: "timeline-step-start",
@@ -2712,12 +3032,12 @@ function renderSteps(stepSize, range) {
2712
3032
  style: { top: "0%" },
2713
3033
  "aria-hidden": "true"
2714
3034
  },
2715
- /* @__PURE__ */ React35.createElement("span", { className: "canopy-timeline__step-line" }),
2716
- /* @__PURE__ */ React35.createElement("span", { className: "canopy-timeline__step-label" }, startYear)
3035
+ /* @__PURE__ */ React36.createElement("span", { className: "canopy-timeline__step-line" }),
3036
+ /* @__PURE__ */ React36.createElement("span", { className: "canopy-timeline__step-label" }, startYear)
2717
3037
  )
2718
3038
  );
2719
3039
  markers.push(
2720
- /* @__PURE__ */ React35.createElement(
3040
+ /* @__PURE__ */ React36.createElement(
2721
3041
  "span",
2722
3042
  {
2723
3043
  key: "timeline-step-end",
@@ -2725,8 +3045,8 @@ function renderSteps(stepSize, range) {
2725
3045
  style: { top: "100%" },
2726
3046
  "aria-hidden": "true"
2727
3047
  },
2728
- /* @__PURE__ */ React35.createElement("span", { className: "canopy-timeline__step-line" }),
2729
- /* @__PURE__ */ React35.createElement("span", { className: "canopy-timeline__step-label" }, endYear)
3048
+ /* @__PURE__ */ React36.createElement("span", { className: "canopy-timeline__step-line" }),
3049
+ /* @__PURE__ */ React36.createElement("span", { className: "canopy-timeline__step-label" }, endYear)
2730
3050
  )
2731
3051
  );
2732
3052
  }
@@ -2736,7 +3056,7 @@ function renderSteps(stepSize, range) {
2736
3056
  const progress = (timestamp - range.startDate.getTime()) / range.span;
2737
3057
  if (progress <= 0 || progress >= 1) continue;
2738
3058
  markers.push(
2739
- /* @__PURE__ */ React35.createElement(
3059
+ /* @__PURE__ */ React36.createElement(
2740
3060
  "span",
2741
3061
  {
2742
3062
  key: `timeline-step-${year}`,
@@ -2744,8 +3064,8 @@ function renderSteps(stepSize, range) {
2744
3064
  style: { top: `calc(${progress * 100}% - 0.5px)` },
2745
3065
  "aria-hidden": "true"
2746
3066
  },
2747
- /* @__PURE__ */ React35.createElement("span", { className: "canopy-timeline__step-line" }),
2748
- /* @__PURE__ */ React35.createElement("span", { className: "canopy-timeline__step-label" }, year)
3067
+ /* @__PURE__ */ React36.createElement("span", { className: "canopy-timeline__step-line" }),
3068
+ /* @__PURE__ */ React36.createElement("span", { className: "canopy-timeline__step-label" }, year)
2749
3069
  )
2750
3070
  );
2751
3071
  }
@@ -2759,7 +3079,7 @@ function TimelinePoint() {
2759
3079
  TimelinePoint.displayName = "TimelinePoint";
2760
3080
 
2761
3081
  // ui/src/content/map/Map.jsx
2762
- import React36 from "react";
3082
+ import React37 from "react";
2763
3083
  import { createRoot } from "react-dom/client";
2764
3084
  var DEFAULT_TILE_LAYERS = [
2765
3085
  {
@@ -3026,7 +3346,7 @@ function MapPopupContent({ marker }) {
3026
3346
  ...manifest,
3027
3347
  href: manifest.href ? withBasePath(manifest.href) : manifest.href || ""
3028
3348
  }));
3029
- return /* @__PURE__ */ React36.createElement("div", { className: "canopy-map__popup" }, thumbnail ? /* @__PURE__ */ React36.createElement("div", { className: "canopy-map__popup-media" }, /* @__PURE__ */ React36.createElement(
3349
+ return /* @__PURE__ */ React37.createElement("div", { className: "canopy-map__popup" }, thumbnail ? /* @__PURE__ */ React37.createElement("div", { className: "canopy-map__popup-media" }, /* @__PURE__ */ React37.createElement(
3030
3350
  "img",
3031
3351
  {
3032
3352
  src: thumbnail,
@@ -3035,19 +3355,19 @@ function MapPopupContent({ marker }) {
3035
3355
  width: typeof thumbWidth === "number" && thumbWidth > 0 ? thumbWidth : void 0,
3036
3356
  height: typeof thumbHeight === "number" && thumbHeight > 0 ? thumbHeight : void 0
3037
3357
  }
3038
- )) : null, /* @__PURE__ */ React36.createElement("div", { className: "canopy-map__popup-body" }, title ? href ? /* @__PURE__ */ React36.createElement("a", { href, className: "canopy-map__popup-title" }, title) : /* @__PURE__ */ React36.createElement("span", { className: "canopy-map__popup-title" }, title) : null, summary ? /* @__PURE__ */ React36.createElement("p", { className: "canopy-map__popup-summary" }, summary) : null, marker.detailsHtml ? /* @__PURE__ */ React36.createElement(
3358
+ )) : null, /* @__PURE__ */ React37.createElement("div", { className: "canopy-map__popup-body" }, title ? href ? /* @__PURE__ */ React37.createElement("a", { href, className: "canopy-map__popup-title" }, title) : /* @__PURE__ */ React37.createElement("span", { className: "canopy-map__popup-title" }, title) : null, summary ? /* @__PURE__ */ React37.createElement("p", { className: "canopy-map__popup-summary" }, summary) : null, marker.detailsHtml ? /* @__PURE__ */ React37.createElement(
3039
3359
  "div",
3040
3360
  {
3041
3361
  className: "canopy-map__popup-details",
3042
3362
  dangerouslySetInnerHTML: { __html: marker.detailsHtml }
3043
3363
  }
3044
- ) : null, !summary && !marker.detailsHtml && href && !title ? /* @__PURE__ */ React36.createElement("a", { href, className: "canopy-map__popup-link" }, "View item") : null, normalizedManifests.length ? /* @__PURE__ */ React36.createElement("div", { className: "canopy-map__popup-manifests" }, /* @__PURE__ */ React36.createElement("div", { className: "canopy-map__popup-manifests-list" }, normalizedManifests.map((manifest, index) => /* @__PURE__ */ React36.createElement(
3364
+ ) : null, !summary && !marker.detailsHtml && href && !title ? /* @__PURE__ */ React37.createElement("a", { href, className: "canopy-map__popup-link" }, "View item") : null, normalizedManifests.length ? /* @__PURE__ */ React37.createElement("div", { className: "canopy-map__popup-manifests" }, /* @__PURE__ */ React37.createElement("div", { className: "canopy-map__popup-manifests-list" }, normalizedManifests.map((manifest, index) => /* @__PURE__ */ React37.createElement(
3045
3365
  "div",
3046
3366
  {
3047
3367
  key: manifest.id || manifest.href || `manifest-${index}`,
3048
3368
  className: "canopy-map__popup-manifests-item"
3049
3369
  },
3050
- /* @__PURE__ */ React36.createElement(ReferencedManifestCard, { manifest })
3370
+ /* @__PURE__ */ React37.createElement(ReferencedManifestCard, { manifest })
3051
3371
  )))) : null));
3052
3372
  }
3053
3373
  function renderPopup(marker) {
@@ -3059,7 +3379,7 @@ function renderPopup(marker) {
3059
3379
  if (hadError) return;
3060
3380
  try {
3061
3381
  if (!root) root = createRoot(container);
3062
- root.render(/* @__PURE__ */ React36.createElement(MapPopupContent, { marker }));
3382
+ root.render(/* @__PURE__ */ React37.createElement(MapPopupContent, { marker }));
3063
3383
  } catch (error) {
3064
3384
  hadError = true;
3065
3385
  if (root) {
@@ -3184,26 +3504,26 @@ function Map2({
3184
3504
  defaultCenter = null,
3185
3505
  defaultZoom = null
3186
3506
  } = {}) {
3187
- const containerRef = React36.useRef(null);
3188
- const mapRef = React36.useRef(null);
3189
- const layerRef = React36.useRef(null);
3190
- const [leafletLib, setLeafletLib] = React36.useState(() => resolveGlobalLeaflet());
3191
- const [leafletError, setLeafletError] = React36.useState(null);
3507
+ const containerRef = React37.useRef(null);
3508
+ const mapRef = React37.useRef(null);
3509
+ const layerRef = React37.useRef(null);
3510
+ const [leafletLib, setLeafletLib] = React37.useState(() => resolveGlobalLeaflet());
3511
+ const [leafletError, setLeafletError] = React37.useState(null);
3192
3512
  const datasetInfo = navDataset && typeof navDataset === "object" ? navDataset : null;
3193
3513
  const datasetHref = datasetInfo && datasetInfo.href || "/api/navplace.json";
3194
3514
  const datasetVersion = datasetInfo && datasetInfo.version;
3195
3515
  const datasetHasFeatures = !!(datasetInfo && datasetInfo.hasFeatures);
3196
- const [navState, setNavState] = React36.useState(() => ({
3516
+ const [navState, setNavState] = React37.useState(() => ({
3197
3517
  loading: false,
3198
3518
  error: null,
3199
3519
  markers: []
3200
3520
  }));
3201
- const [iiifTargets, setIiifTargets] = React36.useState(() => ({
3521
+ const [iiifTargets, setIiifTargets] = React37.useState(() => ({
3202
3522
  loading: false,
3203
3523
  error: null,
3204
3524
  keys: []
3205
3525
  }));
3206
- React36.useEffect(() => {
3526
+ React37.useEffect(() => {
3207
3527
  if (!iiifContent) {
3208
3528
  setIiifTargets({ loading: false, error: null, keys: [] });
3209
3529
  return;
@@ -3251,7 +3571,7 @@ function Map2({
3251
3571
  const navTargets = iiifTargets.keys || [];
3252
3572
  const navTargetsKey = navTargets.join("|");
3253
3573
  const shouldFetchNav = datasetHasFeatures && navTargets.length > 0;
3254
- React36.useEffect(() => {
3574
+ React37.useEffect(() => {
3255
3575
  if (!shouldFetchNav) {
3256
3576
  setNavState({ loading: false, error: null, markers: [] });
3257
3577
  return void 0;
@@ -3283,7 +3603,7 @@ function Map2({
3283
3603
  cancelled = true;
3284
3604
  };
3285
3605
  }, [datasetHref, datasetVersion, navTargetsKey, shouldFetchNav]);
3286
- React36.useEffect(() => {
3606
+ React37.useEffect(() => {
3287
3607
  if (leafletLib) return;
3288
3608
  let cancelled = false;
3289
3609
  waitForLeaflet().then((lib) => {
@@ -3295,7 +3615,7 @@ function Map2({
3295
3615
  cancelled = true;
3296
3616
  };
3297
3617
  }, [leafletLib]);
3298
- const navMatchMap = React36.useMemo(() => {
3618
+ const navMatchMap = React37.useMemo(() => {
3299
3619
  const matchMap = createMarkerMap();
3300
3620
  (navState.markers || []).forEach((marker) => {
3301
3621
  if (!marker || !Array.isArray(marker.matchKeys)) return;
@@ -3306,7 +3626,7 @@ function Map2({
3306
3626
  });
3307
3627
  return matchMap;
3308
3628
  }, [navState.markers]);
3309
- const normalizedCustom = React36.useMemo(() => {
3629
+ const normalizedCustom = React37.useMemo(() => {
3310
3630
  return normalizeCustomMarkers(customPoints).map((point) => {
3311
3631
  if (!point || point.thumbnail || !point.href) return point;
3312
3632
  const match = navMatchMap.get(normalizeKey(point.href));
@@ -3321,11 +3641,11 @@ function Map2({
3321
3641
  };
3322
3642
  });
3323
3643
  }, [customPoints, navMatchMap]);
3324
- const allMarkers = React36.useMemo(() => {
3644
+ const allMarkers = React37.useMemo(() => {
3325
3645
  return [...navState.markers || [], ...normalizedCustom];
3326
3646
  }, [navState.markers, normalizedCustom]);
3327
- const clusterOptions = React36.useMemo(() => buildClusterOptions(leafletLib), [leafletLib]);
3328
- React36.useEffect(() => {
3647
+ const clusterOptions = React37.useMemo(() => buildClusterOptions(leafletLib), [leafletLib]);
3648
+ React37.useEffect(() => {
3329
3649
  if (!containerRef.current || mapRef.current || !leafletLib) return void 0;
3330
3650
  const map = leafletLib.map(containerRef.current, {
3331
3651
  zoomControl: true,
@@ -3363,7 +3683,7 @@ function Map2({
3363
3683
  layerRef.current = null;
3364
3684
  };
3365
3685
  }, [tileLayers, scrollWheelZoom, cluster, clusterOptions, leafletLib]);
3366
- React36.useEffect(() => {
3686
+ React37.useEffect(() => {
3367
3687
  const map = mapRef.current;
3368
3688
  const layer = layerRef.current;
3369
3689
  if (!map || !layer || !leafletLib) return;
@@ -3467,14 +3787,14 @@ function Map2({
3467
3787
  ].filter(Boolean).join(" ");
3468
3788
  const statusLabel = leafletError ? leafletError.message || "Failed to load map library" : !leafletLib ? "Loading map\u2026" : iiifTargets.error ? iiifTargets.error : datasetUnavailable ? "Map data is unavailable for this site." : navState.error ? navState.error : isLoadingMarkers ? "Loading map data\u2026" : !iiifContent && !hasCustomPoints ? "Add iiifContent or MapPoint markers to populate this map." : !hasMarkers ? "No map locations available." : "";
3469
3789
  const showStatus = Boolean(statusLabel);
3470
- return /* @__PURE__ */ React36.createElement("div", { className: rootClass, id: id || void 0, style: style || void 0 }, /* @__PURE__ */ React36.createElement(
3790
+ return /* @__PURE__ */ React37.createElement("div", { className: rootClass, id: id || void 0, style: style || void 0 }, /* @__PURE__ */ React37.createElement(
3471
3791
  "div",
3472
3792
  {
3473
3793
  ref: containerRef,
3474
3794
  className: "canopy-map__canvas",
3475
3795
  style: { height: height || "600px" }
3476
3796
  }
3477
- ), showStatus ? /* @__PURE__ */ React36.createElement("div", { className: "canopy-map__status", "aria-live": "polite" }, statusLabel) : null);
3797
+ ), showStatus ? /* @__PURE__ */ React37.createElement("div", { className: "canopy-map__status", "aria-live": "polite" }, statusLabel) : null);
3478
3798
  }
3479
3799
 
3480
3800
  // ui/src/content/map/MapPoint.jsx